feat: add yolo host

New NixOS VM on proxmox, replacing the Ubuntu box. Reuses the existing
Hyprland config; remote access is Sunshine/Moonlight rather than xrdp,
since xrdp cannot drive a wayland compositor.

Two fixes here are not yolo-specific and affect any fresh install:
git at system level (nix needs it for the type = "git" hyprland input,
but git only came from home-manager, so neither rebuild could run), and
dropping the yogurt input, whose private repo made home-manager
un-evaluatable without GitHub auth.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
naps62
2026-08-17 06:30:54 +00:00
parent f7947375e3
commit 77bdbe108d
12 changed files with 838 additions and 47 deletions
Generated
+232 -43
View File
@@ -1,5 +1,26 @@
{ {
"nodes": { "nodes": {
"agent-of-empires": {
"inputs": {
"crane": "crane",
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1786554876,
"narHash": "sha256-akMknOIdX0QsBsM4lgreXSNNym7TNu9iOrDBqrQuTuQ=",
"owner": "agent-of-empires",
"repo": "agent-of-empires",
"rev": "9b0d691a6cdaa6d2451f6eda05e83628c54eb95a",
"type": "github"
},
"original": {
"owner": "agent-of-empires",
"ref": "v1.14.1",
"repo": "agent-of-empires",
"type": "github"
}
},
"aquamarine": { "aquamarine": {
"inputs": { "inputs": {
"hyprutils": [ "hyprutils": [
@@ -36,7 +57,7 @@
"claude-code": { "claude-code": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1785285785, "lastModified": 1785285785,
@@ -82,11 +103,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1785336468, "lastModified": 1786822283,
"narHash": "sha256-zVeyr06Ra4+NUhMzkaxLmYGdgKhw9DuMR89jpzws82k=", "narHash": "sha256-RdoFKwNQ2m2zbHnq/QbgLf2lUI9cohsnlMwLfJuaTh0=",
"owner": "ilysenko", "owner": "ilysenko",
"repo": "codex-desktop-linux", "repo": "codex-desktop-linux",
"rev": "dc479a4caca73b2a29fe2f2945e72bdedde5a12c", "rev": "e6b51d96ac2b9b5d9adafa852241bd0982972557",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -95,9 +116,24 @@
"type": "github" "type": "github"
} }
}, },
"crane": {
"locked": {
"lastModified": 1771796463,
"narHash": "sha256-9bCDuUzpwJXcHMQYMS1yNuzYMmKO/CCwCexpjWOl62I=",
"owner": "ipetkov",
"repo": "crane",
"rev": "3d3de3313e263e04894f284ac18177bd26169bad",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"ethui": { "ethui": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_3",
"rust": "rust", "rust": "rust",
"utils": "utils" "utils": "utils"
}, },
@@ -132,6 +168,42 @@
"type": "github" "type": "github"
} }
}, },
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1769996383,
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
@@ -201,6 +273,24 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_5": {
"inputs": {
"systems": "systems_8"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"foundry": { "foundry": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_4", "flake-utils": "flake-utils_4",
@@ -353,7 +443,7 @@
"hyprutils": "hyprutils", "hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner", "hyprwayland-scanner": "hyprwayland-scanner",
"hyprwire": "hyprwire", "hyprwire": "hyprwire",
"nixpkgs": "nixpkgs_4", "nixpkgs": "nixpkgs_5",
"pre-commit-hooks": "pre-commit-hooks", "pre-commit-hooks": "pre-commit-hooks",
"systems": "systems_5", "systems": "systems_5",
"xdph": "xdph" "xdph": "xdph"
@@ -648,6 +738,52 @@
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": {
"lastModified": 1781074563,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": {
"lastModified": 1774748309,
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "333c4e0545a6da976206c74db8773a1645b5870a",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1784783405, "lastModified": 1784783405,
"narHash": "sha256-4IHyyLgLBdKefkljdKod4IMn023pQiDXAWJA187cmdY=", "narHash": "sha256-4IHyyLgLBdKefkljdKod4IMn023pQiDXAWJA187cmdY=",
@@ -663,7 +799,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1764517877, "lastModified": 1764517877,
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=",
@@ -679,7 +815,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_3": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1744536153, "lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
@@ -695,7 +831,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_4": { "nixpkgs_5": {
"locked": { "locked": {
"lastModified": 1784356753, "lastModified": 1784356753,
"narHash": "sha256-12KrbMiWLcf8m7pCvAtZh1ZrgF85ZXDXvfR/fWTKy84=", "narHash": "sha256-12KrbMiWLcf8m7pCvAtZh1ZrgF85ZXDXvfR/fWTKy84=",
@@ -711,7 +847,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_5": { "nixpkgs_6": {
"locked": { "locked": {
"lastModified": 1785090369, "lastModified": 1785090369,
"narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=", "narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=",
@@ -727,18 +863,34 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_6": { "nixpkgs_7": {
"locked": { "locked": {
"lastModified": 1751274312, "lastModified": 1775036866,
"narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.11", "ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_8": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@@ -826,6 +978,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"agent-of-empires": "agent-of-empires",
"claude-code": "claude-code", "claude-code": "claude-code",
"codex-cli": "codex-cli", "codex-cli": "codex-cli",
"codex-desktop-linux": "codex-desktop-linux", "codex-desktop-linux": "codex-desktop-linux",
@@ -835,14 +988,14 @@
"home-manager": "home-manager", "home-manager": "home-manager",
"hyprland": "hyprland", "hyprland": "hyprland",
"nix-index-database": "nix-index-database", "nix-index-database": "nix-index-database",
"nixpkgs": "nixpkgs_5", "nixpkgs": "nixpkgs_6",
"noctalia": "noctalia", "noctalia": "noctalia",
"nvchad-starter": "nvchad-starter", "nvchad-starter": "nvchad-starter",
"nvchad4nix": "nvchad4nix", "nvchad4nix": "nvchad4nix",
"rose-pine-hyprcursor": "rose-pine-hyprcursor", "rose-pine-hyprcursor": "rose-pine-hyprcursor",
"sem": "sem",
"spicetify-nix": "spicetify-nix", "spicetify-nix": "spicetify-nix",
"systems": "systems_9", "systems": "systems_10",
"yogurt": "yogurt",
"zen-browser": "zen-browser" "zen-browser": "zen-browser"
} }
}, },
@@ -870,7 +1023,7 @@
}, },
"rust": { "rust": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_4"
}, },
"locked": { "locked": {
"lastModified": 1764729618, "lastModified": 1764729618,
@@ -886,12 +1039,51 @@
"type": "github" "type": "github"
} }
}, },
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_8"
},
"locked": {
"lastModified": 1775445266,
"narHash": "sha256-3fgIj85WHQbOamrpIw9WY3ZL1PoEvjPOjmzMYNsEQJo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "61747bc3cf2da179b9af356ae9e70f3b895b0c24",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"sem": {
"inputs": {
"flake-parts": "flake-parts_2",
"flake-utils": "flake-utils_5",
"nixpkgs": "nixpkgs_7",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1786903203,
"narHash": "sha256-MaISe6TG4I5zb6hSRsBj5ENe5JSh7MjQ+QIDWDhxTo0=",
"owner": "Ataraxy-Labs",
"repo": "sem",
"rev": "ba2dfbbe75836cfe6f69dc036c9060b7ce5cc158",
"type": "github"
},
"original": {
"owner": "Ataraxy-Labs",
"repo": "sem",
"type": "github"
}
},
"spicetify-nix": { "spicetify-nix": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"systems": "systems_8" "systems": "systems_9"
}, },
"locked": { "locked": {
"lastModified": 1785048222, "lastModified": 1785048222,
@@ -922,6 +1114,21 @@
"type": "github" "type": "github"
} }
}, },
"systems_10": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
},
"systems_2": { "systems_2": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
@@ -1029,16 +1236,16 @@
}, },
"systems_9": { "systems_9": {
"locked": { "locked": {
"lastModified": 1689347949, "lastModified": 1681028828,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems", "owner": "nix-systems",
"repo": "default-linux", "repo": "default",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-systems", "owner": "nix-systems",
"repo": "default-linux", "repo": "default",
"type": "github" "type": "github"
} }
}, },
@@ -1119,24 +1326,6 @@
"type": "github" "type": "github"
} }
}, },
"yogurt": {
"inputs": {
"nixpkgs": "nixpkgs_6"
},
"locked": {
"lastModified": 1783264766,
"narHash": "sha256-1iD8gdvxBYBfffHhNsnZPnPGDDttYVRUhghLbAa1Mho=",
"ref": "refs/heads/main",
"rev": "515ab16c76fc0779a1c6be93cfa1424a901373cb",
"revCount": 67,
"type": "git",
"url": "https://github.com/ZePedroResende/yogurt"
},
"original": {
"type": "git",
"url": "https://github.com/ZePedroResende/yogurt"
}
},
"zen-browser": { "zen-browser": {
"inputs": { "inputs": {
"home-manager": "home-manager_2", "home-manager": "home-manager_2",
+8 -1
View File
@@ -66,7 +66,12 @@
url = "github:ilysenko/codex-desktop-linux"; url = "github:ilysenko/codex-desktop-linux";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
yogurt.url = "git+https://github.com/ZePedroResende/yogurt"; # Pinned to the tag, and pins its own nixpkgs for the same reason ethui does
# — it is a verified Rust build. Bump deliberately, not via `flake update`.
agent-of-empires.url = "github:agent-of-empires/agent-of-empires/v1.14.1";
# Semantic-diff tool rev calls via REV_SEM_BIN. NOT nixpkgs' `sem`, which is
# the unrelated Semaphore CI cli.
sem.url = "github:Ataraxy-Labs/sem";
nix-index-database = { nix-index-database = {
url = "github:nix-community/nix-index-database"; url = "github:nix-community/nix-index-database";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -124,6 +129,7 @@
nixosConfigurations = { nixosConfigurations = {
arrakis = mkNixOS "arrakis"; arrakis = mkNixOS "arrakis";
konishi = mkNixOS "konishi"; konishi = mkNixOS "konishi";
yolo = mkNixOS "yolo";
}; };
# Named "<user>@<host>" so `nh home switch` auto-detects them # Named "<user>@<host>" so `nh home switch` auto-detects them
@@ -131,6 +137,7 @@
homeConfigurations = { homeConfigurations = {
"naps62@arrakis" = mkHome "arrakis" x86; "naps62@arrakis" = mkHome "arrakis" x86;
"naps62@konishi" = mkHome "konishi" x86; "naps62@konishi" = mkHome "konishi" x86;
"naps62@yolo" = mkHome "yolo" x86;
}; };
devShells = devShells =
-2
View File
@@ -2,7 +2,6 @@
lib, lib,
pkgs, pkgs,
config, config,
inputs,
... ...
}: }:
{ {
@@ -34,7 +33,6 @@
packages = with pkgs; [ packages = with pkgs; [
impala impala
inputs.yogurt.packages.${pkgs.system}.default
]; ];
username = lib.mkDefault "naps62"; username = lib.mkDefault "naps62";
@@ -154,7 +154,6 @@ in
hl.exec_cmd("hyprctl setcursor Nordzy-cursors ${toString cfg.cursorSize}") hl.exec_cmd("hyprctl setcursor Nordzy-cursors ${toString cfg.cursorSize}")
hl.exec_cmd("hyprsunset") hl.exec_cmd("hyprsunset")
hl.exec_cmd("noctalia") hl.exec_cmd("noctalia")
hl.exec_cmd("yogurt --tray")
end) end)
-- layer rules -- layer rules
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env bash
# Manage the live list of Claude Code Remote Control projects.
# The projects file is the source of truth; sync reconciles systemd to match.
set -euo pipefail
LIST="${CLAUDE_RC_LIST:-$HOME/.config/claude-rc/projects}"
UNIT=claude-rc
die() { echo "claude-rc: $*" >&2; exit 1; }
# configured paths: strip comments, an optional "| Display Name" suffix,
# surrounding space, and any trailing slash.
wanted_paths() {
[ -f "$LIST" ] || return 0
sed -e 's/#.*//' -e 's/|.*//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' "$LIST" \
| grep -v '^$' \
| sed -e 's#/\+$##' \
| sort -u
}
# display name for a path, if the list defines one
name_for() {
[ -f "$LIST" ] || return 0
awk -F'|' -v t="$1" '
{ line=$0; sub(/#.*/,"",line)
p=line; if (index(line,"|")) { p=substr(line,1,index(line,"|")-1) }
gsub(/^[[:space:]]+|[[:space:]]+$/,"",p); sub(/\/+$/,"",p)
if (p == t && index(line,"|")) {
n=substr(line,index(line,"|")+1)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",n)
if (n != "") { print n; exit }
}
}' "$LIST" 2>/dev/null
}
# instance names systemd should have enabled
wanted_units() {
wanted_paths | while read -r p; do
printf '%s@%s.service\n' "$UNIT" "$(systemd-escape -p "$p")"
done | sort -u
}
# instance names systemd currently has
current_units() {
systemctl --user list-units --all --no-legend --plain "${UNIT}@*.service" 2>/dev/null \
| awk '{print $1}' | sort -u
}
cmd_sync() {
local want have add rm
want=$(wanted_units); have=$(current_units)
add=$(comm -23 <(printf '%s\n' "$want") <(printf '%s\n' "$have") | grep -v '^$' || true)
rm=$(comm -13 <(printf '%s\n' "$want") <(printf '%s\n' "$have") | grep -v '^$' || true)
if [ -n "$rm" ]; then
while read -r u; do
echo "- stop $u"
systemctl --user disable --now "$u" >/dev/null 2>&1 || true
done <<<"$rm"
fi
if [ -n "$add" ]; then
while read -r u; do
echo "+ start $u"
systemctl --user enable --now "$u" >/dev/null 2>&1 \
|| echo " FAILED: $u (see: claude-rc log $u)" >&2
done <<<"$add"
fi
[ -z "$add$rm" ] && echo "in sync ($(wanted_paths | grep -c . || true) projects)"
return 0
}
cmd_list() {
local n=0
printf '%-46s %-9s %s\n' PROJECT STATE GIT
while read -r p; do
n=$((n+1))
local u state git
u="${UNIT}@$(systemd-escape -p "$p").service"
state=$(systemctl --user is-active "$u" 2>/dev/null || true)
if [ ! -d "$p" ]; then git="MISSING DIR"
elif git -C "$p" rev-parse --git-dir >/dev/null 2>&1; then git="worktree"
else git="same-dir (not git)"; fi
printf '%-46s %-9s %s\n' "${p/#$HOME/\~}" "$state" "$git"
done < <(wanted_paths)
[ "$n" = 0 ] && echo "(no projects configured; claude-rc add <path>)"
return 0
}
cmd_add() {
local p="${1:-$PWD}"
p=$(cd "$p" 2>/dev/null && pwd) || die "no such directory: ${1:-$PWD}"
if wanted_paths | grep -qxF "$p"; then echo "already listed: $p"; return 0; fi
# revive a commented-out entry if present, else append; keeps the file tidy
# across add/rm cycles instead of accumulating dead duplicates.
if awk -v t="$p" '
{ l=$0; sub(/^[[:space:]]*#[[:space:]]*/,"",l); sub(/\/+$/,"",l)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
if ($0 ~ /^[[:space:]]*#/ && l == t) found=1 }
END { exit !found }' "$LIST"; then
awk -v t="$p" '
{ l=$0; sub(/^[[:space:]]*#[[:space:]]*/,"",l); sub(/\/+$/,"",l)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
if (!done && $0 ~ /^[[:space:]]*#/ && l == t) { print t; done=1 }
else print $0 }
' "$LIST" > "$LIST.tmp" && mv "$LIST.tmp" "$LIST"
else
printf '%s\n' "$p" >> "$LIST"
fi
echo "added: $p"
cmd_sync
}
cmd_rm() {
local p="${1:?usage: claude-rc rm <path>}"
p=$(cd "$p" 2>/dev/null && pwd) || p="${p%/}"
wanted_paths | grep -qxF "$p" || die "not listed: $p"
# comment out rather than delete, so the entry is easy to restore.
# awk, not sed: the path is data, so no regex metachar/delimiter escaping.
awk -v target="$p" '
{
line = $0
sub(/#.*/, "", line)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
sub(/\/+$/, "", line)
if (line != "" && line == target) print "# " $0
else print $0
}
' "$LIST" > "$LIST.tmp" && mv "$LIST.tmp" "$LIST"
echo "removed: $p"
cmd_sync
}
cmd_edit() { "${EDITOR:-vi}" "$LIST"; cmd_sync; }
cmd_log() {
local p="${1:?usage: claude-rc log <path>}"
p=$(cd "$p" 2>/dev/null && pwd) || die "no such directory: $p"
journalctl --user -u "${UNIT}@$(systemd-escape -p "$p").service" -n 40 --no-pager
}
case "${1:-list}" in
sync) cmd_sync ;;
list|ls|status) cmd_list ;;
add) shift; cmd_add "${1:-}" ;;
rm|remove|del) shift; cmd_rm "${1:-}" ;;
edit) cmd_edit ;;
log|logs) shift; cmd_log "${1:-}" ;;
*) cat >&2 <<EOF
usage: claude-rc <cmd>
list projects + running state (default)
add [path] add project (default: cwd)
rm <path> remove project (comments it out)
edit \$EDITOR the list, then sync
sync force reconcile
log <path> journal for one project
list file: $LIST
EOF
exit 1 ;;
esac
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Launch a Claude Code Remote Control server for one project dir.
# Invoked by claude-rc@.service; arg is the project path.
set -euo pipefail
dir="${1:?usage: claude-rc-run <project-dir>}"
if [ ! -d "$dir" ]; then
echo "claude-rc-run: no such directory: $dir" >&2
exit 78 # EX_CONFIG - systemd won't spin on Restart=on-failure
fi
# --spawn worktree needs a git repo; fall back to same-dir if not one
spawn=worktree
git -C "$dir" rev-parse --git-dir >/dev/null 2>&1 || {
echo "claude-rc-run: $dir is not a git repo, using --spawn same-dir" >&2
spawn=same-dir
}
# Optional display name: a projects-file line may read
# /path/to/repo | My Display Name
# Falls back to the bare directory name.
LIST="${CLAUDE_RC_LIST:-$HOME/.config/claude-rc/projects}"
name=$(awk -F'|' -v t="$dir" '
{ line=$0; sub(/#.*/,"",line)
p=line; if (index(line,"|")) { p=substr(line,1,index(line,"|")-1) }
gsub(/^[[:space:]]+|[[:space:]]+$/,"",p); sub(/\/+$/,"",p)
if (p == t && index(line,"|")) {
n=substr(line,index(line,"|")+1)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",n)
if (n != "") { print n; exit }
}
}' "$LIST" 2>/dev/null || true)
[ -n "$name" ] || name="$(basename "$dir")"
cd "$dir"
exec claude remote-control \
--spawn "$spawn" \
--permission-mode bypassPermissions \
--name "$name"
+36
View File
@@ -0,0 +1,36 @@
{
pkgs,
inputs,
...
}:
{
imports = [
../common/programs/default.nix
../common/programs/desktop
../common/programs/zen-browser.nix
../common/programs/hyprland
../common/programs/kitty
../common/programs/gpg.nix
../common/features/xdg.nix
../common/features/downloads-cleanup.nix
./monitors.nix
./services.nix
];
custom.hyprland.cursorSize = 32;
# The agent session manager the hourlog/week-review timers drive. Was a
# hand-installed binary in ~/.local/bin on the Ubuntu box.
home.packages = [ inputs.agent-of-empires.packages.${pkgs.system}.default ];
# Blur and shadow cost a fullscreen pass per frame, and every frame here is
# also x264-encoded for the stream — on a virtio-gpu with no VirGL, in software.
wayland.windowManager.hyprland.extraConfig = ''
hl.config({
decoration = {
blur = { enabled = false },
shadow = { enabled = false },
},
})
'';
}
+11
View File
@@ -0,0 +1,11 @@
_:
{
# Hyprland 0.55+ is Lua-only (see home/common/programs/hyprland).
#
# Matches every output rather than naming one: the virtio-gpu connector name
# varies by qemu display backend (Virtual-1 vs Virtual-0). Explicit mode, not
# `preferred` — this is the resolution Sunshine streams.
wayland.windowManager.hyprland.extraConfig = ''
hl.monitor({ output = "", mode = "2560x1440@60", position = "0x0", scale = 1 })
'';
}
+193
View File
@@ -0,0 +1,193 @@
{
pkgs,
inputs,
...
}:
# The user services this box exists to run, ported from hand-written units in
# ~/.config/systemd/user on the Ubuntu machine.
#
# NOT self-contained: every ExecStart under ~/.bun or ~/.local/bin is an
# imperatively-installed binary, and the WorkingDirectories are clones of
# separate repos. Nix owns the unit definitions here, nothing more.
let
# A user unit gets almost no PATH by default; these are the profile dirs the
# original units got for free from the system PATH on Ubuntu.
toolPath = "%h/.local/bin:%h/.nix-profile/bin:/etc/profiles/per-user/naps62/bin:/run/current-system/sw/bin";
sem = inputs.sem.packages.${pkgs.system}.default;
# runtimeInputs is prepended to PATH, not a replacement, so `claude` still
# resolves from the unit's own PATH.
claude-rc-run = pkgs.writeShellApplication {
name = "claude-rc-run";
runtimeInputs = with pkgs; [
git
gawk
coreutils
];
text = builtins.readFile ./bin/claude-rc-run;
};
claude-rc = pkgs.writeShellApplication {
name = "claude-rc";
runtimeInputs = with pkgs; [
git
gawk
gnused
gnugrep
coreutils
systemd
];
text = builtins.readFile ./bin/claude-rc;
};
in
{
home.packages = [
claude-rc
sem
pkgs.bun
];
systemd.user.services = {
rev = {
Unit = {
Description = "rev always-on local code review server";
After = [ "network.target" ];
};
Service = {
Type = "simple";
WorkingDirectory = "%h/tea/yolo/rev";
# nodejs_26, not pkgs.nodejs: rev's package.json sets engines >=26 and
# the nixpkgs default is 24.
ExecStart = "${pkgs.nodejs_26}/bin/node server/index.ts";
Environment = [
"NODE_ENV=production"
"REV_ROOTS=%h"
"REV_DEPTH=3"
"REV_SEM_BIN=${sem}/bin/sem"
"PATH=${toolPath}"
];
Restart = "always";
RestartSec = 2;
};
Install.WantedBy = [ "default.target" ];
};
rev-deploy = {
Unit = {
Description = "rev-deploy Gitea webhook listener that deploys rev on push to main";
After = [ "network.target" ];
};
Service = {
Type = "simple";
WorkingDirectory = "%h/tea/yolo/rev";
ExecStart = "${pkgs.bun}/bin/bun scripts/deploy-webhook.ts";
EnvironmentFile = "%h/.config/rev/deploy.env";
Environment = [ "PATH=${toolPath}" ];
Restart = "always";
RestartSec = 2;
};
Install.WantedBy = [ "default.target" ];
};
"claude-rc@" = {
Unit = {
Description = "Claude Code Remote Control (/%I)";
Documentation = [ "https://code.claude.com/docs/en/remote-control" ];
After = [ "network-online.target" ];
Wants = [ "network-online.target" ];
StopWhenUnneeded = false;
};
Service = {
Type = "simple";
# Leading "-": a missing dir must not be fatal, or systemd fails with
# 200/CHDIR before claude-rc-run can report the friendlier exit 78.
WorkingDirectory = "-/%I";
ExecStart = "${claude-rc-run}/bin/claude-rc-run /%I";
Environment = [ "PATH=${toolPath}" ];
# `always`, not `on-failure`: a >10min outage times the session out and
# the process exits 0, which on-failure would not restart.
Restart = "always";
RestartSec = 15;
# 78 = dir gone; 200 = systemd CHDIR failure. Without these, a deleted
# project dir restart-loops every 15s forever.
RestartPreventExitStatus = "78 200";
StandardOutput = "append:%h/.local/state/claude-rc/%i.log";
StandardError = "inherit";
};
Install.WantedBy = [ "default.target" ];
};
claude-rc-sync = {
Unit.Description = "Reconcile Claude Remote Control servers with the project list";
Service = {
Type = "oneshot";
ExecStart = "${claude-rc}/bin/claude-rc sync";
Environment = [ "PATH=${toolPath}" ];
};
};
hourlog = {
Unit = {
Description = "Start the Friday hour log in a tmux session";
Documentation = [ "https://git.naps.pt/yolo/agent-skills" ];
ConditionPathIsDirectory = "%h/tea/yolo/agent-skills";
};
Service = {
Type = "oneshot";
ExecStart = "%h/tea/yolo/agent-skills/bin/hourlog-session.sh";
Environment = [ "PATH=${toolPath}" ];
# This unit may be what starts the tmux server; the default cgroup kill
# would take it back down as soon as ExecStart returns.
KillMode = "process";
};
};
week-review = {
Unit = {
Description = "Start the weekly agent-skills review in a tmux session";
Documentation = [ "https://git.naps.pt/yolo/agent-skills" ];
ConditionPathIsDirectory = "%h/tea/yolo/agent-skills";
};
Service = {
Type = "oneshot";
ExecStart = "%h/tea/yolo/agent-skills/bin/week-review-session.sh";
Environment = [ "PATH=${toolPath}" ];
KillMode = "process";
};
};
};
systemd.user.timers = {
hourlog = {
Unit.Description = "Friday hour log, 18:00 Europe/Lisbon";
Timer = {
# Zone suffix pinned because the machine clock is UTC; keeps it at 18:00
# wall time across DST.
OnCalendar = "Fri 18:00 Europe/Lisbon";
Persistent = true;
AccuracySec = "1min";
};
Install.WantedBy = [ "timers.target" ];
};
week-review = {
Unit.Description = "Weekly agent-skills review, Fridays 17:00 Europe/Lisbon";
Timer = {
OnCalendar = "Fri 17:00 Europe/Lisbon";
Persistent = true;
AccuracySec = "1min";
};
Install.WantedBy = [ "timers.target" ];
};
};
systemd.user.paths.claude-rc = {
Unit.Description = "Watch the Claude Remote Control project list for edits";
Path = {
PathChanged = "%h/.config/claude-rc/projects";
Unit = "claude-rc-sync.service";
};
Install.WantedBy = [ "default.target" ];
};
}
+31
View File
@@ -0,0 +1,31 @@
_:
# Sunshine as a remote desktop. Same wlr-screencopy capture as
# features/gaming/sunshine.nix, minus its NVIDIA workarounds: no cudaSupport
# (virtio-gpu has no NVENC, so this software-encodes) and no uhid rule.
#
# First run: https://<host>:47990 to set web-UI credentials, then pair Moonlight.
{
services.sunshine = {
enable = true;
autoStart = true;
openFirewall = true;
capSysAdmin = false; # wlr-screencopy needs no CAP_SYS_ADMIN
settings = {
# MUST be set. Auto-probe tries portalgrab first, and
# xdg-desktop-portal-hyprland implements no RemoteDesktop interface — the
# probe then hangs forever instead of falling back, so sunshine never
# binds its ports and the unit sits "active" doing nothing.
capture = "wlgrab";
# The web UI is only reachable over the network here — there is no local
# browser — and sunshine CSRF-rejects any origin but localhost unless it
# is listed. Both addresses: .250.1 now, .10.2 after the IP handover.
#
# Bare comma-separated, NOT a JSON array: sunshine splits this on commas
# and never strips brackets or quotes, so "[...]" makes every entry fail
# its starts_with("https://") check.
csrf_allowed_origins = "https://10.7.250.1:47990,https://10.7.10.2:47990";
};
};
}
+77
View File
@@ -0,0 +1,77 @@
{
lib,
pkgs,
...
}:
{
imports = [
./hardware-configuration.nix
../common/global
../common/features/user.nix
../common/features/networking.nix
../common/features/display
../common/features/pipewire.nix
../common/features/remote-desktop.nix
../common/features/docker.nix
../common/features/fonts.nix
../common/features/nix-ld.nix
../common/features/appimage.nix
../common/features/home
];
networking.hostName = "yolo";
# Lets the Proxmox host drive clean shutdowns/reboots and report the guest's
# IP. The qemu-guest profile in hardware-configuration.nix only sets up the
# virtio drivers; the agent itself is a separate service.
services.qemuGuest.enable = true;
# Not a security downgrade: naps62 is in `docker`, which is already
# root-equivalent, and sshd is key-only — so the prompt guards nothing while
# blocking unattended rebuilds (nh shells out to sudo and needs a TTY).
security.sudo.wheelNeedsPassword = false;
# Nix shells out to git for the `type = "git"` flake inputs (hyprland).
# Without it at system level, a fresh install cannot build the home config
# that would have provided git — so neither rebuild works.
environment.systemPackages = [ pkgs.git ];
# This clone lives under ~/tea, not the ~/projects path global/nix.nix assumes.
programs.nh.flake = lib.mkForce "/home/naps62/tea/naps62/nixos-config";
# No physical seat: Sunshine is a user service and cannot capture until a
# graphical session exists, so a cold boot must reach one unattended. No
# hyprlock on start (unlike konishi) — nobody could type the password in.
services.displayManager = {
autoLogin = {
enable = true;
user = "naps62";
};
defaultSession = "hyprland";
};
# A Proxmox guest has no emulated sound card, so PipeWire starts with no sink
# and Sunshine has nothing to capture — the stream is silent. This sink is the
# only one, so it wins the default and Sunshine records its monitor.
services.pipewire.extraConfig.pipewire."10-null-sink" = {
"context.objects" = [
{
factory = "adapter";
args = {
"factory.name" = "support.null-audio-sink";
"node.name" = "sunshine-sink";
"node.description" = "Sunshine";
"media.class" = "Audio/Sink";
"audio.position" = "FL,FR";
};
}
];
};
# Pinned to 16 to match the cluster being migrated off Ubuntu; restoring that
# dump into a newer default would need a pg_upgrade first.
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
};
}
+52
View File
@@ -0,0 +1,52 @@
{
config,
lib,
modulesPath,
...
}:
# Proxmox guest (VM 132), q35 + OVMF. Hand-written rather than generated:
# filesystems are matched by label, set when the disk is partitioned, so this
# stays valid if the VM is ever rebuilt from scratch.
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot = {
initrd.availableKernelModules = [
"ahci"
"xhci_pci"
"virtio_pci"
"virtio_scsi"
"sd_mod"
"sr_mod"
];
# In the initrd, not kernelModules: otherwise there is no DRM device until
# late boot and SDDM races it.
initrd.kernelModules = [ "virtio_gpu" ];
kernelModules = [ "kvm-intel" ];
extraModulePackages = [ ];
};
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/BOOT";
fsType = "vfat";
options = [
"fmask=0077"
"dmask=0077"
];
};
swapDevices = [ { device = "/dev/disk/by-label/swap"; } ];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}