Compare commits

6 Commits

Author SHA1 Message Date
Miguel Palhas fda1bf2985 Merge remote-tracking branch 'origin/main' into eww-shell
CI / lint (pull_request) Successful in 30s
CI / eval (pull_request) Successful in 2m16s
2026-08-19 15:11:45 +01:00
Miguel Palhas 3e3c7f1b57 fix: match the real blueman window class
CI / lint (pull_request) Successful in 28s
CI / eval (pull_request) Successful in 1m48s
nixpkgs wraps blueman with --inherit-argv0, so the class is blueman-manager,
not the .blueman-manager-wrapped the rule expected. It never floated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:31:29 +01:00
Miguel Palhas 8e5feb8522 fix: make the audio sliders actually set volume
CI / lint (pull_request) Successful in 29s
CI / eval (pull_request) Successful in 1m48s
yuck interpolates ${...}, not {...}, so wpctl was being handed a literal
"{node}" and failing; the slider snapped back on the next poll.

Also drops the full-screen backdrop. It gave click-outside-to-close, but its
handler fired for clicks on the sliders too, closing the overlay mid-drag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:27:53 +01:00
Miguel Palhas db0e1aea32 feat: make the panel a centered dashboard with audio
CI / lint (pull_request) Successful in 29s
CI / eval (pull_request) Successful in 1m53s
Reshapes the corner strip into a centered card grid over a dimmed backdrop,
which also gives it click-outside-to-close.

Adds an Audio card: speaker and mic sliders driven by wpctl, each with a mute
toggle. Rows hide themselves when the node does not exist, same as battery.

custom.hyprland.panelScale multiplies every length in the stylesheet; konishi
sets 1.6 for its 4K outputs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:22:17 +01:00
Miguel Palhas 4a8e0241e4 fix: detach the panel's launched apps from eww
CI / lint (pull_request) Successful in 38s
CI / eval (pull_request) Successful in 2m10s
eww kills the onclick handler's process group when the window it came from
closes, so `eww close panel; <app>` never reached the app. Launch through
setsid first, close the panel after.

loginctl was also unqualified, which a systemd user unit's PATH does not cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 13:33:39 +01:00
Miguel Palhas 39cc3f2cd0 feat: replace noctalia with an eww panel
CI / lint (pull_request) Successful in 31s
CI / eval (pull_request) Successful in 2m19s
Drops the noctalia shell and its flake input. It was doing six jobs; each is
now a small binary-cached package, so nothing builds from source:

- control centre -> an eww overlay, SUPER+N, opening on the focused monitor
- launcher       -> fuzzel, SUPER+space
- wallpaper      -> wpaperd, with waypaper as the picker
- notifications  -> mako
- polkit agent   -> hyprpolkitagent
- qt/zathura theming -> static dark colours instead of generated ones

The panel shows clock, battery (hidden where there is none), network,
bluetooth, plus buttons for next/pick wallpaper, bluetooth power and lock.

custom.hyprland.verticalOutputs replaces noctalia's per-monitor wallpaper
handling; konishi sets it to DP-2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 13:24:55 +01:00
42 changed files with 1142 additions and 949 deletions
Generated
+124 -227
View File
@@ -7,26 +7,27 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"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"
"lastModified": 1786554876,
"narHash": "sha256-akMknOIdX0QsBsM4lgreXSNNym7TNu9iOrDBqrQuTuQ=",
"owner": "agent-of-empires",
"repo": "agent-of-empires",
"rev": "9b0d691a6cdaa6d2451f6eda05e83628c54eb95a",
"type": "github"
},
"original": {
"type": "git",
"url": "https://git.naps.pt/yolo/agent-of-empires.git"
"owner": "agent-of-empires",
"ref": "v1.14.1",
"repo": "agent-of-empires",
"type": "github"
}
},
"agent-skills": {
"locked": {
"lastModified": 1787576215,
"narHash": "sha256-x1ftVVEcq8p6phZEl4NC4F3RcPqpgx6wEMODMI5QHN8=",
"lastModified": 1786957004,
"narHash": "sha256-lUyQGAN6Kd5onhPa+ptOiwtIaY6Y02q+nyRt9NWkdDg=",
"ref": "refs/heads/main",
"rev": "0900e5fea1245c7d5e962e48a809ff7481c87f95",
"revCount": 99,
"rev": "7cb03f879600e87281356fdc411b2690f09a9175",
"revCount": 47,
"type": "git",
"url": "https://git.naps.pt/yolo/agent-skills.git"
},
@@ -55,11 +56,11 @@
]
},
"locked": {
"lastModified": 1786464219,
"narHash": "sha256-WKqWL8r7CyTDYueTr2ffJ9ya50dellv6IR1JX5PghDY=",
"lastModified": 1784368054,
"narHash": "sha256-zF1iJkBQSDWmRO4/LEeHR1SpKY0lqZaxkoQJpPS9K9U=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "f3d1804205e8158c15595cdda1b566f93349ffae",
"rev": "9b5f14d9483445e766294eb8fbe0b8f370269ed0",
"type": "github"
},
"original": {
@@ -70,15 +71,15 @@
},
"claude-code": {
"inputs": {
"nixpkgs": "nixpkgs_2",
"systems": "systems"
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1787189302,
"narHash": "sha256-cUM75Rob89qTxJvqVRF8GUewOriqQtLiyiy5xfd1fMM=",
"lastModified": 1785285785,
"narHash": "sha256-r1cMlvXjRVKe+uQ/GKwy4TpionpmbNgksFP2758D/MM=",
"owner": "sadjow",
"repo": "claude-code-nix",
"rev": "8fb8cbab6345583e0b68ca7dbbbad08a0a348145",
"rev": "da78262708d858861afbe1f68ea65fedda4054c4",
"type": "github"
},
"original": {
@@ -89,17 +90,17 @@
},
"codex-cli": {
"inputs": {
"flake-utils": "flake-utils",
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1787094470,
"narHash": "sha256-UiHfeA8umiWByi/ijDnfnlGcVSEAhwNv1Gue3h6ey/U=",
"lastModified": 1785294467,
"narHash": "sha256-mylUWHK2wIw8S07K5hbkrLELnflJkDwsSWz2YFbJzLI=",
"owner": "sadjow",
"repo": "codex-cli-nix",
"rev": "cfbeeca2f4dfbbfcc3c1a5b21e61a051ff43533d",
"rev": "e4e3b0672bbb8fba7f32fe53cd9c604990970374",
"type": "github"
},
"original": {
@@ -111,17 +112,17 @@
},
"codex-desktop-linux": {
"inputs": {
"flake-utils": "flake-utils_2",
"flake-utils": "flake-utils_3",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1787079200,
"narHash": "sha256-BYo8mVEqRj6y8PzpFd3rX6TQTknB3Ynp/3ykMTm56D8=",
"lastModified": 1786822283,
"narHash": "sha256-RdoFKwNQ2m2zbHnq/QbgLf2lUI9cohsnlMwLfJuaTh0=",
"owner": "ilysenko",
"repo": "codex-desktop-linux",
"rev": "1875dc2eaab6f9448617d18d0eda58902edf0f1a",
"rev": "e6b51d96ac2b9b5d9adafa852241bd0982972557",
"type": "github"
},
"original": {
@@ -145,21 +146,6 @@
"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",
@@ -219,24 +205,6 @@
"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=",
@@ -253,7 +221,7 @@
},
"flake-utils": {
"inputs": {
"systems": "systems_2"
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
@@ -271,7 +239,7 @@
},
"flake-utils_2": {
"inputs": {
"systems": "systems_3"
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
@@ -288,6 +256,24 @@
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"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"
}
},
"flake-utils_4": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
@@ -302,7 +288,7 @@
"type": "github"
}
},
"flake-utils_4": {
"flake-utils_5": {
"inputs": {
"systems": "systems_8"
},
@@ -322,17 +308,17 @@
},
"foundry": {
"inputs": {
"flake-utils": "flake-utils_3",
"flake-utils": "flake-utils_4",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1787123762,
"narHash": "sha256-gKUrkWZRS2BxEcJDyVSBZJ3fi1rN51PPe8U5hCjFNIY=",
"lastModified": 1785311662,
"narHash": "sha256-lC5ZlyNMf8wcQnrAzUhYb1p6ndzwEovnXKiMqSTLgUo=",
"owner": "shazow",
"repo": "foundry.nix",
"rev": "a194d90ec9f6a4d70d79220e644c531da8324266",
"rev": "0eb3d1233237eb4e6f4f45f31c087ee1a16245f1",
"type": "github"
},
"original": {
@@ -348,11 +334,11 @@
]
},
"locked": {
"lastModified": 1787144466,
"narHash": "sha256-HHfv2/HkNSKbbSyU9iD/g8lbP6r4tl33sSw1W4rXCk0=",
"lastModified": 1785232496,
"narHash": "sha256-65EQYIRRpTdpH8lUiB6Mvo5uBkG60aBIzAJuALfx+O0=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "0471accf8d0a8210b31d947497d179ecc99e0021",
"rev": "2e790b0a6be8ec2b76174ac0931b8ff11919ec98",
"type": "github"
},
"original": {
@@ -369,11 +355,11 @@
]
},
"locked": {
"lastModified": 1787176219,
"narHash": "sha256-djoRr6jBpe35q/0JwAvXpFXg1Ktf+X54NU2RLR7wnHw=",
"lastModified": 1785306346,
"narHash": "sha256-DScBkW0fOgpGPK2trNoX3ryLTlaC14+gglFo/BhGJ4g=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "c53d643b3737e2fcd04e6cb3b3580ef50b2087a0",
"rev": "e705714e918c3b11affcdd15db2cbe3a070420a0",
"type": "github"
},
"original": {
@@ -390,11 +376,11 @@
]
},
"locked": {
"lastModified": 1786719456,
"narHash": "sha256-B74DLQs/VjlqyhnnX3tguWwghqJHWSJX30TKZuAljIg=",
"lastModified": 1783963347,
"narHash": "sha256-r376E2XpakiXwModDHIxlvB6qLq4iFVEq730vxOO4JY=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "83b7606dcf44abe3a94b86e8bb2b3355d22e8797",
"rev": "a45a7c451455a51ae740ec3bce4024b312809c29",
"type": "github"
},
"original": {
@@ -419,11 +405,11 @@
]
},
"locked": {
"lastModified": 1786464181,
"narHash": "sha256-2alOMkLjXANh7unkZnYnCF2K2rApZaOLMoQ3o+VX2CY=",
"lastModified": 1776511930,
"narHash": "sha256-fCpwFiTW0rT7oKJqr3cqHMnkwypSwQKpbtUEtxdkgrM=",
"owner": "hyprwm",
"repo": "hyprcursor",
"rev": "e4ed7c08123df5af460a0a70961380cbfb872f76",
"rev": "39435900785d0c560c6ae8777d29f28617d031ef",
"type": "github"
},
"original": {
@@ -448,11 +434,11 @@
]
},
"locked": {
"lastModified": 1786464367,
"narHash": "sha256-k58p4wbzIXWyRWrW84pP8tD+iaZSSYiiM+fr0Auk4oU=",
"lastModified": 1782566056,
"narHash": "sha256-haEZcHzYrePnjFOYSWTbxm/Nrla0aPslJfmvdCvqtVc=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "7c895c44e3ca6d28ed68ddd80ec02b02b925e7fc",
"rev": "c6e7b9f673f4360bc813d3dc75028f75ee88d3f8",
"type": "github"
},
"original": {
@@ -478,11 +464,11 @@
"xdph": "xdph"
},
"locked": {
"lastModified": 1787138659,
"narHash": "sha256-/3fIl+/xZoZqliAfwYB8ahCZK04Qr/zwdOQgiR41dy8=",
"lastModified": 1785334807,
"narHash": "sha256-bN0KRhDfwSPqbpgUgWVcQMkuDje+8ghWlm7uUQZzaxc=",
"ref": "refs/heads/main",
"rev": "52b368f1d7fdee1b0e96ef3dc655b5a577df4fdb",
"revCount": 7745,
"rev": "7803e68638c1bebce57cf9ea8da51fd9a1e19edd",
"revCount": 7669,
"submodules": true,
"type": "git",
"url": "https://github.com/hyprwm/Hyprland"
@@ -526,11 +512,11 @@
]
},
"locked": {
"lastModified": 1786464504,
"narHash": "sha256-7sHwM86KILQyHDHDuE2SDBlQ2jvZ0EW3hY7sW009/cg=",
"lastModified": 1784196523,
"narHash": "sha256-ahtKMGXFJdlQNhatQm1+BBU/pGfGYnAqQt3vWvq4p8s=",
"owner": "hyprwm",
"repo": "hyprland-guiutils",
"rev": "4c30cf3097ea963c0e250749ee0c59f8b08816d6",
"rev": "a6ccb6cb112ed5a244c0191fb972347ecfa893e0",
"type": "github"
},
"original": {
@@ -580,11 +566,11 @@
]
},
"locked": {
"lastModified": 1786464129,
"narHash": "sha256-339AkTlpMYSIvFuG0rnR+8Yg4/AZKeJalshJavlnKfg=",
"lastModified": 1777320127,
"narHash": "sha256-Qu+Wf2Bp5qUjyn2YpZNq8a7JyzTGowhT1knrwE38a9U=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "9508458be316a0d70d37ebed1ab725ccd10411ff",
"rev": "090117506ddc3d7f26e650ff344d378c2ec329cc",
"type": "github"
},
"original": {
@@ -654,11 +640,11 @@
]
},
"locked": {
"lastModified": 1785930473,
"narHash": "sha256-DitTu625BhEYpZjtjxtGpjrEJwPwW+X/+jJvhSZNSJM=",
"lastModified": 1782554491,
"narHash": "sha256-+p3MlyN/nqRefcf2IckPlGRUn9+hielqpS9XClbLleM=",
"owner": "hyprwm",
"repo": "hyprtoolkit",
"rev": "af515b69dfbe366dc7873aa1475cb2f4db3ebad7",
"rev": "bdba25ced39ea39ab004a8f31593ba0b0ff1ca35",
"type": "github"
},
"original": {
@@ -679,11 +665,11 @@
]
},
"locked": {
"lastModified": 1786464080,
"narHash": "sha256-W1hxvumEM57yV+QwsZ4QdAHEqOkr7e4S8VAkLX60qDE=",
"lastModified": 1784323413,
"narHash": "sha256-XnAVV+H4f8Xdv0yZcSwJ5kCjLyE8fHxPeLX6a3HSrAU=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "c157fe1e3092b980cc69315a6631f89aff09dcce",
"rev": "5f03477ab3a005ff27c527486f551883535aea2f",
"type": "github"
},
"original": {
@@ -704,11 +690,11 @@
]
},
"locked": {
"lastModified": 1786464033,
"narHash": "sha256-QM8Qe4/L8lpdVN4bgwahmi+jyyc4fisseDMe4afcDxA=",
"lastModified": 1777159683,
"narHash": "sha256-Jxixw6wZphUp+nHYxOKUYSckL17QMBx2d5Zp0rJHr1g=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "62e62c1ca23da17612c6890d4ad2064f575643db",
"rev": "b8632713a6beaf28b56f2a7b0ab2fb7088dbb404",
"type": "github"
},
"original": {
@@ -733,11 +719,11 @@
]
},
"locked": {
"lastModified": 1786464294,
"narHash": "sha256-ZQsZ2WvBdkboCIyh8LStDPdAIARmxzn0XMNxxoOhjPE=",
"lastModified": 1778410714,
"narHash": "sha256-o6RzFj4nJXaPRY7EM01siuCQeT41RfwwmcmFQqwFJJg=",
"owner": "hyprwm",
"repo": "hyprwire",
"rev": "4ce7cd6b6128c1ac41caf23c58a30a26b327f9dd",
"rev": "85148a8e612808cf5ddb25d0b3c5840f3498a7dc",
"type": "github"
},
"original": {
@@ -746,27 +732,6 @@
"type": "github"
}
},
"maestro": {
"inputs": {
"crane": "crane_2",
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_6",
"process-compose-flake": "process-compose-flake"
},
"locked": {
"lastModified": 1787610419,
"narHash": "sha256-YRDvlaiEOpzRC5JHMlm/S39EGfrGsD4kmEWwnyHz4Tg=",
"ref": "refs/heads/main",
"rev": "80a93999051d41b29544258dca272d40f3fc0c98",
"revCount": 443,
"type": "git",
"url": "https://git.naps.pt/naps62/maestro.git"
},
"original": {
"type": "git",
"url": "https://git.naps.pt/naps62/maestro.git"
}
},
"nix-index-database": {
"inputs": {
"nixpkgs": [
@@ -774,11 +739,11 @@
]
},
"locked": {
"lastModified": 1786852476,
"narHash": "sha256-IM5CYtf86W4w8eUPpKcY/LpdHElmVBtJhaKnoTKxZEA=",
"lastModified": 1785046085,
"narHash": "sha256-UiK+mmZJuLWQVhJ5b2wDzogIYWAesyRm6LA3h3Ulh3Y=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "c7962dc97b45129df8d751bedaf37beb5a17706e",
"rev": "11665045df8b9938ef811a3bfdc65cffb02b4b70",
"type": "github"
},
"original": {
@@ -819,21 +784,6 @@
}
},
"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=",
@@ -850,11 +800,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1787111413,
"narHash": "sha256-sFosWtq21eHGJRnTc/hvf4M1obRgLEUMNm/IzllkHMA=",
"lastModified": 1784783405,
"narHash": "sha256-4IHyyLgLBdKefkljdKod4IMn023pQiDXAWJA187cmdY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "afe3d8ac4395617bdcdac9f188ac8717a062e014",
"rev": "7525d999cd850b9a488817abc89c75dc733acf17",
"type": "github"
},
"original": {
@@ -898,11 +848,11 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1786247143,
"narHash": "sha256-8S3Kcxs7D4UtxJxSJZz0m14CGhuW0MxfrIwJxeGWGnQ=",
"lastModified": 1784356753,
"narHash": "sha256-12KrbMiWLcf8m7pCvAtZh1ZrgF85ZXDXvfR/fWTKy84=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "279b4a8275f032c566576b3f181fa0f27197f588",
"rev": "61b7c44c4073f0b827768aff0049561b5110ea5a",
"type": "github"
},
"original": {
@@ -914,37 +864,21 @@
},
"nixpkgs_6": {
"locked": {
"lastModified": 1787135253,
"narHash": "sha256-RD2kNWCG+Bjo6h+JVjWVNntZs2GtRoeY2xHjts/FNkA=",
"owner": "NixOS",
"lastModified": 1785090369,
"narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
"rev": "624af665418d3c65d544145b4d34ad696439570e",
"type": "github"
},
"original": {
"owner": "NixOS",
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_7": {
"locked": {
"lastModified": 1787070829,
"narHash": "sha256-vXNVDVtvfiQuXthP0NHPFdNvvMTkGpx0UP8oddIWbNk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0ae2bc1419c3f345984c2629e72e7a631820fa4d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_8": {
"locked": {
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
@@ -960,7 +894,7 @@
"type": "github"
}
},
"nixpkgs_9": {
"nixpkgs_8": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
@@ -1002,11 +936,11 @@
]
},
"locked": {
"lastModified": 1786797655,
"narHash": "sha256-qAWI5Vf0xaRmb8/l9XWYrvEgrIaqKBv8Dg81JO7wkrY=",
"lastModified": 1785121894,
"narHash": "sha256-AWbstkwe4twd+5iWx8QBVzTgwGAUvn4vVdobGKW5BRc=",
"owner": "nix-community",
"repo": "nix4nvchad",
"rev": "b878640ae13c8f70f41aa1159234206051a4378c",
"rev": "8135d402aa554b5f8e5dd2ec341c5a440fd18496",
"type": "github"
},
"original": {
@@ -1037,41 +971,6 @@
"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": 1787648458,
"narHash": "sha256-YO8lx85XorD2cnq5QNBvpj3JH60OrDV04GA3R2Oaih0=",
"ref": "refs/heads/main",
"rev": "88bf55a737046768adcea15b2475096519341494",
"revCount": 234,
"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",
@@ -1084,12 +983,10 @@
"hardware": "hardware",
"home-manager": "home-manager",
"hyprland": "hyprland",
"maestro": "maestro",
"nix-index-database": "nix-index-database",
"nixpkgs": "nixpkgs_7",
"nixpkgs": "nixpkgs_6",
"nvchad-starter": "nvchad-starter",
"nvchad4nix": "nvchad4nix",
"rev": "rev",
"rose-pine-hyprcursor": "rose-pine-hyprcursor",
"sem": "sem",
"spicetify-nix": "spicetify-nix",
@@ -1139,7 +1036,7 @@
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_9"
"nixpkgs": "nixpkgs_8"
},
"locked": {
"lastModified": 1775445266,
@@ -1157,9 +1054,9 @@
},
"sem": {
"inputs": {
"flake-parts": "flake-parts_3",
"flake-utils": "flake-utils_4",
"nixpkgs": "nixpkgs_8",
"flake-parts": "flake-parts_2",
"flake-utils": "flake-utils_5",
"nixpkgs": "nixpkgs_7",
"rust-overlay": "rust-overlay"
},
"locked": {
@@ -1184,11 +1081,11 @@
"systems": "systems_9"
},
"locked": {
"lastModified": 1786855359,
"narHash": "sha256-yeKMWFCPeoIKmPBLLvP1/15ulOJO8i9ZIlSSXGuW6aw=",
"lastModified": 1785048222,
"narHash": "sha256-LjFVfwxensz76SjnvofF1Jsw+xTLd8Qp31W0J6nh2XI=",
"owner": "Gerg-L",
"repo": "spicetify-nix",
"rev": "0f478ff79b82abb785160cd4531293f61d21be86",
"rev": "17e524f330c282d31c32dfe076222d4a12277886",
"type": "github"
},
"original": {
@@ -1411,11 +1308,11 @@
]
},
"locked": {
"lastModified": 1786464334,
"narHash": "sha256-/TBQT5rhBB2Dm4HoZzhGDaCwYmRTs3W3DPhMXFWc/BU=",
"lastModified": 1784371182,
"narHash": "sha256-S8A1lezEalltWcCp3gAic5lssS0xTSISK6fKODefhOk=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "9f0e9ff02739cd538d39bd706422dc50e9ca60dd",
"rev": "08d99f727944dd15e4740090305e31c5fb92a50a",
"type": "github"
},
"original": {
@@ -1432,11 +1329,11 @@
]
},
"locked": {
"lastModified": 1787160391,
"narHash": "sha256-5dRTU55IlWABqnWiXDgtzrc3wT54nPoZwDQ91VOY4mE=",
"lastModified": 1785224333,
"narHash": "sha256-8DNJUVM10ktK02V9ZIV064CH+fuPF272VCAZBn6arig=",
"owner": "0xc000022070",
"repo": "zen-browser-flake",
"rev": "228a12a50971a115caa1c87b7de3d87b08e35275",
"rev": "5f1a4be4d280e1ae06d61a75775a234b1714da3f",
"type": "github"
},
"original": {
+1 -11
View File
@@ -64,22 +64,12 @@
};
# 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 = "git+https://git.naps.pt/yolo/agent-of-empires.git";
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";
# 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";
};
nix-index-database = {
url = "github:nix-community/nix-index-database";
inputs.nixpkgs.follows = "nixpkgs";
-4
View File
@@ -5,10 +5,6 @@
imports = [
../common/programs/default.nix
../common/programs/desktop
../common/programs/workstation-apps.nix
../common/programs/editors.nix
../common/programs/dev-workstation.nix
../common/programs/ai-gui.nix
../common/programs/zen-browser.nix
../common/programs/hyprland
../common/programs/kitty
+105
View File
@@ -0,0 +1,105 @@
{
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}
'';
};
}
+11 -34
View File
@@ -1,43 +1,20 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.custom.blender;
in
{
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.
home.packages = with pkgs; [
blender
prusa-slicer
];
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.
'';
};
};
fonts.fontconfig.enable = true;
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" ];
};
xdg.desktopEntries.prusaslicer-url-handler = {
name = "PrusaSlicer Protocol Handler";
exec = "prusa-slicer %u";
type = "Application";
noDisplay = true;
mimeType = [ "x-scheme-handler/prusaslicer" ];
};
}
-29
View File
@@ -1,29 +0,0 @@
{
config,
pkgs,
inputs,
lib,
...
}:
let
claude-desktop = pkgs.callPackage ../../../pkgs/claude-desktop/package.nix {
inherit (config.custom.aiApps) deviceScaleFactor;
};
codex-cli = inputs.codex-cli.packages.${pkgs.stdenv.hostPlatform.system}.default;
t3-code = pkgs.callPackage ../../../pkgs/t3-code/package.nix {
inherit (config.custom.aiApps) deviceScaleFactor;
};
in
{
imports = [ inputs.codex-desktop-linux.homeManagerModules.default ];
home.packages = lib.optionals pkgs.stdenv.hostPlatform.isx86_64 [
claude-desktop
t3-code
];
programs.codexDesktopLinux = {
enable = true;
cliPackage = codex-cli;
};
}
+27 -14
View File
@@ -1,9 +1,16 @@
{
config,
pkgs,
inputs,
lib,
...
}:
let
# Official Anthropic Claude desktop app (Linux build, x86_64 only).
claude-desktop = pkgs.callPackage ../../../../pkgs/claude-desktop/package.nix {
inherit (config.custom.aiApps) deviceScaleFactor;
};
in
{
imports = [
inputs.agent-skills.homeModules.default
@@ -19,24 +26,24 @@
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
packages =
with pkgs;
[
inputs.claude-code.packages.${pkgs.stdenv.hostPlatform.system}.default
# sandbox
bubblewrap
socat
libseccomp
# sandbox
bubblewrap
socat
libseccomp
# voice
sox
# voice
sox
# beads
dolt
];
# beads
dolt
]
++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 claude-desktop;
file = {
".default-npm-packages".text = ''
@@ -44,11 +51,17 @@
@beads/bd
'';
".claude/statusline.sh" = {
source = ./statusline.sh;
executable = true;
};
};
mutableFiles.".claude/settings.json".source = ./settings.json;
};
}
+86
View File
@@ -0,0 +1,86 @@
{
"$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
}
+9
View File
@@ -7,5 +7,14 @@ let
codex-cli = inputs.codex-cli.packages.${pkgs.stdenv.hostPlatform.system}.default;
in
{
imports = [ inputs.codex-desktop-linux.homeManagerModules.default ];
# cliPackage only tells the desktop app where to find codex; it does not put
# it on PATH, so the cli needs installing separately.
home.packages = [ codex-cli ];
programs.codexDesktopLinux = {
enable = true;
cliPackage = codex-cli;
};
}
+11
View File
@@ -1,13 +1,16 @@
{
lib,
pkgs,
config,
...
}:
{
imports = [
../features/mutable-file.nix
./zsh.nix
./nix.nix
./neovim
./editors.nix
./rust.nix
./elixir.nix
./nodejs.nix
@@ -27,6 +30,14 @@
};
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";
packages = with pkgs; [
impala
];
username = lib.mkDefault "naps62";
homeDirectory = lib.mkDefault "/home/${config.home.username}";
stateVersion = lib.mkDefault "24.05";
+23 -2
View File
@@ -6,8 +6,7 @@
{
imports = [
./darkman.nix
# mpv is installed by programs.mpv there, not as a bare package here.
../mpv.nix
./spicetify.nix
];
home = {
@@ -15,21 +14,43 @@
# various
google-chrome
thunar
obsidian
mpv
# screen recording — evaluating these
obs-studio
kooha # simple Wayland-native screen+audio recorder
vokoscreen-ng # GUI recorder with webcam overlay
gimp
font-manager
imv
pavucontrol
zathura
libsForQt5.qt5ct
kdePackages.qt6ct
nwg-look
xournalpp
jq
ffmpeg
unzip
# remote desktop
remmina
# networking
networkmanagerapplet
mtr
dnsutils
# communication
slack
ferdium
signal-desktop
zoom-us
# dev tools
yaak
bun
# themes
tela-icon-theme
];
-12
View File
@@ -1,12 +0,0 @@
{ pkgs, ... }:
{
home.packages = with pkgs; [
doctl
awscli2
terraform
bruno
coturn
usbutils
ktlint
];
}
+8
View File
@@ -7,6 +7,7 @@
./codex.nix
./pi.nix
./ralph-claude-code.nix
./t3-code.nix
];
programs = {
@@ -25,12 +26,19 @@
home.packages = with pkgs; [
imagemagick
doctl
awscli2
terraform
inotify-tools
devenv
bruno
sshfs
coturn
file
usbutils
nmap
lsof
ktlint
croc
opencode
process-compose
-3
View File
@@ -19,9 +19,6 @@ tmp/**/*
*.log
*.tmp
# Local git worktree checkouts
worktrees/
# Latex
_inputs.tex
_sections.tex
+11 -16
View File
@@ -10,8 +10,6 @@ let
hyprPkgs = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system};
osd = "${pkgs.swayosd}/bin/swayosd-client";
# xdph runs this instead of hyprland-share-picker (screencopy:custom_picker_binary).
# The contract is just "print [SELECTION] to stdout"; a leading "r" grants a
# restore token. Printing it immediately means the dialog never renders.
@@ -367,21 +365,18 @@ in
hl.dispatch(hl.dsp.window.center())
end)
-- volume / brightness / media (locked so they work on the lock screen;
-- volume and brightness repeat on hold). Everything goes through
-- swayosd-client, which applies the change and draws the OSD popup;
-- store paths because Hyprland execs these with the login PATH, which
-- doesn't pick up profile changes made after the session started.
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("${osd} --output-volume raise"), { locked = true, repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("${osd} --output-volume lower"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("${osd} --brightness raise"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("${osd} --brightness lower"), { locked = true, repeating = true })
-- volume / brightness (locked so they work on the lock screen; repeat on hold)
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl set 5%+"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl set 5%-"), { locked = true, repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("${osd} --output-volume mute-toggle"), { locked = true })
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("${osd} --input-volume mute-toggle"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("${osd} --playerctl next"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("${osd} --playerctl prev"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("${osd} --playerctl play-pause"), { locked = true })
-- media keys (locked)
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("pactl set-sink-mute @DEFAULT_SINK@ toggle"), { locked = true })
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("volumectl -m toggle-mute"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
-- move/resize windows with mod + LMB/RMB drag
hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
-36
View File
@@ -44,41 +44,5 @@
};
};
# swayosd: the on-screen volume/brightness/caps-lock popup. Noctalia drew this
# before; without it the XF86 keys still work but give no feedback, which reads
# as "the keys are dead". swayosd-client does the change *and* the popup, so the
# binds in default.nix call it instead of wpctl/brightnessctl directly.
services.swayosd = {
enable = true;
topMargin = 0.85;
stylePath = pkgs.writeText "swayosd-style.css" ''
window#osd {
padding: 12px 20px;
border-radius: 12px;
border: 1px solid #4c566a;
background: alpha(#2e3440, 0.94);
}
window#osd #container { margin: 12px; }
window#osd image,
window#osd label { color: #eceff4; }
window#osd progressbar:disabled { opacity: 0.5; }
window#osd trough {
min-height: 6px;
border-radius: 6px;
background: #434c5e;
}
window#osd progress {
min-height: 6px;
border-radius: 6px;
background: #88c0d0;
}
'';
};
services.hyprpolkitagent.enable = true;
}
-7
View File
@@ -1,7 +0,0 @@
{ 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 ];
}
-36
View File
@@ -1,36 +0,0 @@
{
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";
};
};
};
}
@@ -1,198 +0,0 @@
{
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" ];
};
};
}
@@ -1,104 +0,0 @@
"""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()
+1
View File
@@ -0,0 +1 @@
Screenshots: stored in ~/downloads/screenshots, with date time in the filename
@@ -0,0 +1,100 @@
---
description: Merge worktree changes and clean up safely
---
# Merge Worktree and Cleanup
Merge changes from the current worktree into a target branch and clean up the worktree.
**Usage:** `/merge [target-branch]`
**Arguments:**
- `$1`: Target branch to merge into (default: auto-detect main/master)
## Process
### 1. Pre-flight Checks
```bash
CURRENT_BRANCH=$(git branch --show-current)
WORKTREE_DIR=$(git rev-parse --git-common-dir)
if [[ ! "$WORKTREE_DIR" == *".git/worktrees"* ]]; then
echo "❌ Error: You're not in a worktree"
exit 1
fi
if ! git diff-index --quiet HEAD --; then
echo "❌ Error: You have uncommitted changes"
git status --short
exit 1
fi
```
### 2. Determine Target Branch
```bash
TARGET_BRANCH="${1:-}"
if [ -z "$TARGET_BRANCH" ]; then
if git show-ref --verify --quiet refs/heads/main; then
TARGET_BRANCH="main"
elif git show-ref --verify --quiet refs/heads/master; then
TARGET_BRANCH="master"
else
echo "❌ Error: Cannot auto-detect main branch"
exit 1
fi
fi
```
### 3. Update Target Branch
```bash
WORKTREE_PATH=$(pwd)
COMMON_DIR=$(git rev-parse --git-common-dir)
REPO_ROOT=$(echo "$COMMON_DIR" | sed 's/\.git.*//' | sed 's/\/$//')
if [ "$COMMON_DIR" = ".git" ]; then
REPO_ROOT=$(git rev-parse --show-toplevel)
fi
cd "$REPO_ROOT"
git checkout "$TARGET_BRANCH"
git pull origin "$TARGET_BRANCH"
```
### 4. Merge
```bash
git merge --no-ff "$CURRENT_BRANCH" -m "Merge branch '$CURRENT_BRANCH'"
```
If you prefer a squash merge, do this instead:
```bash
git merge --squash "$CURRENT_BRANCH"
git commit -m "feat: <summary>"
```
### 5. Push and Clean Up
```bash
git push origin "$TARGET_BRANCH"
git worktree remove "$WORKTREE_PATH" --force || rm -rf "$WORKTREE_PATH"
git branch -d "$CURRENT_BRANCH" || git branch -D "$CURRENT_BRANCH"
```
### 6. Optional: Delete Remote Branch
```bash
git push origin --delete "$CURRENT_BRANCH"
```
## Language-specific checks
### Rust
- `cargo check`
- `cargo clippy` (clean up new warnings)
### TypeScript
- `tsc`
@@ -0,0 +1,9 @@
---
description: Rebase/merge upstream changes into the current branch
---
This command should only work while in a secondary branch (not main or master).
Commit any unstaged changes
Fetch updates from the origin repo
If there are new commits on the parent branch (typically origin/main), then merge them back into the current branch
Solve any conflicts, and analyze the incoming changes to see if additional changes are required to the branch's code (e.g.: if something was renamed in the meantime in main, our new code may need to be adjusted)
+6 -5
View File
@@ -1,13 +1,12 @@
_:
{
# Global rules (opencode/AGENTS.md) and the shared commands (merge, update)
# come from the agent-skills home-manager module — opencode has no @file
# imports, so the module concatenates the shared fragments into one file.
# opencode also auto-loads skills from ~/.claude/skills and ~/.agents/skills.
xdg.configFile = {
# Global config file
"opencode/opencode.json".source = ./opencode.json;
# Global rules (equivalent to ~/.claude/CLAUDE.md)
"opencode/AGENTS.md".source = ./AGENTS.md;
# Custom agents
"opencode/agents/oracle.md".source = ./agents/oracle.md;
"opencode/agents/explorer.md".source = ./agents/explorer.md;
@@ -17,8 +16,10 @@ _:
"opencode/agents/designer-bold.md".source = ./agents/designer-bold.md;
"opencode/agents/analyze-branch.md".source = ./agents/analyze-branch.md;
# Global commands (opencode-specific ones only)
# Global commands
"opencode/commands/work.md".source = ./commands/work.md;
"opencode/commands/merge.md".source = ./commands/merge.md;
"opencode/commands/update.md".source = ./commands/update.md;
"opencode/commands/smart-debug.md".source = ./commands/smart-debug.md;
"opencode/commands/tdd-cycle.md".source = ./commands/tdd-cycle.md;
"opencode/commands/security-scan.md".source = ./commands/security-scan.md;
+25 -5
View File
@@ -1,12 +1,32 @@
{ pkgs, ... }:
{
config,
lib,
pkgs,
inputs,
...
}:
let
pi = pkgs.callPackage ../../../pkgs/pi/package.nix { };
skills = inputs.agent-skills;
# pi has no `@file` imports in context files, so fragments get concatenated
# into one AGENTS.md. Skills need no wiring: pi reads ~/.agents/skills, which
# the agent-skills module already links.
agentsMd = pkgs.writeText "pi-AGENTS.md" (
lib.concatMapStringsSep "\n" builtins.readFile [
"${skills}/claude-md/machines/${config.programs.agentSkills.machine}.md"
"${skills}/claude-md/operating.md"
"${skills}/claude-md/writing.md"
"${skills}/claude-md/code-comments.md"
"${skills}/claude-md/RTK.md"
]
);
in
{
# ~/.pi/agent/AGENTS.md comes from the agent-skills home-manager module
# (pi has no @file imports, so the module concatenates the shared fragments).
# Pi reads skills straight from ~/.agents/skills, which the module links.
home.packages = [ pi ];
# Settings stay unmanaged: pi writes ~/.pi/agent/settings.json itself from
# /settings, /trust and `pi config`.
home.packages = [ pi ];
home.file.".pi/agent/AGENTS.md".source = agentsMd;
}
-7
View File
@@ -1,7 +0,0 @@
{ 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 ];
}
+16
View File
@@ -0,0 +1,16 @@
{
config,
pkgs,
lib,
...
}:
let
# T3 Code: open-source desktop control plane for coding agents (x86_64 only).
# Shares the custom.aiApps.deviceScaleFactor knob (defined in ../claude).
t3-code = pkgs.callPackage ../../../pkgs/t3-code/package.nix {
inherit (config.custom.aiApps) deviceScaleFactor;
};
in
{
home.packages = lib.optional pkgs.stdenv.hostPlatform.isx86_64 t3-code;
}
-1
View File
@@ -30,7 +30,6 @@ set -ag terminal-overrides ",xterm*:Tc:smcup@:rmcup@"
# Forward Shift+Enter and other modified keys through to applications
set -s extended-keys on
set -s extended-keys-format csi-u
set -as terminal-features "tmux-256color:extkeys"
# -------------------------------------------------------------------
-20
View File
@@ -1,20 +0,0 @@
{ pkgs, ... }:
{
imports = [ ./desktop/spicetify.nix ];
home.packages = with pkgs; [
obsidian
obs-studio
kooha
vokoscreen-ng
gimp
font-manager
xournalpp
remmina
slack
signal-desktop
zoom-us
yaak
bun
];
}
+1 -12
View File
@@ -10,18 +10,7 @@
enableCompletion = true;
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
# 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
'';
completionInit = "autoload -U compinit && compinit -u";
shellAliases = {
c = "cargo";
+1 -19
View File
@@ -5,10 +5,6 @@
imports = [
../common/programs/default.nix
../common/programs/desktop
../common/programs/workstation-apps.nix
../common/programs/editors.nix
../common/programs/dev-workstation.nix
../common/programs/ai-gui.nix
../common/programs/zen-browser.nix
../common/programs/hyprland
../common/programs/kitty
@@ -24,24 +20,10 @@
home.sessionVariables = {
LIBVA_DRIVER_NAME = "nvidia";
# Was GDM_BACKEND, which nothing reads (SDDM is the DM here).
GBM_BACKEND = "nvidia-drm";
GDM_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;
+139
View File
@@ -0,0 +1,139 @@
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}"
+247
View File
@@ -0,0 +1,247 @@
{
"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
}
}
+31 -24
View File
@@ -4,12 +4,14 @@
...
}:
let
# ./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
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
)
);
in
@@ -21,20 +23,12 @@ 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
@@ -46,18 +40,31 @@ in
light = "Bibata-Modern-Amber";
};
# Headless browser driver the agent tooling shells out to. Was a global npm
# install on the ubuntu box.
home.packages = [ pkgs.agent-browser ];
home = {
# Headless browser driver the agent tooling shells out to. Was a global npm
# install on the ubuntu box.
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.
programs.agentSkills = {
machine = "yolo";
prDaemon.enable = true;
hourlog.enable = true;
weekReview.enable = true;
# 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;
};
};
};
programs.agentSkills.machine = "yolo";
programs.nh.flake = "/home/naps62/tea/nixos-config";
# Idle lock and dpms-off blank the virtual output: Sunshine then captures a
-5
View File
@@ -1,5 +0,0 @@
{
"permission": {
"bash": "allow"
}
}
+112 -69
View File
@@ -3,11 +3,12 @@
inputs,
...
}:
# The user services this box exists to run.
# The user services this box exists to run, ported from hand-written units in
# ~/.config/systemd/user on the Ubuntu machine.
#
# 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.
# 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.
@@ -18,91 +19,133 @@ 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
# ACP adapter aoe's structured (web) sessions spawn as `claude-agent-acp`.
pkgs.claude-agent-acp
pkgs.bun
];
# maestro's and rev's units come from their flake modules, not from the
# hand-written set below. 8081, not 8080: aoe-web already has that port on
# this host. There is no auth layer, so 0.0.0.0 is only safe behind the LAN
# perimeter.
services.maestro = {
enable = true;
web.enable = true;
settings = {
daemon.bind_addr = "127.0.0.1:7070";
web = {
bind_addr = "0.0.0.0:8081";
daemon_url = "http://127.0.0.1:7070";
};
};
};
# 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. rev and agent-skills apply
# straight away; maestro only bumps the lock and notifies, because switching
# restarts the daemon that owns every interactive shell on this box.
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";
apply = false;
};
};
};
systemd.user.services = {
aoe-web = {
rev = {
Unit = {
Description = "aoe serve Agent of Empires web dashboard";
Description = "rev always-on local code review server";
After = [ "network.target" ];
# MUST stay 0: at RestartSec=2 a fast-crashing aoe burns the default
# MUST stay 0: at RestartSec=2 a fast-crashing rev 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";
# 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).
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";
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;
# 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" ];
};
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;
};
Install.WantedBy = [ "default.target" ];
};
hourlog = {
Unit = {
Description = "Start the Friday hour log in a tmux session";
Documentation = [ "https://git.naps.pt/yolo/agent-skills" ];
ConditionPathIsDirectory = "%h/tea/agent-skills";
};
Service = {
Type = "oneshot";
ExecStart = "%h/tea/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/agent-skills";
};
Service = {
Type = "oneshot";
ExecStart = "%h/tea/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" ];
};
};
}
# pr-daemon, hourlog and week-review are not here: their units ship with the
# scripts they run, in the agent-skills module. This host opts in with
# programs.agentSkills.*.enable in home/yolo/default.nix.
-6
View File
@@ -35,12 +35,6 @@
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 = "/home/naps62/yolo";
}
];
boot.kernelParams = [
+5 -2
View File
@@ -1,4 +1,7 @@
{ pkgs, ... }:
let
sddm-noctalia-theme = pkgs.callPackage ../../../../pkgs/sddm-noctalia-theme/package.nix { };
in
{
services.xserver = {
enable = true;
@@ -12,7 +15,7 @@
services.displayManager.sddm = {
enable = true;
wayland.enable = true;
theme = "sddm-astronaut-theme";
theme = "sddm-noctalia";
package = pkgs.kdePackages.sddm;
extraPackages = with pkgs.kdePackages; [
qtmultimedia
@@ -23,6 +26,6 @@
};
environment.systemPackages = with pkgs; [
sddm-astronaut
sddm-noctalia-theme
];
}
@@ -10,13 +10,6 @@
portalPackage = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.xdg-desktop-portal-hyprland;
};
# swayosd (the volume/brightness OSD, configured in home) writes
# /sys/class/backlight/*/brightness directly. The package ships a udev rule
# that chgrps those files to `video`; without both the rule and the group,
# brightness keys pop the OSD but change nothing.
services.udev.packages = [ pkgs.swayosd ];
users.users.naps62.extraGroups = [ "video" ];
# GTK/GNOME services still useful on Wayland
programs.dconf.enable = true;
-10
View File
@@ -26,10 +26,6 @@
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 = [
@@ -38,12 +34,6 @@
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 = "/home/naps62/yolo";
}
];
# Arm Wake-on-LAN (magic packet) on the Intel igc NIC and re-apply it on every
-24
View File
@@ -58,30 +58,6 @@
# 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.
+33
View File
@@ -0,0 +1,33 @@
{ pkgs, ... }:
pkgs.stdenv.mkDerivation {
pname = "sddm-noctalia-theme";
version = "1.0";
src = pkgs.fetchFromGitHub {
owner = "mahaveergurjar";
repo = "sddm";
rev = "noctalia";
sha256 = "sha256-e/gYI6znHXxlDCOVh4p265x3kO0nQUU897hCY1yEz88=";
};
installPhase = ''
mkdir -p $out/share/sddm/themes/sddm-noctalia
cp -r * $out/share/sddm/themes/sddm-noctalia/
# Patch for Qt6 SDDM greeter compatibility
# SDDM reads QtVersion from metadata.desktop to find the greeter binary;
# default is 5 (sddm-greeter), we need 6 (sddm-greeter-qt6)
substituteInPlace $out/share/sddm/themes/sddm-noctalia/metadata.desktop \
--replace-fail "Theme-Id=noctalia" "QtVersion=6
Theme-Id=noctalia"
'';
meta = with pkgs.lib; {
description = "Noctalia-themed SDDM login theme";
homepage = "https://github.com/mahaveergurjar/sddm/tree/noctalia";
license = licenses.gpl3;
platforms = platforms.linux;
};
}