Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0322a8d9de | |||
| 21b4f9e30e | |||
| 12105a75d6 | |||
| 108814238a | |||
| b163785d2a | |||
| dd263b3e9f | |||
| 4c16212e0a | |||
| 5ce2aefc67 | |||
| d80b481e1a | |||
| b2f5625a63 | |||
| 2431722fe7 | |||
| d4ee1c66a8 | |||
| 826c4b4058 | |||
| 6ede2df401 | |||
| 0556b58c79 | |||
| 47c0732624 | |||
| e14bdcf3d1 | |||
| c56ef62cf4 | |||
| 914753fb55 | |||
| 2df744b244 | |||
| c4db021783 | |||
| 0a26367881 | |||
| 01de255a7a | |||
| a5661de579 | |||
| 1079822f67 | |||
| eb3eb0fe40 | |||
| 9bd137c31f | |||
| 4088e0f72c | |||
| 79a8cae2e7 | |||
| 0e6109df93 | |||
| 954d23cce6 | |||
| 6f3e4fb462 | |||
| 35f0145e22 | |||
| b21b25fe7b | |||
| a50eaa4707 | |||
| d74f1ac39e | |||
| ccc5d0d88c | |||
| ca1cba782e | |||
| 434ed3729f | |||
| df9fb4c495 | |||
| 4b12fd94db | |||
| aa7a9b179d | |||
| 08db70518b | |||
| 09bd517439 | |||
| 2cfdc7e68e | |||
| 5bafa07427 | |||
| f8c95e3b8d | |||
| 00810b4104 | |||
| ede94f9251 | |||
| ca0ad8f662 | |||
| 0519030103 | |||
| 4cb6e63e55 | |||
| 6581bab9ca | |||
| 7c59ed8af6 | |||
| 2b5c0eabea | |||
| f3acdf7781 | |||
| 16bf875f5b | |||
| 75f216165c | |||
| 89fb6c8b14 | |||
| 13eb534ab3 | |||
| 803f63d92b | |||
| 0d43a0178a | |||
| 5f3f502b8d | |||
| 92b51ddffc | |||
| cf74a9e49b | |||
| f1cbd1d540 | |||
| 448c5df0ed | |||
| 88d0be4098 | |||
| 755de4fb71 | |||
| 7d0018903d | |||
| 799158d90e | |||
| 5da1b61309 | |||
| 7a5c9981a7 | |||
| 0d364dcbbb | |||
| 8d39467f6b | |||
| e56962960b | |||
| 25bb46b706 | |||
| 7381a6b764 | |||
| 8befa14be1 | |||
| bf85f679f8 | |||
| 6d2754318c | |||
| 49579d92dd | |||
| 7495d84f1e | |||
| 9e03f255e4 | |||
| 3922a93586 | |||
| 4a90cd1dfc | |||
| 0039ab20ee | |||
| 5d53b7bf35 | |||
| 51a6696efb | |||
| 05bae722f7 | |||
| 5ee27b6df1 | |||
| d2c03ed846 | |||
| e08e845b38 | |||
| 91e6b195cf | |||
| 4d93e94c12 | |||
| c99627b3f6 | |||
| 2a3ccd2442 | |||
| 2c692550db | |||
| a23488dc52 | |||
| 2e464d2fe8 | |||
| 58e953a857 | |||
| 014dbde21d | |||
| 48c33fdbb5 | |||
| c11acc8b88 | |||
| 3d372cd155 | |||
| 0d2a4cb86f | |||
| fc88ed82fd | |||
| fb01826479 | |||
| d932409986 | |||
| 7c7aa0a7d9 | |||
| f99e0d4a8f | |||
| d41572a51c | |||
| 0f059c02c8 | |||
| 11f6ad5a3d | |||
| 1ce7046994 | |||
| 42fa03ec0e | |||
| 1b52576097 | |||
| 522eda7644 | |||
| 25ca90f134 | |||
| ccf0b2c35d | |||
| 2532100b26 | |||
| 9b4a2b44dc | |||
| 503b3baeb4 |
Generated
+164
-26
@@ -7,27 +7,26 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1786554876,
|
||||
"narHash": "sha256-akMknOIdX0QsBsM4lgreXSNNym7TNu9iOrDBqrQuTuQ=",
|
||||
"owner": "agent-of-empires",
|
||||
"repo": "agent-of-empires",
|
||||
"rev": "9b0d691a6cdaa6d2451f6eda05e83628c54eb95a",
|
||||
"type": "github"
|
||||
"lastModified": 1787387938,
|
||||
"narHash": "sha256-113jGEYKSeTwYgLdlVuRyqPBHYHJcU1kbRKiAUhj04Q=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "96472052106cb5c448e9fa39244b0d0ccae9be72",
|
||||
"revCount": 2192,
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/yolo/agent-of-empires.git"
|
||||
},
|
||||
"original": {
|
||||
"owner": "agent-of-empires",
|
||||
"ref": "v1.14.1",
|
||||
"repo": "agent-of-empires",
|
||||
"type": "github"
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/yolo/agent-of-empires.git"
|
||||
}
|
||||
},
|
||||
"agent-skills": {
|
||||
"locked": {
|
||||
"lastModified": 1787174393,
|
||||
"narHash": "sha256-nMxPQbVaK9cdsidGP78VOxUKTAHRKHGQEzkRbKxuivY=",
|
||||
"lastModified": 1788356316,
|
||||
"narHash": "sha256-y5dQioiefegmuPLulFSEL4DrRJMaEHt1UcMGyOM6fRk=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "465b20a7c64c84c645acbd94548c36c36decbfe4",
|
||||
"revCount": 66,
|
||||
"rev": "abd21928bf993962f98364d48a2877155beaa6d0",
|
||||
"revCount": 111,
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/yolo/agent-skills.git"
|
||||
},
|
||||
@@ -75,11 +74,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1787169825,
|
||||
"narHash": "sha256-3MmWHAzeoFHfwjUCQC01olBN+MC4xoSuR7D0bpPr+EE=",
|
||||
"lastModified": 1788303707,
|
||||
"narHash": "sha256-ICEHLJAM480kNrsOQ+DPWWV3XFJUDc6/n/PPcjAuRR8=",
|
||||
"owner": "sadjow",
|
||||
"repo": "claude-code-nix",
|
||||
"rev": "f575914388682670df73e80c9caf82063b659699",
|
||||
"rev": "7f0aeb2f0c6cf1ca2bf3b0b76b823b24b04b5b21",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -146,6 +145,21 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane_2": {
|
||||
"locked": {
|
||||
"lastModified": 1787326676,
|
||||
"narHash": "sha256-lWhBbBvC05/xwivKBBiM2YNizpmgqCgyOIzomvRuwxs=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "692f7e9ef2ece8125b466f66f2af532b3edaed0d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ethui": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
@@ -205,6 +219,24 @@
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1785627969,
|
||||
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_3": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775087534,
|
||||
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
||||
@@ -714,6 +746,43 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"maestro": {
|
||||
"inputs": {
|
||||
"crane": "crane_2",
|
||||
"flake-parts": "flake-parts_2",
|
||||
"nixpkgs": "nixpkgs_6",
|
||||
"process-compose-flake": "process-compose-flake"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788472721,
|
||||
"narHash": "sha256-meDGgW8/Wf8vh1Yt/PqsAq3tkQ1s5fKn/29tvBkc2UU=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "8325bc6d9a9a6c74995737af121114c365b17b44",
|
||||
"revCount": 617,
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/naps62/maestro.git"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/naps62/maestro.git"
|
||||
}
|
||||
},
|
||||
"nix-flatpak": {
|
||||
"locked": {
|
||||
"lastModified": 1767983141,
|
||||
"narHash": "sha256-7ZCulYUD9RmJIDULTRkGLSW1faMpDlPKcbWJLYHoXcs=",
|
||||
"owner": "gmodena",
|
||||
"repo": "nix-flatpak",
|
||||
"rev": "440818969ac2cbd77bfe025e884d0aa528991374",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "gmodena",
|
||||
"ref": "latest",
|
||||
"repo": "nix-flatpak",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-index-database": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -766,6 +835,21 @@
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib_2": {
|
||||
"locked": {
|
||||
"lastModified": 1785031560,
|
||||
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib_3": {
|
||||
"locked": {
|
||||
"lastModified": 1774748309,
|
||||
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
|
||||
@@ -782,11 +866,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1787111413,
|
||||
"narHash": "sha256-sFosWtq21eHGJRnTc/hvf4M1obRgLEUMNm/IzllkHMA=",
|
||||
"lastModified": 1787964612,
|
||||
"narHash": "sha256-0N9nghg3nwzX6b6qc77EzjR9cu/Z+UR66FlfsCqiURs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "afe3d8ac4395617bdcdac9f188ac8717a062e014",
|
||||
"rev": "e8be7818e19ada32105a8af937a6a473b38167ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -845,6 +929,22 @@
|
||||
}
|
||||
},
|
||||
"nixpkgs_6": {
|
||||
"locked": {
|
||||
"lastModified": 1787135253,
|
||||
"narHash": "sha256-RD2kNWCG+Bjo6h+JVjWVNntZs2GtRoeY2xHjts/FNkA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_7": {
|
||||
"locked": {
|
||||
"lastModified": 1787070829,
|
||||
"narHash": "sha256-vXNVDVtvfiQuXthP0NHPFdNvvMTkGpx0UP8oddIWbNk=",
|
||||
@@ -860,7 +960,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_7": {
|
||||
"nixpkgs_8": {
|
||||
"locked": {
|
||||
"lastModified": 1775036866,
|
||||
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
||||
@@ -876,7 +976,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_8": {
|
||||
"nixpkgs_9": {
|
||||
"locked": {
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
@@ -953,6 +1053,41 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"process-compose-flake": {
|
||||
"locked": {
|
||||
"lastModified": 1782060835,
|
||||
"narHash": "sha256-Q8HH2t1l3MITtWCWY4ytcH5F/Q7aAHo3Iq/QTMAKTe0=",
|
||||
"owner": "Platonic-Systems",
|
||||
"repo": "process-compose-flake",
|
||||
"rev": "464ff6880737f063c3f0d3d2c7781fda9190868f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Platonic-Systems",
|
||||
"repo": "process-compose-flake",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rev": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788356169,
|
||||
"narHash": "sha256-NpIwwZ3O+rahCrWkYJvZEcSM0FeoO0av7bR7MDgZNZ0=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "b432e8a9ed4e8c819df1aaf547161b71967395d3",
|
||||
"revCount": 235,
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/yolo/rev.git"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.naps.pt/yolo/rev.git"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"agent-of-empires": "agent-of-empires",
|
||||
@@ -965,10 +1100,13 @@
|
||||
"hardware": "hardware",
|
||||
"home-manager": "home-manager",
|
||||
"hyprland": "hyprland",
|
||||
"maestro": "maestro",
|
||||
"nix-flatpak": "nix-flatpak",
|
||||
"nix-index-database": "nix-index-database",
|
||||
"nixpkgs": "nixpkgs_6",
|
||||
"nixpkgs": "nixpkgs_7",
|
||||
"nvchad-starter": "nvchad-starter",
|
||||
"nvchad4nix": "nvchad4nix",
|
||||
"rev": "rev",
|
||||
"rose-pine-hyprcursor": "rose-pine-hyprcursor",
|
||||
"sem": "sem",
|
||||
"spicetify-nix": "spicetify-nix",
|
||||
@@ -1018,7 +1156,7 @@
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_8"
|
||||
"nixpkgs": "nixpkgs_9"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775445266,
|
||||
@@ -1036,9 +1174,9 @@
|
||||
},
|
||||
"sem": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts_2",
|
||||
"flake-parts": "flake-parts_3",
|
||||
"flake-utils": "flake-utils_4",
|
||||
"nixpkgs": "nixpkgs_7",
|
||||
"nixpkgs": "nixpkgs_8",
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
|
||||
@@ -64,12 +64,25 @@
|
||||
};
|
||||
# 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";
|
||||
agent-of-empires.url = "git+https://git.naps.pt/yolo/agent-of-empires.git";
|
||||
# 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";
|
||||
# Claude Code + Codex skills, commands, hooks and CLAUDE.md fragments.
|
||||
agent-skills.url = "git+https://git.naps.pt/yolo/agent-skills.git";
|
||||
# Terminal-session orchestrator. Pins its own nixpkgs for the same reason
|
||||
# ethui and agent-of-empires do — it is a verified Rust build.
|
||||
maestro.url = "git+https://git.naps.pt/naps62/maestro.git";
|
||||
# Always-on local code review server. Follows nixpkgs, unlike the Rust
|
||||
# inputs above: it is a plain node bundle, and a second nixpkgs would put a
|
||||
# second node 26 in the closure for nothing.
|
||||
rev = {
|
||||
url = "git+https://git.naps.pt/yolo/rev.git";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
# Declarative flatpak remotes and packages; nixpkgs' services.flatpak only
|
||||
# exposes `enable`. Has no nixpkgs input to follow.
|
||||
nix-flatpak.url = "github:gmodena/nix-flatpak/?ref=latest";
|
||||
nix-index-database = {
|
||||
url = "github:nix-community/nix-index-database";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
self,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.home.mutableFiles;
|
||||
repoPath = config.home.mutableFilesRepoPath;
|
||||
flakePrefix = self.outPath;
|
||||
|
||||
fileEntries = lib.attrsToList cfg;
|
||||
|
||||
toRepoPath = storePath: repoPath + lib.removePrefix flakePrefix (toString storePath);
|
||||
|
||||
checkScript = lib.concatMapStringsSep "\n" (
|
||||
{ name, value }:
|
||||
let
|
||||
target = "${config.home.homeDirectory}/${name}";
|
||||
storePath = value.source;
|
||||
# toRepoPath only works for sources that live in the flake tree. A
|
||||
# generated source is not under flakePrefix, so removePrefix is a no-op
|
||||
# and the hint would print a /nix/store path to copy back onto.
|
||||
originalPath =
|
||||
if value.upstreamPath != null then "${repoPath}/${value.upstreamPath}" else toRepoPath value.source;
|
||||
in
|
||||
''
|
||||
if [ -f "${target}" ] && ! ${lib.getExe' pkgs.diffutils "diff"} -q "${storePath}" "${target}" > /dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "!! mutable file changed: ${name}"
|
||||
echo " To bring changes upstream:"
|
||||
echo " cp ${target} ${originalPath}"
|
||||
echo ""
|
||||
${lib.getExe' pkgs.diffutils "diff"} -u "${storePath}" "${target}" || true
|
||||
_mutable_changed=1
|
||||
fi
|
||||
''
|
||||
) fileEntries;
|
||||
|
||||
copyScript = lib.concatMapStringsSep "\n" (
|
||||
{ name, value }:
|
||||
let
|
||||
target = "${config.home.homeDirectory}/${name}";
|
||||
inherit (value) source;
|
||||
dirName = builtins.dirOf target;
|
||||
in
|
||||
''
|
||||
mkdir -p "${dirName}"
|
||||
cp -f "${source}" "${target}"
|
||||
chmod ${if value.executable then "755" else "644"} "${target}"
|
||||
''
|
||||
) fileEntries;
|
||||
in
|
||||
{
|
||||
options.home.mutableFilesRepoPath = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Absolute path to the nixos-config repo on disk.";
|
||||
};
|
||||
|
||||
options.home.mutableFiles = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "Path to the source file.";
|
||||
};
|
||||
executable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether the file should be executable.";
|
||||
};
|
||||
|
||||
upstreamPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "home/yolo/claude-settings.json";
|
||||
description = ''
|
||||
Repo-relative file to name in the "bring changes upstream" hint.
|
||||
Required when `source` is generated rather than a file in the
|
||||
flake tree, since the store path cannot be mapped back.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
description = "Files to copy (not symlink) into the home directory, with change detection.";
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg != { }) {
|
||||
home.activation.mutableFiles = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
_mutable_changed=0
|
||||
${checkScript}
|
||||
if [ "$_mutable_changed" -eq 1 ]; then
|
||||
echo ""
|
||||
echo "!! Aborting: mutable files have been modified outside of nix."
|
||||
echo " Bring the changes upstream first, then re-run."
|
||||
exit 1
|
||||
fi
|
||||
${copyScript}
|
||||
'';
|
||||
};
|
||||
}
|
||||
+34
-11
@@ -1,20 +1,43 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.custom.blender;
|
||||
in
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
blender
|
||||
prusa-slicer
|
||||
];
|
||||
options.custom.blender = {
|
||||
cuda = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Build Blender with CUDA/OptiX so Cycles renders on the GPU instead of
|
||||
the CPU. Only useful on an NVIDIA host; enabling it on arrakis (Intel)
|
||||
would mean a long build for nothing.
|
||||
|
||||
fonts.fontconfig.enable = true;
|
||||
No binary cache serves this — cuda-maintainers was measured to make no
|
||||
difference — so flipping this on rebuilds Blender, OpenUSD, OpenSubdiv
|
||||
and OpenImageDenoise from source.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
xdg.desktopEntries.prusaslicer-url-handler = {
|
||||
name = "PrusaSlicer Protocol Handler";
|
||||
exec = "prusa-slicer %u";
|
||||
type = "Application";
|
||||
noDisplay = true;
|
||||
mimeType = [ "x-scheme-handler/prusaslicer" ];
|
||||
config = {
|
||||
home.packages = [
|
||||
(if cfg.cuda then pkgs.blender.override { cudaSupport = true; } else pkgs.blender)
|
||||
pkgs.prusa-slicer
|
||||
];
|
||||
|
||||
fonts.fontconfig.enable = true;
|
||||
|
||||
xdg.desktopEntries.prusaslicer-url-handler = {
|
||||
name = "PrusaSlicer Protocol Handler";
|
||||
exec = "prusa-slicer %u";
|
||||
type = "Application";
|
||||
noDisplay = true;
|
||||
mimeType = [ "x-scheme-handler/prusaslicer" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
description = "--force-device-scale-factor value for Claude Desktop and T3 Code.";
|
||||
};
|
||||
|
||||
# ~/.claude/settings.json stays unmanaged: Claude Code rewrites it itself
|
||||
# (model pins, permission grants, plugin state), so any nix copy drifts within
|
||||
# a session and every `nh home switch` then aborts on the diff.
|
||||
config.home = {
|
||||
packages = with pkgs; [
|
||||
inputs.claude-code.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||
@@ -47,6 +50,5 @@
|
||||
};
|
||||
};
|
||||
|
||||
mutableFiles.".claude/settings.json".source = ./settings.json;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Edit",
|
||||
"Write",
|
||||
"Bash(ls:*)",
|
||||
"Bash(tree:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(head:*)",
|
||||
"Bash(tail:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(fd:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(sort:*)",
|
||||
"Bash(uniq:*)",
|
||||
"Bash(diff:*)",
|
||||
"Bash(pwd:*)",
|
||||
"Bash(which:*)",
|
||||
"Bash(jq:*)",
|
||||
"Bash(git:*)",
|
||||
"Bash(gh:*)",
|
||||
"Bash(sed:*)",
|
||||
"Bash(cp:*)",
|
||||
"Bash(chmod:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(kitty @ set-tab-title:*)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/home/naps62/projects",
|
||||
"/home/naps62/ethui",
|
||||
"/home/naps62/labs",
|
||||
"/home/naps62/subvisual"
|
||||
]
|
||||
},
|
||||
"model": "opus[1m]",
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh"
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"typescript-lsp@claude-plugins-official": true
|
||||
},
|
||||
"extraKnownMarketplaces": {
|
||||
"impeccable": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "pbakaus/impeccable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"enabled": true,
|
||||
"autoAllowBashIfSandboxed": false,
|
||||
"allowedNetworkHosts": [
|
||||
"github.com",
|
||||
"api.github.com",
|
||||
"raw.githubusercontent.com",
|
||||
"gist.github.com",
|
||||
"release-assets.githubusercontent.com"
|
||||
]
|
||||
},
|
||||
"tui": "fullscreen",
|
||||
"voiceEnabled": true,
|
||||
"defaultMode": "acceptEdits",
|
||||
"feedbackSurveyState": {
|
||||
"lastShownTime": 1754052643456
|
||||
},
|
||||
"mcpServers": {
|
||||
"herd-mcp": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.herd.eco/v1"
|
||||
},
|
||||
"linear": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.linear.app/mcp"
|
||||
},
|
||||
"home-assistant": {
|
||||
"type": "http",
|
||||
"url": "https://ha-mcp.n62.casa/mcp"
|
||||
}
|
||||
},
|
||||
"agentPushNotifEnabled": true
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
../features/mutable-file.nix
|
||||
./zsh.nix
|
||||
./nix.nix
|
||||
./neovim
|
||||
@@ -28,10 +27,6 @@
|
||||
};
|
||||
|
||||
home = {
|
||||
# mkDefault: hosts whose clone lives elsewhere (yolo, under ~/tea) override
|
||||
# this with a plain assignment.
|
||||
mutableFilesRepoPath = lib.mkDefault "${config.home.homeDirectory}/projects/nixos-config";
|
||||
|
||||
username = lib.mkDefault "naps62";
|
||||
homeDirectory = lib.mkDefault "/home/${config.home.username}";
|
||||
stateVersion = lib.mkDefault "24.05";
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [ ./darkman.nix ];
|
||||
imports = [
|
||||
./darkman.nix
|
||||
# mpv is installed by programs.mpv there, not as a bare package here.
|
||||
../mpv.nix
|
||||
];
|
||||
|
||||
home = {
|
||||
packages = with pkgs; [
|
||||
# various
|
||||
google-chrome
|
||||
thunar
|
||||
mpv
|
||||
imv
|
||||
pavucontrol
|
||||
zathura
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{ inputs, ... }:
|
||||
# Maestro: the terminal-session orchestrator. Module only — no `enable` here,
|
||||
# because the daemon owns every interactive shell on the host it runs on, and
|
||||
# only one box should be doing that. Hosts opt in from their own services.nix.
|
||||
{
|
||||
imports = [ inputs.maestro.homeManagerModules.default ];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.custom.mpv;
|
||||
in
|
||||
{
|
||||
options.custom.mpv = {
|
||||
hwdec = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "auto-safe";
|
||||
example = "nvdec-copy";
|
||||
description = ''
|
||||
Value for mpv's `hwdec`. `auto-safe` only picks a decoder mpv considers
|
||||
reliable for the running driver and falls back to software otherwise,
|
||||
so it is correct on Intel (arrakis) and on hosts with no usable GPU
|
||||
decoder (yolo). NVIDIA hosts override it.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
programs.mpv = {
|
||||
enable = true;
|
||||
|
||||
config = {
|
||||
hwdec = cfg.hwdec;
|
||||
vo = lib.mkDefault "gpu-next";
|
||||
save-position-on-quit = true;
|
||||
keep-open = "yes";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# nix-autodeploy: one webhook endpoint that turns "a repo I own pushed to main"
|
||||
# into "that flake input is bumped, committed, and (optionally) applied".
|
||||
#
|
||||
# Replaces per-app deploy webhooks that rebuilt from a checkout on the box. The
|
||||
# apps are flake inputs now, so deploying one is a lock bump plus a generation
|
||||
# switch — the same operation for every app, hence one service instead of N.
|
||||
let
|
||||
cfg = config.services.nixAutodeploy;
|
||||
|
||||
# A user unit inherits almost no PATH, and the deploy shells out to git (with
|
||||
# the gitea credential helper), nix and nh.
|
||||
profilePath = lib.concatStringsSep ":" [
|
||||
"%h/.local/bin"
|
||||
"%h/.nix-profile/bin"
|
||||
"/etc/profiles/per-user/%u/bin"
|
||||
"/run/current-system/sw/bin"
|
||||
];
|
||||
|
||||
deploy = pkgs.writeShellApplication {
|
||||
name = "nix-autodeploy-deploy";
|
||||
runtimeInputs = [
|
||||
pkgs.git
|
||||
pkgs.nix
|
||||
pkgs.nh
|
||||
pkgs.curl
|
||||
pkgs.jq
|
||||
pkgs.util-linux
|
||||
];
|
||||
text = ''
|
||||
# usage: nix-autodeploy-deploy <flake-input> <apply|notify>
|
||||
input=$1
|
||||
mode=$2
|
||||
flake=${lib.escapeShellArg cfg.flake}
|
||||
topic=${lib.escapeShellArg cfg.ntfy.topic}
|
||||
ntfy_url=${lib.escapeShellArg cfg.ntfy.url}
|
||||
|
||||
notify() {
|
||||
[ -n "''${NTFY_TOKEN:-}" ] || return 0
|
||||
curl -fsS -X POST "$ntfy_url/$topic" \
|
||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
||||
-H "Title: $1" -d "$2" > /dev/null || true
|
||||
}
|
||||
|
||||
# Serialize: two pushes landing together would otherwise race on the same
|
||||
# working tree and the same flake.lock.
|
||||
exec 9> "''${XDG_RUNTIME_DIR:-/tmp}/nix-autodeploy.lock"
|
||||
flock 9
|
||||
|
||||
cd "$flake"
|
||||
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [ "$branch" != main ]; then
|
||||
notify "autodeploy skipped ($input)" "checkout is on $branch, not main"
|
||||
exit 0
|
||||
fi
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
notify "autodeploy skipped ($input)" "working tree is dirty"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git fetch --quiet origin main
|
||||
git merge --ff-only --quiet origin/main
|
||||
|
||||
nix flake update "$input"
|
||||
if git diff --quiet -- flake.lock; then
|
||||
echo "autodeploy: $input already at the pushed revision"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rev=$(nix flake metadata --json |
|
||||
jq -r --arg i "$input" '.locks.nodes[$i].locked.rev[0:7]')
|
||||
git commit --quiet -m "chore(flake): bump $input to $rev" -- flake.lock
|
||||
git push --quiet origin main
|
||||
|
||||
if [ "$mode" != apply ]; then
|
||||
notify "$input bumped to $rev" "Not applied — run 'nh home switch' when convenient."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if nh home switch "$flake"; then
|
||||
notify "$input deployed" "Bumped to $rev and switched."
|
||||
else
|
||||
notify "$input FAILED to apply" "Lock is at $rev on main; the switch failed. See journalctl --user -u run-*."
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
listener = pkgs.writers.writePython3Bin "nix-autodeploy" {
|
||||
# Only line length: http.server's do_GET/do_POST spelling is already
|
||||
# excused inline.
|
||||
flakeIgnore = [ "E501" ];
|
||||
} (builtins.readFile ./listener.py);
|
||||
|
||||
repoFile = (pkgs.formats.json { }).generate "nix-autodeploy-repos.json" (
|
||||
lib.mapAttrs (_: r: { inherit (r) input apply; }) cfg.repos
|
||||
);
|
||||
in
|
||||
{
|
||||
options.services.nixAutodeploy = {
|
||||
enable = lib.mkEnableOption "the forge-webhook listener that bumps and applies flake inputs";
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 7375;
|
||||
description = "Port the listener binds on 0.0.0.0. There is no auth beyond the webhook HMAC, so only expose it through the reverse proxy.";
|
||||
};
|
||||
|
||||
flake = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "/home/naps62/tea/nixos-config";
|
||||
description = "Absolute path to the nixos-config checkout whose flake.lock gets bumped. Must be on main and clean, or the deploy skips.";
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "%h/.config/nix-autodeploy/env";
|
||||
description = ''
|
||||
File holding `NIX_AUTODEPLOY_SECRET` (the webhook HMAC secret, shared
|
||||
with every repo below) and `NTFY_TOKEN`. Not in the store — these are
|
||||
secrets.
|
||||
'';
|
||||
};
|
||||
|
||||
ntfy = {
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://ntfy.home.naps.pt";
|
||||
description = "Base URL of the ntfy server deploy results are posted to.";
|
||||
};
|
||||
topic = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "nix-autodeploy";
|
||||
description = "ntfy topic for deploy results.";
|
||||
};
|
||||
};
|
||||
|
||||
repos = lib.mkOption {
|
||||
default = { };
|
||||
description = "Forge repositories to listen for, keyed by `<owner>/<repo>` exactly as the webhook payload spells it.";
|
||||
example = lib.literalExpression ''
|
||||
{ "yolo/rev" = { input = "rev"; }; }
|
||||
'';
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
input = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the flake input in this repo's flake.nix that tracks that repository.";
|
||||
};
|
||||
apply = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to run `nh home switch` after the bump. Set false for an
|
||||
app whose restart disrupts a live session — the lock is still
|
||||
bumped and pushed, and the ntfy message says it is waiting.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.user.services.nix-autodeploy = {
|
||||
Unit = {
|
||||
Description = "nix-autodeploy — forge webhooks bump and apply flake inputs";
|
||||
After = [ "network.target" ];
|
||||
# Same restart-budget trap every other always-on unit here avoids: at
|
||||
# RestartSec=2 a fast-crashing listener would park in `failed`.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
ExecStart = lib.getExe listener;
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
Environment = [
|
||||
"PATH=${profilePath}"
|
||||
"NIX_AUTODEPLOY_PORT=${toString cfg.port}"
|
||||
"NIX_AUTODEPLOY_DEPLOY_BIN=${lib.getExe deploy}"
|
||||
"NIX_AUTODEPLOY_ENV_FILE=${cfg.environmentFile}"
|
||||
"NIX_AUTODEPLOY_REPOS_FILE=${repoFile}"
|
||||
];
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
};
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
"""Forge webhook listener that turns a push into a flake-input bump.
|
||||
|
||||
Verifies the HMAC a Gitea (or GitHub) webhook signs the body with, then hands
|
||||
the actual work to a transient systemd unit. Nothing is done in-process: the
|
||||
deploy runs `nh home switch`, which restarts every unit home-manager owns —
|
||||
including this listener — so the job has to outlive it.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
SECRET = os.environ.get("NIX_AUTODEPLOY_SECRET", "").encode()
|
||||
PORT = int(os.environ.get("NIX_AUTODEPLOY_PORT", "7375"))
|
||||
DEPLOY = os.environ["NIX_AUTODEPLOY_DEPLOY_BIN"]
|
||||
# {"<owner>/<repo>": {"input": "rev", "apply": true}, ...}. Passed as a file,
|
||||
# not a variable: systemd's Environment= strips the quotes out of inline JSON.
|
||||
with open(os.environ["NIX_AUTODEPLOY_REPOS_FILE"]) as fh:
|
||||
REPOS = json.load(fh)
|
||||
ENV_FILE = os.environ.get("NIX_AUTODEPLOY_ENV_FILE", "")
|
||||
MAX_BODY = 1 << 20
|
||||
|
||||
if not SECRET:
|
||||
sys.exit("NIX_AUTODEPLOY_SECRET is not set")
|
||||
|
||||
|
||||
def signature_ok(body: bytes, headers) -> bool:
|
||||
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
|
||||
# Gitea sends the bare hex digest; GitHub prefixes it with "sha256=".
|
||||
for name in ("X-Gitea-Signature", "X-Hub-Signature-256"):
|
||||
got = headers.get(name)
|
||||
if got and hmac.compare_digest(got.removeprefix("sha256="), expected):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def spawn(repo: str, entry: dict) -> None:
|
||||
cmd = [
|
||||
"systemd-run",
|
||||
"--user",
|
||||
"--collect",
|
||||
f"--description=nix-autodeploy: {repo}",
|
||||
# PATH is not inherited by a transient unit, and the deploy shells out
|
||||
# to git, nix and the gitea credential helper.
|
||||
f"--setenv=PATH={os.environ['PATH']}",
|
||||
]
|
||||
if ENV_FILE:
|
||||
cmd.append(f"--property=EnvironmentFile={ENV_FILE}")
|
||||
cmd += [DEPLOY, entry["input"], "apply" if entry.get("apply", True) else "notify"]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def reply(self, code: int, text: str) -> None:
|
||||
payload = text.encode()
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802 - BaseHTTPRequestHandler's spelling
|
||||
if self.path == "/health":
|
||||
self.reply(200, "ok\n")
|
||||
else:
|
||||
self.reply(404, "no\n")
|
||||
|
||||
def do_POST(self) -> None: # noqa: N802
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
if length > MAX_BODY:
|
||||
return self.reply(413, "body too large\n")
|
||||
body = self.rfile.read(length)
|
||||
|
||||
if not signature_ok(body, self.headers):
|
||||
return self.reply(401, "bad signature\n")
|
||||
|
||||
try:
|
||||
event = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
return self.reply(400, "bad json\n")
|
||||
|
||||
ref = event.get("ref")
|
||||
repo = (event.get("repository") or {}).get("full_name")
|
||||
if ref != "refs/heads/main":
|
||||
return self.reply(200, f"ignored ref {ref}\n")
|
||||
|
||||
entry = REPOS.get(repo)
|
||||
if entry is None:
|
||||
return self.reply(200, f"ignored repo {repo}\n")
|
||||
|
||||
spawn(repo, entry)
|
||||
self.reply(202, f"deploying {entry['input']}\n")
|
||||
|
||||
def log_message(self, fmt: str, *args) -> None:
|
||||
# Journal already timestamps; the default format prepends its own.
|
||||
sys.stderr.write(f"{self.address_string()} {fmt % args}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
|
||||
@@ -0,0 +1,7 @@
|
||||
{ inputs, ... }:
|
||||
# rev: the always-on code review server. Module only — no `enable` here. It
|
||||
# discovers every repo under its roots and serves them without auth, so only a
|
||||
# box that is already a trusted single-user machine should run it.
|
||||
{
|
||||
imports = [ inputs.rev.homeManagerModules.default ];
|
||||
}
|
||||
@@ -10,7 +10,18 @@
|
||||
enableCompletion = true;
|
||||
autosuggestion.enable = true;
|
||||
syntaxHighlighting.enable = true;
|
||||
completionInit = "autoload -U compinit && compinit -u";
|
||||
# A full compinit security-checks and rebuilds the dump on every shell
|
||||
# start. Do that at most once a day and use the cached dump (-C) in
|
||||
# between. Missing dump globs to nothing, so it takes the full path.
|
||||
completionInit = ''
|
||||
autoload -U compinit
|
||||
_zcompdump_fresh() {
|
||||
setopt local_options extendedglob
|
||||
[[ -n ''${ZDOTDIR:-$HOME}/.zcompdump(#qNmh-24) ]]
|
||||
}
|
||||
if _zcompdump_fresh; then compinit -C; else compinit -u; fi
|
||||
unfunction _zcompdump_fresh
|
||||
'';
|
||||
|
||||
shellAliases = {
|
||||
c = "cargo";
|
||||
|
||||
@@ -24,10 +24,24 @@
|
||||
|
||||
home.sessionVariables = {
|
||||
LIBVA_DRIVER_NAME = "nvidia";
|
||||
GDM_BACKEND = "nvidia-drm";
|
||||
# Was GDM_BACKEND, which nothing reads (SDDM is the DM here).
|
||||
GBM_BACKEND = "nvidia-drm";
|
||||
__GLX_VENDOR_LIBRARY_NAME = "nvidia";
|
||||
|
||||
# Firefox/Zen decode video in a separate RDD process whose sandbox denies
|
||||
# /dev/nvidia*, so nvidia-vaapi-driver never initialises there and playback
|
||||
# silently drops to software. Costs one sandbox layer; no narrower switch
|
||||
# exists. Check with `nvidia-smi --query-gpu=utilization.decoder`.
|
||||
MOZ_DISABLE_RDD_SANDBOX = "1";
|
||||
};
|
||||
|
||||
# Plain `nvdec` keeps frames in GPU memory and breaks some filters.
|
||||
custom.mpv.hwdec = "nvdec-copy";
|
||||
|
||||
# The 4060 would render Cycles far faster, but nothing caches this build —
|
||||
# flipping it on compiles Blender, OpenUSD, OpenSubdiv and OpenImageDenoise.
|
||||
custom.blender.cuda = false;
|
||||
|
||||
custom.hyprland = {
|
||||
yaziSize = "2400 1800";
|
||||
cursorSize = 42;
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
default_profile = "default"
|
||||
|
||||
[acp]
|
||||
allow_agent_install = false
|
||||
allowed_agents = []
|
||||
auto_stop_idle_secs = 0
|
||||
compaction_reminder = false
|
||||
compaction_reminder_percent = 75
|
||||
default_agent = "claude"
|
||||
max_concurrent_workers = 5
|
||||
node_path = ""
|
||||
offer_structured_in_new_session = true
|
||||
rate_limit_auto_resume = false
|
||||
replay_events = 0
|
||||
restrict_agents = false
|
||||
show_tool_durations = true
|
||||
silent_orphan_grace_secs = 120
|
||||
|
||||
[auth]
|
||||
persist_sessions = true
|
||||
|
||||
[diff]
|
||||
context_lines = 3
|
||||
split_view = false
|
||||
|
||||
[hooks]
|
||||
|
||||
[host_hooks]
|
||||
|
||||
[logging]
|
||||
default_level = "info"
|
||||
file_path = "debug.log"
|
||||
keep_count = 5
|
||||
max_size_mib = 50
|
||||
output = "file"
|
||||
rotation = "size"
|
||||
show_spans = false
|
||||
|
||||
[logging.targets]
|
||||
|
||||
[sandbox]
|
||||
auto_cleanup = true
|
||||
container_runtime = "docker"
|
||||
default_image = "ghcr.io/agent-of-empires/aoe-sandbox:latest"
|
||||
default_terminal_mode = "host"
|
||||
enabled_by_default = false
|
||||
environment = [
|
||||
"TERM",
|
||||
"COLORTERM",
|
||||
"FORCE_COLOR",
|
||||
"NO_COLOR",
|
||||
]
|
||||
extra_volumes = []
|
||||
mount_ssh = false
|
||||
selinux_relabel = false
|
||||
volume_ignores = []
|
||||
volume_ignores_strategy = "anonymous"
|
||||
|
||||
[session]
|
||||
agent_status_hooks = true
|
||||
auto_resume_on_restart = true
|
||||
auto_stop_idle_secs = 0
|
||||
click_action = "live_send"
|
||||
confirm_before_quit = false
|
||||
confirm_delete = false
|
||||
conversation_summary = false
|
||||
default_attach_mode = "tmux"
|
||||
delete_to_trash = false
|
||||
favorites_first = true
|
||||
inherit_host_environment = false
|
||||
live_send_exit_chord = "C-q"
|
||||
live_send_leader = "C-b"
|
||||
live_send_on_view_switch = false
|
||||
merge_hooks_into_selected_agent = true
|
||||
mouse_capture = true
|
||||
opencode_preassign_session_id = false
|
||||
prevent_sleep_idle_grace_minutes = 15
|
||||
prevent_sleep_when_active = false
|
||||
restart_wake_message = "wake up: pick up what you were doing"
|
||||
row_tag = "none"
|
||||
show_session_colors = true
|
||||
show_tips = false
|
||||
smart_rename = true
|
||||
smart_rename_agent = ""
|
||||
snooze_duration_minutes = 30
|
||||
strict_hotkeys = false
|
||||
tie_workdir_to_name = true
|
||||
trash_retention_days = 30
|
||||
unread_indicator = true
|
||||
yolo_mode_default = true
|
||||
|
||||
[session.agent_detect_as]
|
||||
synclaude = "claude"
|
||||
|
||||
[session.custom_agents]
|
||||
synclaude = "synclaude"
|
||||
|
||||
[skills]
|
||||
auto_propagate = false
|
||||
|
||||
[sound]
|
||||
enabled = false
|
||||
|
||||
[status_hooks]
|
||||
enabled = false
|
||||
|
||||
[telemetry]
|
||||
enabled = false
|
||||
|
||||
[theme]
|
||||
color_mode = "truecolor"
|
||||
idle_decay_minutes = 0
|
||||
name = "zinc"
|
||||
|
||||
[tmux]
|
||||
clipboard = "auto"
|
||||
mouse = "auto"
|
||||
status_bar = "auto"
|
||||
vt_live = true
|
||||
|
||||
[updates]
|
||||
auto_update_plugins = false
|
||||
update_check_mode = "notify"
|
||||
|
||||
[web]
|
||||
notifications_enabled = true
|
||||
notify_on_error = true
|
||||
notify_on_idle = false
|
||||
notify_on_waiting = true
|
||||
notify_on_wake_fire = true
|
||||
|
||||
[worktree]
|
||||
auto_cleanup = true
|
||||
bare_repo_path_template = "./{branch}"
|
||||
delete_branch_on_cleanup = false
|
||||
enabled = true
|
||||
init_submodules = true
|
||||
path_template = "../{repo-name}-worktrees/{branch}"
|
||||
workspace_path_template = "../{branch}-workspace-{session-id}"
|
||||
@@ -1,247 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash",
|
||||
"Edit",
|
||||
"Write",
|
||||
"NotebookEdit",
|
||||
"WebFetch",
|
||||
"WebSearch"
|
||||
],
|
||||
"deny": [
|
||||
"Read(~/.ssh/**)",
|
||||
"Read(*.pem)",
|
||||
"Read(*.key)",
|
||||
"mcp__dokploy__application-one",
|
||||
"mcp__dokploy__mounts-listByServiceId",
|
||||
"mcp__dokploy__mounts-one",
|
||||
"mcp__dokploy__compose-one",
|
||||
"mcp__dokploy__schedule-create",
|
||||
"mcp__dokploy__schedule-update",
|
||||
"mcp__dokploy__schedule-runManually"
|
||||
],
|
||||
"defaultMode": "auto"
|
||||
},
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/comment-lint.py"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "AskUserQuestion",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf running > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/tmux-attention.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "permission_prompt|elicitation_dialog|agent_needs_input",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf waiting > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "idle_prompt|agent_completed",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf idle > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/comms-lint.py"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/tmux-reset.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "rtk hook claude"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; IN=$(cat 2>/dev/null); S=running; case \"$IN\" in *\\\"tool_name\\\":\\\"AskUserQuestion\\\"*) S=waiting ;; esac; printf %s \"$S\" > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/git-autoupdate.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 ~/.claude/scripts/aoe-register-remote.py"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/naps62/tea/rev/scripts/rev-hook-session-start.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c '[ -n \"$AOE_INSTANCE_ID\" ] || exit 0; command -v aoe >/dev/null 2>&1 || exit 0; aoe __extract-session-id 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/naps62/tea/rev/scripts/rev-hook-stop.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf idle > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c '[ -n \"$AOE_INSTANCE_ID\" ] || exit 0; command -v aoe >/dev/null 2>&1 || exit 0; aoe __extract-session-id 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf running > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"StopFailure": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf idle > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ElicitationResult": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "sh -c 'unset IFS; set -f; umask 077; [ -n \"$AOE_INSTANCE_ID\" ] || exit 0; case \"$AOE_INSTANCE_ID\" in *[!0-9a-zA-Z_-]*) exit 0 ;; esac; B=/tmp/aoe-hooks-1000; mkdir -p \"$B\" 2>/dev/null || exit 0; LS=$(LC_ALL=C ls -ldn \"$B\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; ME=$(id -u 2>/dev/null) || exit 0; [ \"$3\" = \"$ME\" ] || exit 0; D=\"$B/$AOE_INSTANCE_ID\"; mkdir -p \"$D\" 2>/dev/null; LS=$(LC_ALL=C ls -ldn \"$D\" 2>/dev/null) || exit 0; set -- $LS; M=\"$1\"; case \"$M\" in drwx------|drwx------.|drwx------+|drwx------@) ;; *) exit 0 ;; esac; [ \"$3\" = \"$ME\" ] || exit 0; printf running > \"$D/status\" 2>/dev/null; exit 0 # aoe-hooks'"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"worktree": {
|
||||
"baseRef": "fresh"
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"building@subvisual": true,
|
||||
"rust-analyzer-lsp@claude-plugins-official": false,
|
||||
"caveman@caveman": true
|
||||
},
|
||||
"extraKnownMarketplaces": {
|
||||
"subvisual": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "subvisual/harness"
|
||||
}
|
||||
},
|
||||
"superpowers-marketplace": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "obra/superpowers-marketplace"
|
||||
}
|
||||
},
|
||||
"caveman": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "JuliusBrussee/caveman"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true,
|
||||
"theme": "dark",
|
||||
"editorMode": "vim",
|
||||
"preferredNotifChannel": "auto",
|
||||
"autoCompactEnabled": true,
|
||||
"remoteControlAtStartup": false,
|
||||
"inputNeededNotifEnabled": true,
|
||||
"mcpServers": {
|
||||
"playwright": {
|
||||
"args": [
|
||||
"-y",
|
||||
"@playwright/mcp@latest"
|
||||
],
|
||||
"command": "npx",
|
||||
"type": "stdio"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
+17
-31
@@ -4,14 +4,12 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
claudeSettings = "home/yolo/claude-settings.json";
|
||||
|
||||
# ./claude-settings.json holds only what yolo overrides; everything else is
|
||||
# inherited so common changes reach this host. Attrsets merge key-by-key,
|
||||
# lists are replaced whole (permissions.allow is yolo's, not a union).
|
||||
mergedClaudeSettings = (pkgs.formats.json { }).generate "claude-settings.json" (
|
||||
lib.recursiveUpdate (lib.importJSON ../common/programs/claude/settings.json) (
|
||||
lib.importJSON ./claude-settings.json
|
||||
# ./opencode.json holds only yolo's overrides; everything else is inherited
|
||||
# so agents, commands and skills keep coming from common. Attrsets merge
|
||||
# key-by-key, lists are replaced whole.
|
||||
mergedOpencodeConfig = (pkgs.formats.json { }).generate "opencode.json" (
|
||||
lib.recursiveUpdate (lib.importJSON ../common/programs/opencode/opencode.json) (
|
||||
lib.importJSON ./opencode.json
|
||||
)
|
||||
);
|
||||
in
|
||||
@@ -23,12 +21,20 @@ in
|
||||
../common/programs/kitty
|
||||
../common/programs/gpg.nix
|
||||
../common/programs/aoe
|
||||
../common/programs/maestro
|
||||
../common/programs/rev
|
||||
../common/programs/nix-autodeploy
|
||||
../common/features/xdg.nix
|
||||
./monitors.nix
|
||||
./services.nix
|
||||
./ssh.nix
|
||||
];
|
||||
|
||||
# Host-local: bash goes from "ask" to "allow" so unattended opencode sessions
|
||||
# stop stalling on every git and grep.
|
||||
# It also drops the prompt on branches under review, which is the tradeoff.
|
||||
xdg.configFile."opencode/opencode.json".source = lib.mkForce mergedOpencodeConfig;
|
||||
|
||||
custom.hyprland.cursorSize = 32;
|
||||
|
||||
# Amber, and a different silhouette to Nordzy — this desktop is only ever seen
|
||||
@@ -40,29 +46,9 @@ in
|
||||
light = "Bibata-Modern-Amber";
|
||||
};
|
||||
|
||||
home = {
|
||||
# Headless browser driver the agent tooling shells out to. Was a global npm
|
||||
# install on the ubuntu box.
|
||||
packages = [ pkgs.agent-browser ];
|
||||
|
||||
# Both default to ~/projects/nixos-config in common/programs; this clone
|
||||
# lives under ~/tea. nh.flake sets NH_FLAKE, so without it `nh home switch`
|
||||
# with no argument resolves to a path that does not exist.
|
||||
mutableFilesRepoPath = "/home/naps62/tea/nixos-config";
|
||||
|
||||
mutableFiles = {
|
||||
# Host-local, not shared: this sets yolo_mode_default = true, which starts
|
||||
# aoe sessions with permission checks skipped. Only correct on this box.
|
||||
".config/agent-of-empires/config.toml".source = ./aoe-config.toml;
|
||||
|
||||
# Likewise host-local: carries skipDangerousModePermissionPrompt and the
|
||||
# rev hook paths, neither of which belong on a workstation.
|
||||
".claude/settings.json" = {
|
||||
source = lib.mkForce mergedClaudeSettings;
|
||||
upstreamPath = claudeSettings;
|
||||
};
|
||||
};
|
||||
};
|
||||
# Headless browser driver the agent tooling shells out to. Was a global npm
|
||||
# install on the ubuntu box.
|
||||
home.packages = [ pkgs.agent-browser ];
|
||||
|
||||
# This is the one box that runs the agent-skills units; each starts a session,
|
||||
# so a second machine enabling them would run the same job twice.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"permission": {
|
||||
"bash": "allow"
|
||||
}
|
||||
}
|
||||
+55
-57
@@ -3,12 +3,11 @@
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
# The user services this box exists to run, ported from hand-written units in
|
||||
# ~/.config/systemd/user on the Ubuntu machine.
|
||||
# The user services this box exists to run.
|
||||
#
|
||||
# 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.
|
||||
# maestro and rev come from their own flakes, so nix owns the build as well as
|
||||
# the unit. aoe-web is still the odd one out: its unit is defined here and the
|
||||
# binary comes from the flake input.
|
||||
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.
|
||||
@@ -19,76 +18,75 @@ let
|
||||
aoe = inputs.agent-of-empires.packages.${pkgs.system}.aoe-with-web;
|
||||
in
|
||||
{
|
||||
# sem is here as well as on rev's unit: the shell uses it directly too.
|
||||
home.packages = [
|
||||
sem
|
||||
pkgs.bun
|
||||
# ACP adapter aoe's structured (web) sessions spawn as `claude-agent-acp`.
|
||||
pkgs.claude-agent-acp
|
||||
];
|
||||
|
||||
# maestro's and rev's units come from their flake modules, not from the
|
||||
# hand-written set below.
|
||||
#
|
||||
# settings stays empty on purpose: ~/.config/maestro/config.toml is a
|
||||
# hand-edited plain file, not nix-generated (module skips it iff `{}`).
|
||||
# There is no `web.enable`: the daemon serves the UI on `daemon.bind_addr`.
|
||||
services.maestro.enable = true;
|
||||
|
||||
# Everything under ~, three levels deep — the worktrees live at
|
||||
# ~/<area>/<repo>/worktrees/<name>. sem gives entity-level diffs; without it
|
||||
# rev falls back to line diffs.
|
||||
services.rev = {
|
||||
enable = true;
|
||||
roots = [ "%h" ];
|
||||
depth = 3;
|
||||
semBin = "${sem}/bin/sem";
|
||||
};
|
||||
|
||||
# One endpoint for every repo this config pins. All three apply straight
|
||||
# away: a maestro restart drops the daemon but not the sessions it manages,
|
||||
# so the interactive shells on this box survive it.
|
||||
services.nixAutodeploy = {
|
||||
enable = true;
|
||||
flake = "/home/naps62/tea/nixos-config";
|
||||
environmentFile = "%h/.config/nix-autodeploy/env";
|
||||
repos = {
|
||||
"yolo/rev".input = "rev";
|
||||
"yolo/agent-skills".input = "agent-skills";
|
||||
"naps62/maestro".input = "maestro";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.services = {
|
||||
rev = {
|
||||
aoe-web = {
|
||||
Unit = {
|
||||
Description = "rev — always-on local code review server";
|
||||
Description = "aoe serve — Agent of Empires web dashboard";
|
||||
After = [ "network.target" ];
|
||||
# MUST stay 0: at RestartSec=2 a fast-crashing rev burns the default
|
||||
# MUST stay 0: at RestartSec=2 a fast-crashing aoe burns the default
|
||||
# 5-starts-per-10s budget, and systemd parks the unit in `failed` until
|
||||
# a manual `systemctl --user reset-failed`.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
WorkingDirectory = "%h/tea/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";
|
||||
WorkingDirectory = "%h";
|
||||
# The fork removed dashboard auth entirely, so there is no --auth flag
|
||||
# any more — the reverse proxy is the only access gate. --allowed-host
|
||||
# is what makes the rebinding gate accept a hostname under a wildcard
|
||||
# bind (an IP literal needs no flag).
|
||||
ExecStart = "${aoe}/bin/aoe serve --host 0.0.0.0 --port 8080 --behind-proxy --allowed-host aoe.n62.casa";
|
||||
# TMUX_TMPDIR keeps the daemon on the same tmux server the shell and
|
||||
# TUI use, instead of a second one under /tmp (same bug pr-daemon had).
|
||||
Environment = [
|
||||
"NODE_ENV=production"
|
||||
"REV_ROOTS=%h"
|
||||
"REV_DEPTH=3"
|
||||
"REV_SEM_BIN=${sem}/bin/sem"
|
||||
"PATH=${toolPath}"
|
||||
"TMUX_TMPDIR=%t"
|
||||
];
|
||||
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" ];
|
||||
# Same restart-budget trap as `rev` above.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
WorkingDirectory = "%h/tea/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" ];
|
||||
};
|
||||
|
||||
aoe-web = {
|
||||
Unit = {
|
||||
Description = "aoe serve — Agent of Empires web dashboard";
|
||||
After = [ "network.target" ];
|
||||
# Same restart-budget trap as `rev` above.
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
WorkingDirectory = "%h";
|
||||
# aoe refuses `--auth none` on a non-loopback bind unless --behind-proxy
|
||||
# is set; --allowed-host is what makes the rebinding gate accept a
|
||||
# hostname under a wildcard bind (an IP literal needs no flag).
|
||||
ExecStart = "${aoe}/bin/aoe serve --host 0.0.0.0 --port 8080 --auth none --behind-proxy --allowed-host aoe.n62.casa";
|
||||
Environment = [ "PATH=${toolPath}" ];
|
||||
Restart = "always";
|
||||
RestartSec = 2;
|
||||
# If this unit boots before any shell, the shared tmux server lands in
|
||||
# its cgroup; the default control-group kill would take every session
|
||||
# down on restart.
|
||||
KillMode = "process";
|
||||
};
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
share = "media";
|
||||
mountPoint = "/mnt/media";
|
||||
}
|
||||
{
|
||||
# yolo VM's /home/naps62 (share defined in hosts/yolo/default.nix).
|
||||
server = "10.7.10.2";
|
||||
share = "home";
|
||||
mountPoint = "/mnt/yolo";
|
||||
}
|
||||
];
|
||||
|
||||
boot.kernelParams = [
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{ inputs, lib, ... }:
|
||||
{
|
||||
imports = [ inputs.nix-flatpak.nixosModules.nix-flatpak ];
|
||||
|
||||
services.flatpak = {
|
||||
enable = true;
|
||||
|
||||
# mkOptionDefault so the module's default `flathub` remote is kept rather
|
||||
# than replaced.
|
||||
remotes = lib.mkOptionDefault [
|
||||
{
|
||||
name = "flathub-beta";
|
||||
location = "https://flathub.org/beta-repo/flathub-beta.flatpakrepo";
|
||||
}
|
||||
];
|
||||
|
||||
packages = [
|
||||
# PrusaSlicer 3.x. As of 3.0 Prusa ships no Linux binary at all — Flathub
|
||||
# is the only channel, and the alphas live in flathub-beta. Runs alongside
|
||||
# pkgs.prusa-slicer (2.9.x, in home/common/programs/3d.nix): 3.x keeps its
|
||||
# profiles in a separate PrusaSlicer3-dev config dir.
|
||||
{
|
||||
appId = "com.prusa3d.PrusaSlicer";
|
||||
origin = "flathub-beta";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -40,7 +40,10 @@ in
|
||||
};
|
||||
mountPoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Local mount point, e.g. \"/mnt/retroarch\".";
|
||||
# MUST NOT live under $HOME: starship and eww stat every entry of the
|
||||
# home dir, so a mount point there triggers the automount on every
|
||||
# prompt and blocks for mount-timeout while the VPN is down.
|
||||
description = "Local mount point outside $HOME, e.g. \"/mnt/retroarch\".";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
|
||||
@@ -20,12 +20,17 @@
|
||||
../common/features/wine.nix
|
||||
../common/features/gaming
|
||||
../common/features/appimage.nix
|
||||
../common/features/flatpak.nix
|
||||
../common/features/smb-mounts.nix
|
||||
../common/features/home
|
||||
];
|
||||
|
||||
networking.hostName = "konishi";
|
||||
|
||||
# Set here rather than in hardware-configuration.nix so it survives a
|
||||
# nixos-generate-config regeneration; the list merges with that file's.
|
||||
fileSystems."/".options = [ "noatime" ];
|
||||
|
||||
# NAS media share — reachable only over the wg-home VPN. Lazy automount, so it
|
||||
# never blocks boot and (re)mounts on first access once the VPN is up.
|
||||
custom.smbMounts = [
|
||||
@@ -34,6 +39,12 @@
|
||||
share = "media";
|
||||
mountPoint = "/mnt/media";
|
||||
}
|
||||
{
|
||||
# yolo VM's /home/naps62 (share defined in hosts/yolo/default.nix).
|
||||
server = "10.7.10.2";
|
||||
share = "home";
|
||||
mountPoint = "/mnt/yolo";
|
||||
}
|
||||
];
|
||||
|
||||
# Arm Wake-on-LAN (magic packet) on the Intel igc NIC and re-apply it on every
|
||||
|
||||
+37
-4
@@ -30,11 +30,20 @@
|
||||
# this VM has a real 8G swap partition, and the host manages its own memory.
|
||||
zramSwap.enable = lib.mkForce false;
|
||||
|
||||
# The only out-of-band way in. `vga: virtio-gl` renders through a GL context
|
||||
# with no QEMU console surface, so noVNC and screendump both go dark; this
|
||||
# pairs with the VM's serial0 socket to keep `qm terminal` working. Listed
|
||||
# last so it wins /dev/console and gets the getty.
|
||||
boot.kernelParams = [
|
||||
# When the host pages this VM's memory out, KVM's async page fault parks
|
||||
# the faulting task in an uninterruptible wait instead of stalling the
|
||||
# whole vCPU. If the "page ready" wakeup is ever dropped, that task is
|
||||
# wedged forever and SIGKILL cannot touch it — it took out maestro-web,
|
||||
# nix activation generators and a dozen agent sessions, a few per day,
|
||||
# until a reboot. Disabling async PF makes host page-ins stall the vCPU
|
||||
# synchronously: slower under host memory pressure, but nothing hangs.
|
||||
"no-kvmapf"
|
||||
|
||||
# The only out-of-band way in. `vga: virtio-gl` renders through a GL
|
||||
# context with no QEMU console surface, so noVNC and screendump both go
|
||||
# dark; this pairs with the VM's serial0 socket to keep `qm terminal`
|
||||
# working. Listed last so it wins /dev/console and gets the getty.
|
||||
"console=tty1"
|
||||
"console=ttyS0,115200"
|
||||
];
|
||||
@@ -58,6 +67,30 @@
|
||||
# virtio drivers; the agent itself is a separate service.
|
||||
qemuGuest.enable = true;
|
||||
|
||||
# Exposes /home/naps62 to arrakis/konishi (custom.smbMounts on both, mounted
|
||||
# at ~/yolo). Guest auth — the home dir holds SSH keys and credentials, so
|
||||
# the only guard is `hosts allow` limiting clients to the wireguard subnet
|
||||
# (desktops connect from wg addresses, 10.10.*).
|
||||
# Clients address this VM as 10.7.10.2; keep that DHCP lease reserved.
|
||||
samba = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
settings = {
|
||||
global = {
|
||||
"map to guest" = "bad user";
|
||||
"hosts allow" = "10.10.0.0/16 127.0.0.1";
|
||||
"hosts deny" = "ALL";
|
||||
};
|
||||
home = {
|
||||
path = "/home/naps62";
|
||||
"read only" = "no";
|
||||
"guest ok" = "yes";
|
||||
"force user" = "naps62";
|
||||
"force group" = "users";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user