Compare commits
36 Commits
renovate/l
...
polecat/gu
| Author | SHA1 | Date | |
|---|---|---|---|
| 14d659bef0 | |||
| 1b585847ab | |||
| e7906331dc | |||
| dc722843a9 | |||
| 03f169284d | |||
| 8908500073 | |||
| 87f6d5c759 | |||
| a851c2551c | |||
|
|
6cf63e86c1 | ||
| c3ed6c0a26 | |||
|
|
53fa89b2e9 | ||
| 3acf9d2796 | |||
| 123e7d3b3a | |||
|
|
56097aefa4 | ||
| 21a8b5c5d9 | |||
| 8f8582b0f3 | |||
| 94fb5a3e64 | |||
| 7df68ba8c8 | |||
| 2799632308 | |||
| 346c031278 | |||
| 188d2befb0 | |||
| 8e8b5f4304 | |||
| 4098ee3987 | |||
| e1e37da7c2 | |||
| a46d11a770 | |||
|
|
8553b9826e | ||
| a0c081e12e | |||
| d92e4b3ddf | |||
| 70b40966be | |||
| 475a633ab7 | |||
| a39416c9db | |||
| 63c3f4e84d | |||
| baf64f7f4a | |||
|
|
f0b6ede7ed | ||
| d0cb16391f | |||
| d872293f19 |
83
flake.lock
generated
83
flake.lock
generated
@@ -8,17 +8,18 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769367425,
|
||||
"narHash": "sha256-r7VNku6B8VNYr88jQfuMInu6tKCPtoIXFYozTTAJpMY=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "b0a6a456bad1c46a3351d999455968a715f52744",
|
||||
"revCount": 5499,
|
||||
"type": "git",
|
||||
"url": "https://git.johnogle.info/johno/beads.git"
|
||||
"lastModified": 1769840331,
|
||||
"narHash": "sha256-Yp0K4JoXX8EcHp1juH4OZ7dcCmkopDu4VvAgZEOxgL8=",
|
||||
"owner": "steveyegge",
|
||||
"repo": "beads",
|
||||
"rev": "93965b4abeed920a4701e03571d1b6bb75810722",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.johnogle.info/johno/beads.git"
|
||||
"owner": "steveyegge",
|
||||
"repo": "beads",
|
||||
"rev": "93965b4abeed920a4701e03571d1b6bb75810722",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"doomemacs": {
|
||||
@@ -47,11 +48,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769329593,
|
||||
"narHash": "sha256-u5PSA+8TUYF/13ziBcnoE67nkDwpjAdecKh3srcJJm0=",
|
||||
"lastModified": 1769848312,
|
||||
"narHash": "sha256-ggBocPd1L4l5MFNV0Fw9aSGZZO4aGzCfgh4e6hQ77RE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "emacs-overlay",
|
||||
"rev": "776dc33d735af583a14cc56b406ea658398964a7",
|
||||
"rev": "be0b4f4f28f69be61e9174807250e3235ee11d50",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -81,17 +82,17 @@
|
||||
"gastown": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1769375540,
|
||||
"narHash": "sha256-8PkI0LGaYmQf9HjErc7kPGmyVGhj7SWH6zgE9F88igQ=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "647a573e7ccb6bb48b780b1637fa9ba394ca4ba2",
|
||||
"revCount": 3065,
|
||||
"type": "git",
|
||||
"url": "https://git.johnogle.info/johno/gastown.git"
|
||||
"lastModified": 1770098007,
|
||||
"narHash": "sha256-CFlN57BXlR5FobTChdE2GgdIGx4xJcFFCk1E5Q98cSQ=",
|
||||
"owner": "steveyegge",
|
||||
"repo": "gastown",
|
||||
"rev": "13461161063bf7b2365fe5fd4df88e32c3ba2a28",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.johnogle.info/johno/gastown.git"
|
||||
"owner": "steveyegge",
|
||||
"repo": "gastown",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"google-cookie-retrieval": {
|
||||
@@ -206,11 +207,11 @@
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769330679,
|
||||
"narHash": "sha256-X7rw5ouiAYKmbbKLtkEc/Kqcg6DxKgOtgaftzuchy/M=",
|
||||
"lastModified": 1769849328,
|
||||
"narHash": "sha256-BjH1Ge6O8ObN6Z97un2U87pl4POO99Q8RSsgIuTZq8Q=",
|
||||
"owner": "marienz",
|
||||
"repo": "nix-doom-emacs-unstraightened",
|
||||
"rev": "0c2d527055f448c8856129c6d063535e06aeff4d",
|
||||
"rev": "fc1d7190c49558cdc6af20d7657075943a500a93",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -257,6 +258,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-qt": {
|
||||
"locked": {
|
||||
"lastModified": 1770464364,
|
||||
"narHash": "sha256-z5NJPSBwsLf/OfD8WTmh79tlSU8XgIbwmk6qB1/TFzY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "23d72dabcb3b12469f57b37170fcbc1789bd7457",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1769170682,
|
||||
@@ -273,6 +290,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"perles": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1769460725,
|
||||
"narHash": "sha256-zM2jw+emxe8+mNyR1ebMWkQiEx8uSmhoqqI0IxXLDgs=",
|
||||
"owner": "zjrosen",
|
||||
"repo": "perles",
|
||||
"rev": "57b20413eea461452b59e13f5a4a367953b1f768",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "zjrosen",
|
||||
"repo": "perles",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"plasma-manager": {
|
||||
"inputs": {
|
||||
"home-manager": [
|
||||
@@ -330,7 +363,9 @@
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nix-doom-emacs-unstraightened": "nix-doom-emacs-unstraightened",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-qt": "nixpkgs-qt",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"perles": "perles",
|
||||
"plasma-manager": "plasma-manager",
|
||||
"plasma-manager-unstable": "plasma-manager-unstable"
|
||||
}
|
||||
|
||||
21
flake.nix
21
flake.nix
@@ -4,6 +4,9 @@
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
|
||||
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
# Separate nixpkgs for qt5webengine-dependent packages (jellyfin-media-player, etc.)
|
||||
# Updates on separate Renovate schedule to avoid massive qt rebuilds
|
||||
nixpkgs-qt.url = "github:nixos/nixpkgs/nixos-25.11";
|
||||
|
||||
nix-darwin = {
|
||||
url = "github:nix-darwin/nix-darwin/nix-darwin-25.11";
|
||||
@@ -43,12 +46,19 @@
|
||||
};
|
||||
|
||||
beads = {
|
||||
url = "git+https://git.johnogle.info/johno/beads.git";
|
||||
# v0.49.1 has dolt server mode support (gt-1mf.3)
|
||||
# Pinned to 259ddd92 - uses Go 1.24 compatible with nixpkgs
|
||||
url = "github:steveyegge/beads/93965b4abeed920a4701e03571d1b6bb75810722";
|
||||
inputs.nixpkgs.follows = "nixpkgs-unstable";
|
||||
};
|
||||
|
||||
gastown = {
|
||||
url = "git+https://git.johnogle.info/johno/gastown.git";
|
||||
url = "github:steveyegge/gastown";
|
||||
flake = false; # No flake.nix upstream yet
|
||||
};
|
||||
|
||||
perles = {
|
||||
url = "github:zjrosen/perles";
|
||||
flake = false; # No flake.nix upstream yet
|
||||
};
|
||||
|
||||
@@ -69,6 +79,11 @@
|
||||
config.allowUnfree = true;
|
||||
overlays = unstableOverlays;
|
||||
};
|
||||
# Separate nixpkgs for qt5webengine-heavy packages to avoid rebuild churn
|
||||
qt-pinned = import inputs.nixpkgs-qt {
|
||||
system = prev.stdenv.hostPlatform.system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
custom = prev.callPackage ./packages {};
|
||||
# Compatibility: bitwarden renamed to bitwarden-desktop in unstable
|
||||
bitwarden-desktop = prev.bitwarden-desktop or prev.bitwarden;
|
||||
@@ -210,7 +225,7 @@
|
||||
};
|
||||
|
||||
# Darwin/macOS configurations
|
||||
darwinConfigurations."blkfv4yf49kt7" = inputs.nix-darwin.lib.darwinSystem rec {
|
||||
darwinConfigurations."BLKFV4YF49KT7" = inputs.nix-darwin.lib.darwinSystem rec {
|
||||
system = "aarch64-darwin";
|
||||
modules = darwinModules ++ [
|
||||
./machines/johno-macbookpro/configuration.nix
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
aerospace = {
|
||||
enable = true;
|
||||
leader = "cmd";
|
||||
ctrlShortcuts.enable = true;
|
||||
ctrlShortcuts.enable = false;
|
||||
sketchybar.enable = true;
|
||||
# Optional: Add per-machine userSettings overrides
|
||||
# userSettings = {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
home.roles = {
|
||||
base.enable = true;
|
||||
plasma-manager-kodi.enable = true;
|
||||
kdeconnect.enable = true;
|
||||
};
|
||||
|
||||
home.packages = with pkgs; [
|
||||
|
||||
@@ -632,7 +632,9 @@ in
|
||||
text = ''
|
||||
#!/bin/bash
|
||||
|
||||
DISK_USAGE=$(df -H / | grep -v Filesystem | awk '{print $5}')
|
||||
# Monitor /System/Volumes/Data which contains user data on APFS
|
||||
# The root / is a read-only snapshot with minimal usage
|
||||
DISK_USAGE=$(df -H /System/Volumes/Data | grep -v Filesystem | awk '{print $5}')
|
||||
|
||||
${pkgs.sketchybar}/bin/sketchybar --set $NAME label="$DISK_USAGE"
|
||||
'';
|
||||
|
||||
@@ -22,6 +22,7 @@ in
|
||||
shellcheck
|
||||
tmux
|
||||
tree
|
||||
watch
|
||||
];
|
||||
|
||||
# Automatic garbage collection for user profile (home-manager generations).
|
||||
|
||||
44
home/roles/development/beads-search-query-optimization.patch
Normal file
44
home/roles/development/beads-search-query-optimization.patch
Normal file
@@ -0,0 +1,44 @@
|
||||
diff --git a/internal/storage/dolt/queries.go b/internal/storage/dolt/queries.go
|
||||
index 7d8214ee..8acdaae2 100644
|
||||
--- a/internal/storage/dolt/queries.go
|
||||
+++ b/internal/storage/dolt/queries.go
|
||||
@@ -212,8 +212,21 @@ func (s *DoltStore) SearchIssues(ctx context.Context, query string, filter types
|
||||
}
|
||||
|
||||
// nolint:gosec // G201: whereSQL contains column comparisons with ?, limitSQL is a safe integer
|
||||
+ // Performance fix: SELECT all columns directly instead of id-only + WHERE IN (all_ids)
|
||||
+ // See: hq-ihwsj - bd list uses inefficient WHERE IN (all_ids) query pattern
|
||||
querySQL := fmt.Sprintf(`
|
||||
- SELECT id FROM issues
|
||||
+ SELECT id, content_hash, title, description, design, acceptance_criteria, notes,
|
||||
+ status, priority, issue_type, assignee, estimated_minutes,
|
||||
+ created_at, created_by, owner, updated_at, closed_at, external_ref,
|
||||
+ compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason,
|
||||
+ deleted_at, deleted_by, delete_reason, original_type,
|
||||
+ sender, ephemeral, pinned, is_template, crystallizes,
|
||||
+ await_type, await_id, timeout_ns, waiters,
|
||||
+ hook_bead, role_bead, agent_state, last_activity, role_type, rig, mol_type,
|
||||
+ event_kind, actor, target, payload,
|
||||
+ due_at, defer_until,
|
||||
+ quality_score, work_type, source_system
|
||||
+ FROM issues
|
||||
%s
|
||||
ORDER BY priority ASC, created_at DESC
|
||||
%s
|
||||
@@ -225,7 +238,15 @@ func (s *DoltStore) SearchIssues(ctx context.Context, query string, filter types
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
- return s.scanIssueIDs(ctx, rows)
|
||||
+ var issues []*types.Issue
|
||||
+ for rows.Next() {
|
||||
+ issue, err := scanIssueRow(rows)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ issues = append(issues, issue)
|
||||
+ }
|
||||
+ return issues, rows.Err()
|
||||
}
|
||||
|
||||
// GetReadyWork returns issues that are ready to work on (not blocked)
|
||||
@@ -5,21 +5,62 @@ with lib;
|
||||
let
|
||||
cfg = config.home.roles.development;
|
||||
|
||||
# FIXME: Temporary override for upstream beads vendorHash mismatch
|
||||
# Remove after upstream fix: https://github.com/steveyegge/beads/issues/XXX
|
||||
beadsPackage = globalInputs.beads.packages.${system}.default.overrideAttrs (old: {
|
||||
vendorHash = "sha256-YU+bRLVlWtHzJ1QPzcKJ70f+ynp8lMoIeFlm+29BNPE=";
|
||||
});
|
||||
# Build beads from flake input with corrected vendorHash
|
||||
# The upstream default.nix has stale vendorHash for commits with server mode
|
||||
beadsRev = builtins.substring 0 8 (globalInputs.beads.rev or "unknown");
|
||||
beadsPackage = pkgs.buildGoModule {
|
||||
pname = "beads";
|
||||
version = "0.49.1-${beadsRev}";
|
||||
src = globalInputs.beads;
|
||||
subPackages = [ "cmd/bd" ];
|
||||
doCheck = false;
|
||||
# Regenerated vendorHash for commit 93965b4a (has dolt server mode, Go 1.24)
|
||||
vendorHash = "sha256-gwxGv8y4+1+k0741CnOYcyJPTJ5vTrynqPoO8YS9fbQ=";
|
||||
nativeBuildInputs = [ pkgs.git ];
|
||||
meta = with lib; {
|
||||
description = "beads (bd) - An issue tracker designed for AI-supervised coding workflows";
|
||||
homepage = "https://github.com/steveyegge/beads";
|
||||
license = licenses.mit;
|
||||
mainProgram = "bd";
|
||||
};
|
||||
};
|
||||
|
||||
# Gastown - multi-agent workspace manager (no upstream flake.nix yet)
|
||||
# Source is tracked via flake input for renovate updates
|
||||
gastownRev = builtins.substring 0 8 (globalInputs.gastown.rev or "unknown");
|
||||
gastownPackage = pkgs.buildGoModule {
|
||||
pname = "gastown";
|
||||
version = "unstable-${builtins.substring 0 8 globalInputs.gastown.rev or "unknown"}";
|
||||
version = "unstable-${gastownRev}";
|
||||
src = globalInputs.gastown;
|
||||
vendorHash = "sha256-ripY9vrYgVW8bngAyMLh0LkU/Xx1UUaLgmAA7/EmWQU=";
|
||||
subPackages = [ "cmd/gt" ];
|
||||
doCheck = false;
|
||||
|
||||
# Must match ldflags from gastown Makefile - BuiltProperly=1 is required
|
||||
# or gt will error with "This binary was built with 'go build' directly"
|
||||
ldflags = [
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.Version=${gastownRev}"
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.Commit=${gastownRev}"
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.BuildTime=nix-build"
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.BuiltProperly=1"
|
||||
];
|
||||
|
||||
# Bug fixes not yet merged upstream
|
||||
# Each patch is stored in a separate file for clarity and maintainability
|
||||
patches = [
|
||||
# Fix validateRecipient bug: normalize addresses before comparison
|
||||
./gastown-fix-validate-recipient.patch
|
||||
# Fix agentBeadToAddress to use title field for hq- prefixed beads
|
||||
./gastown-fix-agent-bead-address-title.patch
|
||||
# Fix crew/polecat home paths: remove incorrect /rig suffix
|
||||
./gastown-fix-role-home-paths.patch
|
||||
# Fix town root detection: don't map to Mayor (causes spurious mismatch warnings)
|
||||
./gastown-fix-town-root-detection.patch
|
||||
# Statusline optimization: skip expensive beads queries for detached sessions
|
||||
# Reduces Dolt CPU from ~70% to ~20% by caching and early-exit
|
||||
./gastown-statusline-optimization.patch
|
||||
];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Gas Town - multi-agent workspace manager by Steve Yegge";
|
||||
homepage = "https://github.com/steveyegge/gastown";
|
||||
@@ -28,6 +69,28 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
# Perles - TUI for beads issue tracking (no upstream flake.nix yet)
|
||||
# Source is tracked via flake input for renovate updates
|
||||
perlesRev = builtins.substring 0 8 (globalInputs.perles.rev or "unknown");
|
||||
perlesPackage = pkgs.buildGoModule {
|
||||
pname = "perles";
|
||||
version = "unstable-${perlesRev}";
|
||||
src = globalInputs.perles;
|
||||
vendorHash = "sha256-JHERJDzbiqgjWXwRhXVjgDEiDQ3AUXRIONotfPF21B0=";
|
||||
doCheck = false;
|
||||
|
||||
ldflags = [
|
||||
"-X main.version=${perlesRev}"
|
||||
];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Perles - Terminal UI for beads issue tracking";
|
||||
homepage = "https://github.com/zjrosen/perles";
|
||||
license = licenses.mit;
|
||||
mainProgram = "perles";
|
||||
};
|
||||
};
|
||||
|
||||
# Fetch the claude-plugins repository (for humanlayer commands/agents)
|
||||
# Update the rev to get newer versions of the commands
|
||||
claudePluginsRepo = builtins.fetchGit {
|
||||
@@ -62,9 +125,11 @@ in
|
||||
home.packages = [
|
||||
beadsPackage
|
||||
gastownPackage
|
||||
perlesPackage
|
||||
pkgs.unstable.claude-code
|
||||
pkgs.unstable.claude-code-router
|
||||
pkgs.unstable.codex
|
||||
pkgs.dolt
|
||||
pkgs.sqlite
|
||||
|
||||
# Custom packages
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --git a/internal/mail/router.go b/internal/mail/router.go
|
||||
--- a/internal/mail/router.go
|
||||
+++ b/internal/mail/router.go
|
||||
@@ -315,7 +315,10 @@ func agentBeadToAddress(bead *agentBead) string {
|
||||
}
|
||||
|
||||
// For other hq- agents, fall back to description parsing
|
||||
- return parseAgentAddressFromDescription(bead.Description)
|
||||
+ if bead.Title != "" && strings.Contains(bead.Title, "/") {
|
||||
+ return bead.Title
|
||||
+ }
|
||||
+ return parseAgentAddressFromDescription(bead.Description)
|
||||
}
|
||||
|
||||
// Handle gt- prefixed IDs (legacy format)
|
||||
@@ -0,0 +1,35 @@
|
||||
diff --git a/internal/mail/router.go b/internal/mail/router.go
|
||||
--- a/internal/mail/router.go
|
||||
+++ b/internal/mail/router.go
|
||||
@@ -330,8 +330,29 @@ func agentBeadToAddress(bead *agentBead) string {
|
||||
}
|
||||
|
||||
// Handle gt- prefixed IDs (legacy format)
|
||||
- if !strings.HasPrefix(id, "gt-") {
|
||||
- return "" // Not a valid agent bead ID
|
||||
+ // Handle rig-specific prefixes: <prefix>-<rig>-<role>-<name>
|
||||
+ // Examples: j-java-crew-americano -> java/crew/americano
|
||||
+ idParts := strings.Split(id, "-")
|
||||
+ if len(idParts) >= 3 {
|
||||
+ for i, part := range idParts {
|
||||
+ if part == "crew" || part == "polecat" || part == "polecats" {
|
||||
+ if i >= 1 && i < len(idParts)-1 {
|
||||
+ rig := idParts[i-1]
|
||||
+ name := strings.Join(idParts[i+1:], "-")
|
||||
+ return rig + "/" + part + "/" + name
|
||||
+ }
|
||||
+ }
|
||||
+ if part == "witness" || part == "refinery" {
|
||||
+ if i >= 1 {
|
||||
+ return idParts[i-1] + "/" + part
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Handle gt- prefixed IDs (legacy format)
|
||||
+ if !strings.HasPrefix(id, "gt-") {
|
||||
+ return "" // Not a valid agent bead ID
|
||||
}
|
||||
|
||||
// Strip prefix
|
||||
25
home/roles/development/gastown-fix-copydir-symlinks.patch
Normal file
25
home/roles/development/gastown-fix-copydir-symlinks.patch
Normal file
@@ -0,0 +1,25 @@
|
||||
diff --git a/internal/git/git.go b/internal/git/git.go
|
||||
--- a/internal/git/git.go
|
||||
+++ b/internal/git/git.go
|
||||
@@ -73,7 +73,19 @@ func copyDir(src, dest string) error {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
destPath := filepath.Join(dest, entry.Name())
|
||||
|
||||
- if entry.IsDir() {
|
||||
+ // Handle symlinks (recreate them, do not follow)
|
||||
+ if entry.Type()&os.ModeSymlink != 0 {
|
||||
+ linkTarget, err := os.Readlink(srcPath)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+ if err := os.Symlink(linkTarget, destPath); err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ if entry.IsDir() {
|
||||
if err := copyDir(srcPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
18
home/roles/development/gastown-fix-role-home-paths.patch
Normal file
18
home/roles/development/gastown-fix-role-home-paths.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff --git a/internal/cmd/role.go b/internal/cmd/role.go
|
||||
--- a/internal/cmd/role.go
|
||||
+++ b/internal/cmd/role.go
|
||||
@@ -326,11 +326,11 @@ func getRoleHome(role Role, rig, polecat, townRoot string) string {
|
||||
if rig == "" || polecat == "" {
|
||||
return ""
|
||||
}
|
||||
- return filepath.Join(townRoot, rig, "polecats", polecat, "rig")
|
||||
+ return filepath.Join(townRoot, rig, "polecats", polecat)
|
||||
case RoleCrew:
|
||||
if rig == "" || polecat == "" {
|
||||
return ""
|
||||
}
|
||||
- return filepath.Join(townRoot, rig, "crew", polecat, "rig")
|
||||
+ return filepath.Join(townRoot, rig, "crew", polecat)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
18
home/roles/development/gastown-fix-town-root-detection.patch
Normal file
18
home/roles/development/gastown-fix-town-root-detection.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff --git a/internal/cmd/prime.go b/internal/cmd/prime.go
|
||||
--- a/internal/cmd/prime.go
|
||||
+++ b/internal/cmd/prime.go
|
||||
@@ -276,11 +276,12 @@ func detectRole(cwd, townRoot string) RoleInfo {
|
||||
|
||||
// Check for mayor role
|
||||
// At town root, or in mayor/ or mayor/rig/
|
||||
if relPath == "." || relPath == "" {
|
||||
- ctx.Role = RoleMayor
|
||||
- return ctx
|
||||
+ return ctx // RoleUnknown - town root is shared space
|
||||
}
|
||||
+
|
||||
+ // Check for mayor role: mayor/ or mayor/rig/
|
||||
if len(parts) >= 1 && parts[0] == "mayor" {
|
||||
ctx.Role = RoleMayor
|
||||
return ctx
|
||||
}
|
||||
13
home/roles/development/gastown-fix-validate-recipient.patch
Normal file
13
home/roles/development/gastown-fix-validate-recipient.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/internal/mail/router.go b/internal/mail/router.go
|
||||
index b864c069..4b6a045b 100644
|
||||
--- a/internal/mail/router.go
|
||||
+++ b/internal/mail/router.go
|
||||
@@ -646,7 +646,7 @@ func (r *Router) validateRecipient(identity string) error {
|
||||
}
|
||||
|
||||
for _, agent := range agents {
|
||||
- if agentBeadToAddress(agent) == identity {
|
||||
+ if AddressToIdentity(agentBeadToAddress(agent)) == AddressToIdentity(identity) {
|
||||
return nil // Found matching agent
|
||||
}
|
||||
}
|
||||
135
home/roles/development/gastown-statusline-optimization.patch
Normal file
135
home/roles/development/gastown-statusline-optimization.patch
Normal file
@@ -0,0 +1,135 @@
|
||||
diff --git a/internal/cmd/statusline.go b/internal/cmd/statusline.go
|
||||
index 2edf1be8..00253eea 100644
|
||||
--- a/internal/cmd/statusline.go
|
||||
+++ b/internal/cmd/statusline.go
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
+ "time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
@@ -14,6 +15,37 @@ import (
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
+// statusLineCacheTTL is how long cached status output remains valid.
|
||||
+const statusLineCacheTTL = 10 * time.Second
|
||||
+
|
||||
+// statusLineCachePath returns the cache file path for a session.
|
||||
+func statusLineCachePath(session string) string {
|
||||
+ return filepath.Join(os.TempDir(), fmt.Sprintf("gt-status-%s", session))
|
||||
+}
|
||||
+
|
||||
+// getStatusLineCache returns cached status if fresh, empty string otherwise.
|
||||
+func getStatusLineCache(session string) string {
|
||||
+ path := statusLineCachePath(session)
|
||||
+ info, err := os.Stat(path)
|
||||
+ if err != nil {
|
||||
+ return ""
|
||||
+ }
|
||||
+ if time.Since(info.ModTime()) > statusLineCacheTTL {
|
||||
+ return ""
|
||||
+ }
|
||||
+ data, err := os.ReadFile(path)
|
||||
+ if err != nil {
|
||||
+ return ""
|
||||
+ }
|
||||
+ return string(data)
|
||||
+}
|
||||
+
|
||||
+// setStatusLineCache writes status to cache file.
|
||||
+func setStatusLineCache(session, status string) {
|
||||
+ path := statusLineCachePath(session)
|
||||
+ _ = os.WriteFile(path, []byte(status), 0644)
|
||||
+}
|
||||
+
|
||||
|
||||
var (
|
||||
statusLineSession string
|
||||
@@ -34,6 +66,19 @@ func init() {
|
||||
func runStatusLine(cmd *cobra.Command, args []string) error {
|
||||
t := tmux.NewTmux()
|
||||
|
||||
+ // Optimization: skip expensive beads queries for detached sessions
|
||||
+ if statusLineSession != "" {
|
||||
+ if !t.IsSessionAttached(statusLineSession) {
|
||||
+ fmt.Print("○ |")
|
||||
+ return nil
|
||||
+ }
|
||||
+ // Check cache for attached sessions too
|
||||
+ if cached := getStatusLineCache(statusLineSession); cached != "" {
|
||||
+ fmt.Print(cached)
|
||||
+ return nil
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// Get session environment
|
||||
var rigName, polecat, crew, issue, role string
|
||||
|
||||
@@ -150,7 +195,11 @@ func runWorkerStatusLine(t *tmux.Tmux, session, rigName, polecat, crew, issue st
|
||||
|
||||
// Output
|
||||
if len(parts) > 0 {
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -389,7 +438,11 @@ func runMayorStatusLine(t *tmux.Tmux) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -458,7 +511,11 @@ func runDeaconStatusLine(t *tmux.Tmux) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -526,7 +583,11 @@ func runWitnessStatusLine(t *tmux.Tmux, rigName string) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -617,7 +678,11 @@ func runRefineryStatusLine(t *tmux.Tmux, rigName string) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -210,18 +210,25 @@
|
||||
:around #'my/org-caldav-add-until-property)
|
||||
)
|
||||
|
||||
(defun my/get-rbw-password (alias)
|
||||
(defun my/get-rbw-password (alias &optional no-error)
|
||||
"Return the password for ALIAS via rbw, unlocking the vault only if needed.
|
||||
Returns nil and signals an error if the entry is not found."
|
||||
(let* ((cmd (format "rbw get %s 2>/dev/null" (shell-quote-argument alias)))
|
||||
(output (string-trim (shell-command-to-string cmd))))
|
||||
(if (string-empty-p output)
|
||||
(user-error "rbw: no entry found for '%s' - run: rbw add %s" alias alias)
|
||||
output)))
|
||||
If NO-ERROR is non-nil, return nil instead of signaling an error when
|
||||
rbw is unavailable or the entry is not found."
|
||||
(if (not (executable-find "rbw"))
|
||||
(if no-error
|
||||
nil
|
||||
(user-error "rbw: not installed or not in PATH"))
|
||||
(let* ((cmd (format "rbw get %s 2>/dev/null" (shell-quote-argument alias)))
|
||||
(output (string-trim (shell-command-to-string cmd))))
|
||||
(if (string-empty-p output)
|
||||
(if no-error
|
||||
nil
|
||||
(user-error "rbw: no entry found for '%s' - run: rbw add %s" alias alias))
|
||||
output))))
|
||||
|
||||
(after! gptel
|
||||
:config
|
||||
(setq! gptel-api-key (my/get-rbw-password "openai-api-key-chatgpt-el")
|
||||
(setq! gptel-api-key (my/get-rbw-password "openai-api-key-chatgpt-el" t)
|
||||
gptel-default-mode 'org-mode
|
||||
gptel-use-tools t
|
||||
gptel-confirm-tool-calls 'always
|
||||
|
||||
@@ -90,6 +90,8 @@ with lib;
|
||||
htop
|
||||
tmux
|
||||
zfs
|
||||
rclone
|
||||
custom.rclone-torbox-setup # Helper script to set up TorBox credentials via rbw
|
||||
];
|
||||
|
||||
# Enable SSH
|
||||
@@ -126,6 +128,26 @@ with lib;
|
||||
|
||||
roles.virtualisation.enable = true;
|
||||
|
||||
# TorBox WebDAV mount for rdt-client and Jellyfin
|
||||
roles.rclone-mount = {
|
||||
enable = true;
|
||||
mounts.torbox = {
|
||||
webdavUrl = "https://webdav.torbox.app";
|
||||
username = "john@ogle.fyi"; # TorBox account email
|
||||
mountPoint = "/media/media/torbox-rclone";
|
||||
environmentFile = "/etc/rclone/torbox.env";
|
||||
vfsCacheMode = "full"; # Best for streaming media
|
||||
dirCacheTime = "5m";
|
||||
extraArgs = [
|
||||
"--buffer-size=64M"
|
||||
"--vfs-read-chunk-size=32M"
|
||||
"--vfs-read-chunk-size-limit=off"
|
||||
];
|
||||
# Wait for ZFS media pool to be mounted before starting
|
||||
requiresMountsFor = [ "/media" ];
|
||||
};
|
||||
};
|
||||
|
||||
# Time zone
|
||||
time.timeZone = "America/Los_Angeles"; # Adjust as needed
|
||||
|
||||
|
||||
@@ -6,24 +6,24 @@
|
||||
}:
|
||||
|
||||
let
|
||||
version = "2.1.12";
|
||||
version = "2.1.30";
|
||||
|
||||
srcs = {
|
||||
aarch64-darwin = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-arm64/claude";
|
||||
sha256 = "40be59519a84bd35eb1111aa46f72aa6b3443866d3f6336252a198fdcaefbbe5";
|
||||
sha256 = "3ccc14f322b1e8da0cd58afc254fd5100eee066fa14729f30745e67a3f7979f7";
|
||||
};
|
||||
x86_64-darwin = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/darwin-x64/claude";
|
||||
sha256 = "0eee4b46c91749480bf856f88e49b15a3e944faa9d346679c5f0c0d7fa6f2f54";
|
||||
sha256 = "8a083696006483b8382ec0e47cd8f2e3223f3d2cab1a21c524fa08c082b5600e";
|
||||
};
|
||||
x86_64-linux = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-x64/claude";
|
||||
sha256 = "3fe979215489dc1b31463fadf95ed2d2d5473a9969447bb7a46431f4578847d4";
|
||||
sha256 = "ada8f1cf9272965d38b10f1adb6cea885e621c83f7e7bb233008c721f43fad54";
|
||||
};
|
||||
aarch64-linux = {
|
||||
url = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${version}/linux-arm64/claude";
|
||||
sha256 = "e214b1d3b5afd4cd2de9177359001d41a3eb98cb1e3665fe97edc592f5aa132f";
|
||||
sha256 = "45fbf35a1011b06f86170b20beb64c599db0658aac70e2de2410c45d15775596";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
|
||||
claude-code = pkgs.callPackage ./claude-code {};
|
||||
mcrcon-rbw = pkgs.callPackage ./mcrcon-rbw {};
|
||||
rclone-torbox-setup = pkgs.callPackage ./rclone-torbox-setup {};
|
||||
}
|
||||
|
||||
98
packages/rclone-torbox-setup/default.nix
Normal file
98
packages/rclone-torbox-setup/default.nix
Normal file
@@ -0,0 +1,98 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
pkgs.writeShellScriptBin "rclone-torbox-setup" ''
|
||||
set -euo pipefail
|
||||
|
||||
# Default values
|
||||
RBW_ENTRY="''${1:-torbox}"
|
||||
ENV_FILE="''${2:-/etc/rclone/torbox.env}"
|
||||
|
||||
usage() {
|
||||
echo "Usage: rclone-torbox-setup [rbw-entry] [env-file]"
|
||||
echo ""
|
||||
echo "Sets up rclone credentials for TorBox WebDAV mount."
|
||||
echo "Retrieves password from rbw (Bitwarden), obscures it for rclone,"
|
||||
echo "and writes it to the environment file for the systemd service."
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " rbw-entry Name of the Bitwarden entry containing the password (default: torbox)"
|
||||
echo " env-file Path to write the environment file (default: /etc/rclone/torbox.env)"
|
||||
echo ""
|
||||
echo "The Bitwarden entry should contain your TorBox password as the password field."
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " rclone-torbox-setup torbox-password /etc/rclone/torbox.env"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ "''${1:-}" == "-h" ]] || [[ "''${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
echo "rclone TorBox credential setup"
|
||||
echo "=============================="
|
||||
echo ""
|
||||
|
||||
# Check if rbw is available
|
||||
if ! command -v rbw &> /dev/null; then
|
||||
echo "Error: rbw is not available. Please ensure rbw is installed and configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if rclone is available
|
||||
if ! command -v rclone &> /dev/null; then
|
||||
echo "Error: rclone is not available. Please ensure rclone is installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Retrieving password from rbw entry: $RBW_ENTRY"
|
||||
|
||||
# Retrieve password from Bitwarden
|
||||
if ! TORBOX_PASS=$(rbw get "$RBW_ENTRY" 2>/dev/null); then
|
||||
echo ""
|
||||
echo "Error: Failed to retrieve password from rbw entry '$RBW_ENTRY'"
|
||||
echo ""
|
||||
echo "Please ensure:"
|
||||
echo " 1. The entry '$RBW_ENTRY' exists in Bitwarden"
|
||||
echo " 2. rbw is unlocked: rbw unlock"
|
||||
echo " 3. rbw is synced: rbw sync"
|
||||
echo ""
|
||||
echo "To create the entry in Bitwarden:"
|
||||
echo " - Name: $RBW_ENTRY"
|
||||
echo " - Password: Your TorBox password"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Password retrieved successfully"
|
||||
|
||||
# Obscure the password for rclone
|
||||
echo "Obscuring password for rclone..."
|
||||
if ! OBSCURED_PASS=$(echo -n "$TORBOX_PASS" | rclone obscure -); then
|
||||
echo "Error: Failed to obscure password with rclone"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create the directory if needed (requires sudo)
|
||||
ENV_DIR=$(dirname "$ENV_FILE")
|
||||
if [[ ! -d "$ENV_DIR" ]]; then
|
||||
echo "Creating directory $ENV_DIR (requires sudo)..."
|
||||
sudo mkdir -p "$ENV_DIR"
|
||||
fi
|
||||
|
||||
# Write the environment file
|
||||
echo "Writing environment file to $ENV_FILE (requires sudo)..."
|
||||
echo "RCLONE_WEBDAV_PASS=$OBSCURED_PASS" | sudo tee "$ENV_FILE" > /dev/null
|
||||
sudo chmod 600 "$ENV_FILE"
|
||||
|
||||
echo ""
|
||||
echo "Setup complete!"
|
||||
echo ""
|
||||
echo "The environment file has been created at: $ENV_FILE"
|
||||
echo "The rclone-mount-torbox systemd service will use this file."
|
||||
echo ""
|
||||
echo "To activate the mount after NixOS rebuild:"
|
||||
echo " sudo systemctl start rclone-mount-torbox"
|
||||
echo ""
|
||||
echo "To check status:"
|
||||
echo " sudo systemctl status rclone-mount-torbox"
|
||||
''
|
||||
@@ -13,7 +13,7 @@
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": [
|
||||
"before 5am on monday"
|
||||
"after 2pm and before 4pm on Saturday"
|
||||
]
|
||||
},
|
||||
"dependencyDashboard": true,
|
||||
@@ -37,6 +37,9 @@
|
||||
"/^nixpkgs$/",
|
||||
"/^home-manager$/",
|
||||
"/^nix-darwin$/"
|
||||
],
|
||||
"schedule": [
|
||||
"after 2pm and before 4pm on Saturday"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -48,6 +51,21 @@
|
||||
"matchPackageNames": [
|
||||
"/nixpkgs-unstable/",
|
||||
"/home-manager-unstable/"
|
||||
],
|
||||
"schedule": [
|
||||
"after 2pm and before 4pm on Saturday"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nixpkgs-qt updates on Saturday (staggered from main ecosystem)",
|
||||
"matchManagers": [
|
||||
"nix"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"/nixpkgs-qt/"
|
||||
],
|
||||
"schedule": [
|
||||
"after 4pm and before 6pm on Saturday"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ with lib;
|
||||
./nfs-mounts
|
||||
./nvidia
|
||||
./printing
|
||||
./rclone-mount
|
||||
./remote-build
|
||||
./spotifyd
|
||||
./users
|
||||
|
||||
@@ -47,23 +47,23 @@ in
|
||||
if cfg.jellyfinScaleFactor != null
|
||||
then pkgs.symlinkJoin {
|
||||
name = "jellyfin-media-player-scaled";
|
||||
paths = [ pkgs.jellyfin-media-player ];
|
||||
paths = [ pkgs.qt-pinned.jellyfin-media-player ];
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
postBuild = ''
|
||||
mkdir -p $out/bin
|
||||
rm -f $out/bin/jellyfin-desktop
|
||||
makeWrapper ${pkgs.jellyfin-media-player}/bin/jellyfin-desktop $out/bin/jellyfin-desktop \
|
||||
makeWrapper ${pkgs.qt-pinned.jellyfin-media-player}/bin/jellyfin-desktop $out/bin/jellyfin-desktop \
|
||||
--add-flags "--tv --scale-factor ${toString cfg.jellyfinScaleFactor}"
|
||||
|
||||
# Update .desktop file to include scale factor and TV mode arguments
|
||||
mkdir -p $out/share/applications
|
||||
rm -f $out/share/applications/org.jellyfin.JellyfinDesktop.desktop
|
||||
substitute ${pkgs.jellyfin-media-player}/share/applications/org.jellyfin.JellyfinDesktop.desktop \
|
||||
substitute ${pkgs.qt-pinned.jellyfin-media-player}/share/applications/org.jellyfin.JellyfinDesktop.desktop \
|
||||
$out/share/applications/org.jellyfin.JellyfinDesktop.desktop \
|
||||
--replace-fail "Exec=jellyfin-desktop" "Exec=jellyfin-desktop --tv --scale-factor ${toString cfg.jellyfinScaleFactor}"
|
||||
'';
|
||||
}
|
||||
else pkgs.jellyfin-media-player;
|
||||
else pkgs.qt-pinned.jellyfin-media-player;
|
||||
in mkIf cfg.enable
|
||||
{
|
||||
users.extraUsers.kodi = {
|
||||
|
||||
149
roles/rclone-mount/default.nix
Normal file
149
roles/rclone-mount/default.nix
Normal file
@@ -0,0 +1,149 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.roles.rclone-mount;
|
||||
|
||||
# Generate systemd service for a single mount
|
||||
mkMountService = name: mountCfg: {
|
||||
description = "rclone mount for ${name}";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# Wait for parent mount points (e.g., ZFS pools) to be available
|
||||
unitConfig = mkIf (mountCfg.requiresMountsFor != []) {
|
||||
RequiresMountsFor = mountCfg.requiresMountsFor;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${mountCfg.mountPoint}";
|
||||
ExecStart = concatStringsSep " " ([
|
||||
"${pkgs.rclone}/bin/rclone mount"
|
||||
":webdav:${mountCfg.remotePath}"
|
||||
"${mountCfg.mountPoint}"
|
||||
"--webdav-url=${mountCfg.webdavUrl}"
|
||||
"--webdav-vendor=${mountCfg.webdavVendor}"
|
||||
"--webdav-user=${mountCfg.username}"
|
||||
"--allow-other"
|
||||
"--vfs-cache-mode=${mountCfg.vfsCacheMode}"
|
||||
"--dir-cache-time=${mountCfg.dirCacheTime}"
|
||||
"--poll-interval=${mountCfg.pollInterval}"
|
||||
"--log-level=${mountCfg.logLevel}"
|
||||
] ++ mountCfg.extraArgs);
|
||||
ExecStop = "${pkgs.fuse}/bin/fusermount -uz ${mountCfg.mountPoint}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
EnvironmentFile = mountCfg.environmentFile;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.roles.rclone-mount = {
|
||||
enable = mkEnableOption "Enable rclone WebDAV mounts";
|
||||
|
||||
mounts = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
webdavUrl = mkOption {
|
||||
type = types.str;
|
||||
description = "WebDAV server URL (e.g., https://webdav.torbox.app)";
|
||||
};
|
||||
|
||||
webdavVendor = mkOption {
|
||||
type = types.enum [ "other" "nextcloud" "owncloud" "sharepoint" "sharepoint-ntlm" "fastmail" ];
|
||||
default = "other";
|
||||
description = "WebDAV server vendor for optimizations";
|
||||
};
|
||||
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
description = "WebDAV username (often email address)";
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to environment file containing RCLONE_WEBDAV_PASS.
|
||||
The password should be obscured using: rclone obscure <password>
|
||||
File format: RCLONE_WEBDAV_PASS=<obscured_password>
|
||||
'';
|
||||
};
|
||||
|
||||
mountPoint = mkOption {
|
||||
type = types.str;
|
||||
description = "Local mount point path";
|
||||
};
|
||||
|
||||
remotePath = mkOption {
|
||||
type = types.str;
|
||||
default = "/";
|
||||
description = "Remote path on WebDAV server to mount";
|
||||
};
|
||||
|
||||
vfsCacheMode = mkOption {
|
||||
type = types.enum [ "off" "minimal" "writes" "full" ];
|
||||
default = "full";
|
||||
description = ''
|
||||
VFS cache mode. For streaming media, 'full' is recommended.
|
||||
- off: No caching (direct reads/writes)
|
||||
- minimal: Cache open files only
|
||||
- writes: Cache writes and open files
|
||||
- full: Full caching of all files
|
||||
'';
|
||||
};
|
||||
|
||||
dirCacheTime = mkOption {
|
||||
type = types.str;
|
||||
default = "5m";
|
||||
description = "Time to cache directory entries";
|
||||
};
|
||||
|
||||
pollInterval = mkOption {
|
||||
type = types.str;
|
||||
default = "1m";
|
||||
description = "Poll interval for remote changes";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum [ "DEBUG" "INFO" "NOTICE" "ERROR" ];
|
||||
default = "INFO";
|
||||
description = "rclone log level";
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Extra arguments to pass to rclone mount";
|
||||
};
|
||||
|
||||
requiresMountsFor = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of mount points that must be available before this service starts.
|
||||
Use this when the mount point's parent is on a ZFS pool or other filesystem
|
||||
that may not be mounted at boot time.
|
||||
Example: [ "/media" ] to wait for the media ZFS pool to mount.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
description = "Attribute set of rclone WebDAV mounts to configure";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Ensure FUSE is available
|
||||
environment.systemPackages = [ pkgs.rclone pkgs.fuse ];
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
# Create systemd services for each mount
|
||||
systemd.services = mapAttrs' (name: mountCfg:
|
||||
nameValuePair "rclone-mount-${name}" (mkMountService name mountCfg)
|
||||
) cfg.mounts;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user