Compare commits
16
Commits
faaf24ddc0
...
713d91d5fc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
713d91d5fc | ||
|
|
e5ba1bfc55 | ||
|
|
b5fa599671 | ||
|
|
0fa567245a | ||
|
|
969bd69d8d | ||
|
|
6c8046bac8 | ||
|
|
914a7e5105 | ||
|
|
f09ba07b63 | ||
|
|
7904433d34 | ||
|
|
0ec3c6413c | ||
|
|
5a4588c532 | ||
|
|
c17524e358 | ||
|
|
2abd842e97 | ||
|
|
d5507811ea | ||
|
|
f431e81ce6 | ||
|
|
a172e49c7e |
@@ -59,6 +59,12 @@ Secrets (needs the admin age key at `~/.config/sops/age/keys.txt`):
|
|||||||
./scripts/edit_secrets secrets/<host>.yaml
|
./scripts/edit_secrets secrets/<host>.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Claude: never run `sops --decrypt`/`edit_secrets --show` and print the result — that
|
||||||
|
puts every plaintext secret in the file into the conversation transcript, not just the
|
||||||
|
one you wanted.** To add or change a single value non-interactively, use
|
||||||
|
`sops --set '["key"] "value"' secrets/<host>.yaml` (quote the value as JSON), which
|
||||||
|
writes without ever displaying the file's existing contents.
|
||||||
|
|
||||||
Test a service config BEFORE touching hardware — always do this for nontrivial changes:
|
Test a service config BEFORE touching hardware — always do this for nontrivial changes:
|
||||||
```
|
```
|
||||||
# x86 QEMU VM of mercury's DNS/DHCP stack (fast; validates pihole/unbound at runtime)
|
# x86 QEMU VM of mercury's DNS/DHCP stack (fast; validates pihole/unbound at runtime)
|
||||||
|
|||||||
Generated
+237
-46
@@ -3,6 +3,7 @@
|
|||||||
"authentik-nix": {
|
"authentik-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"authentik-src": "authentik-src",
|
"authentik-src": "authentik-src",
|
||||||
|
"client-ts-generator-src": "client-ts-generator-src",
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
@@ -13,11 +14,11 @@
|
|||||||
"uv2nix": "uv2nix"
|
"uv2nix": "uv2nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785159467,
|
"lastModified": 1786986906,
|
||||||
"narHash": "sha256-1LwjkVJK5pst6Atm3uOiZW3eBMY3gs0ICO14eFGlpGw=",
|
"narHash": "sha256-DJ1oU9szQJNdEM0dysh4NnKOB1HwOKtNukrUYKpawVs=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "authentik-nix",
|
"repo": "authentik-nix",
|
||||||
"rev": "a492deb563cf54cb8fe134e94a7ba3b01820ca5e",
|
"rev": "afdb2eeca1e0b38fabb93c4a8944be73d3581268",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -43,6 +44,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"client-ts-generator-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1784638510,
|
||||||
|
"narHash": "sha256-NfwEWQ/SRjgeUz+F/7uoWAMwk7OqdF2+686krhvJn2M=",
|
||||||
|
"owner": "goauthentik",
|
||||||
|
"repo": "client-ts",
|
||||||
|
"rev": "5850af5867bef6fd4291731797d21b704c7f189d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "goauthentik",
|
||||||
|
"repo": "client-ts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"disko": {
|
"disko": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -83,6 +100,27 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
|
"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_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"hermes-agent",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782949081,
|
"lastModified": 1782949081,
|
||||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||||
@@ -118,6 +156,29 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hermes-agent": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts_2",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"npm-lockfile-fix": "npm-lockfile-fix",
|
||||||
|
"pyproject-build-systems": "pyproject-build-systems_2",
|
||||||
|
"pyproject-nix": "pyproject-nix_2",
|
||||||
|
"uv2nix": "uv2nix_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1787007062,
|
||||||
|
"narHash": "sha256-C1SJX0VqlxrLknGhFKj7+DKpfJ1eI4udPE67kW+dT+M=",
|
||||||
|
"owner": "NousResearch",
|
||||||
|
"repo": "hermes-agent",
|
||||||
|
"rev": "55e34fb7d0cd8fd1e16be82a1ceafee05fca63d3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NousResearch",
|
||||||
|
"repo": "hermes-agent",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -125,11 +186,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785119570,
|
"lastModified": 1786924861,
|
||||||
"narHash": "sha256-Rgs2xKnGLFWQscxUaXX07oyZeuMDOHEbqDOsgliLFGM=",
|
"narHash": "sha256-hftabkb+73OcGzvwFAjCiQorAhprs9TnU1+FkGO5CIw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "d4fd24667c8cbef124bb70a20380cab75ec8474d",
|
"rev": "09ae1b85a6db412d841d60f924b23f881f0d0a38",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -146,11 +207,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785267577,
|
"lastModified": 1785852009,
|
||||||
"narHash": "sha256-PoNXV5H5BbncwyO0C6Dar6C49GSf3UGDRRMpuArX8y4=",
|
"narHash": "sha256-EgIk8Enyhiqa5J326BDNgujeU+lEbYxZzo59rXKWr4Q=",
|
||||||
"ref": "refs/heads/develop",
|
"ref": "refs/heads/develop",
|
||||||
"rev": "73ce9ddbdf27d21c87322bcd638c1efe08be6827",
|
"rev": "dcec9d205e4f8fbe18f71260a0614dd0187f4204",
|
||||||
"revCount": 4,
|
"revCount": 33,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.mgaction.town/darman/hypr-chrome.git"
|
"url": "https://git.mgaction.town/darman/hypr-chrome.git"
|
||||||
},
|
},
|
||||||
@@ -181,9 +242,9 @@
|
|||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"pyproject-build-systems": "pyproject-build-systems_2",
|
"pyproject-build-systems": "pyproject-build-systems_3",
|
||||||
"pyproject-nix": "pyproject-nix_2",
|
"pyproject-nix": "pyproject-nix_3",
|
||||||
"uv2nix": "uv2nix_2"
|
"uv2nix": "uv2nix_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1768002052,
|
"lastModified": 1768002052,
|
||||||
@@ -253,11 +314,11 @@
|
|||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784704708,
|
"lastModified": 1786520020,
|
||||||
"narHash": "sha256-xuzg2Xfalr1X5ah5IPgqB8Vyif+2je1qkCtGxCB+rm0=",
|
"narHash": "sha256-9b0hAKM8UtNsm4lD3aySuXPjPpSibh6yfYEcNvr4fU0=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-anywhere",
|
"repo": "nixos-anywhere",
|
||||||
"rev": "91fc9b70fc295258c366cce8627efb6f185fd9fb",
|
"rev": "4cf3b82df8422f82657ae98c3b8374a5fd74f9a1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -274,11 +335,11 @@
|
|||||||
"nixos-unstable": "nixos-unstable"
|
"nixos-unstable": "nixos-unstable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784802994,
|
"lastModified": 1786617555,
|
||||||
"narHash": "sha256-4PcD0Ibzdkh85G+70w5dLlR9YgQ2bmNIjiPPMSzO57w=",
|
"narHash": "sha256-r3brKkBWm34wltszIeoZw2VCFUEoS2nNAe3W2Zgzc9k=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-images",
|
"repo": "nixos-images",
|
||||||
"rev": "6ece16b0c97986fe085122e796044add4cc3ff64",
|
"rev": "f01878e82602ad3d0a2e74a14440521924e57bc5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -306,11 +367,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785090369,
|
"lastModified": 1786862985,
|
||||||
"narHash": "sha256-m0pDuRJG7EDo9ri+4Ksu83VsI+PlxNC9lNBfydejce4=",
|
"narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "624af665418d3c65d544145b4d34ad696439570e",
|
"rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -322,11 +383,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782614948,
|
"lastModified": 1785031560,
|
||||||
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
|
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixpkgs.lib",
|
"repo": "nixpkgs.lib",
|
||||||
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
|
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -337,11 +398,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785141334,
|
"lastModified": 1786963906,
|
||||||
"narHash": "sha256-kh35kIx7el4Jk8Ki3BH9/Pn1eZYSYLJ6LMALos0zOy0=",
|
"narHash": "sha256-3tkeMWSvHPo3tYljfXbPC/TgknikU1GvdVr/DkdfvE0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "38a4887411571457d700c51c64a6e49ead2ed5ab",
|
"rev": "f4b6996c4e8b9ee06ce147ec344c885f51071b14",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -353,11 +414,27 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785133411,
|
"lastModified": 1785318670,
|
||||||
"narHash": "sha256-Yjv0WEg39KRYS0rBdTbu6Fc/or/ihAKk13W9sQ6VWd0=",
|
"narHash": "sha256-dN6Ou5x/+23FZLEpYP3IffO+NyJFzUlGumt1uu3MMaY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2f5a153c270b70cb0f8c11f46d96d6d3bc39f4e3",
|
"rev": "0954f7ee2f6bb3dc7d4e3d0d8bcb8fd4bde4cfc5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1786943417,
|
||||||
|
"narHash": "sha256-b4qgjdFtlz5TAZ1Hi7DFJeqX3sjaDUnrwr9OO+O1rM0=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0dd31db7e6dbf9ce05697c4545f6fe01accec994",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -367,6 +444,27 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"npm-lockfile-fix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"hermes-agent",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775903712,
|
||||||
|
"narHash": "sha256-2GV79U6iVH4gKAPWYrxUReB0S41ty/Y3dBLquU8AlaA=",
|
||||||
|
"owner": "jeslie0",
|
||||||
|
"repo": "npm-lockfile-fix",
|
||||||
|
"rev": "c6093acb0c0548e0f9b8b3d82918823721930fe8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "jeslie0",
|
||||||
|
"repo": "npm-lockfile-fix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pyproject-build-systems": {
|
"pyproject-build-systems": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -383,11 +481,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782093830,
|
"lastModified": 1785730568,
|
||||||
"narHash": "sha256-6gmEVe69+KlRkZD4PEEV5xAlB9CB0Y9TiuEgQjDrKTQ=",
|
"narHash": "sha256-NjSPsgjJ7MSpBtTkUcmNhRe6AFZ96+zsca2M8YuQi8Y=",
|
||||||
"owner": "pyproject-nix",
|
"owner": "pyproject-nix",
|
||||||
"repo": "build-system-pkgs",
|
"repo": "build-system-pkgs",
|
||||||
"rev": "430680a19bc85a3bda55f12e4cc1a1aadcf2e478",
|
"rev": "90fde00db3687922d39d95fc591475fd0bbbcd72",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -397,6 +495,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pyproject-build-systems_2": {
|
"pyproject-build-systems_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"hermes-agent",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pyproject-nix": [
|
||||||
|
"hermes-agent",
|
||||||
|
"pyproject-nix"
|
||||||
|
],
|
||||||
|
"uv2nix": [
|
||||||
|
"hermes-agent",
|
||||||
|
"uv2nix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1785115949,
|
||||||
|
"narHash": "sha256-8AM37BfyGaL2v/SZyg4PupRxJ01Y4htvM+WrTjWrPpo=",
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "build-system-pkgs",
|
||||||
|
"rev": "62c0d86027edb1c4f39a5facc09876348144f7c9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "build-system-pkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pyproject-build-systems_3": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"mediamanager-nix",
|
"mediamanager-nix",
|
||||||
@@ -432,6 +559,27 @@
|
|||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1786031528,
|
||||||
|
"narHash": "sha256-cROiHKO3UbIKqF5FG5NikvydzlfIj4EcR1Cty9qOVt4=",
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "pyproject.nix",
|
||||||
|
"rev": "1b1485546d85f6f6c7aadb10c4923dbc09633263",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "pyproject.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pyproject-nix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"hermes-agent",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784591072,
|
"lastModified": 1784591072,
|
||||||
"narHash": "sha256-zP/WaDxrRu8GANZM61+V2LT/7ycEEdoyLWn7M6WzU7M=",
|
"narHash": "sha256-zP/WaDxrRu8GANZM61+V2LT/7ycEEdoyLWn7M6WzU7M=",
|
||||||
@@ -446,7 +594,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pyproject-nix_2": {
|
"pyproject-nix_3": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"mediamanager-nix",
|
"mediamanager-nix",
|
||||||
@@ -471,15 +619,17 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"authentik-nix": "authentik-nix",
|
"authentik-nix": "authentik-nix",
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
|
"hermes-agent": "hermes-agent",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"hypr-chrome": "hypr-chrome",
|
"hypr-chrome": "hypr-chrome",
|
||||||
"mediamanager-nix": "mediamanager-nix",
|
"mediamanager-nix": "mediamanager-nix",
|
||||||
"nix-flatpak": "nix-flatpak",
|
"nix-flatpak": "nix-flatpak",
|
||||||
"nixos-anywhere": "nixos-anywhere",
|
"nixos-anywhere": "nixos-anywhere",
|
||||||
"nixos-images": "nixos-images",
|
"nixos-images": "nixos-images",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_3",
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
"sops-nix": "sops-nix"
|
"sops-nix": "sops-nix",
|
||||||
|
"tome": "tome"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sops-nix": {
|
"sops-nix": {
|
||||||
@@ -489,11 +639,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783174389,
|
"lastModified": 1786629091,
|
||||||
"narHash": "sha256-aCWC8ngycU7OdJrU2+Je3qf+1a2ykuBvpPhZT/9tXMc=",
|
"narHash": "sha256-gkig4nPi1CWc4Z50GBsjE4ygSE7hMpl/TwID2an2Cck=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "f1406619a3884cd5c47992a70b8b35c9c0fcb4c9",
|
"rev": "a8627b21b9107c5711c96b84f32a9a4b3d45295f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -517,6 +667,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tome": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1785431655,
|
||||||
|
"narHash": "sha256-EoM4HmJb7MZArMoP4y1b7DZcvM8iANYjMiE5gGUx070=",
|
||||||
|
"ref": "refs/heads/master",
|
||||||
|
"rev": "4f3ca447cdc967371adaf71b26cf283952e54212",
|
||||||
|
"revCount": 51,
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.mgaction.town:2222/darman/TOME.git"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "ssh://gitea@git.mgaction.town:2222/darman/TOME.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
"treefmt-nix": {
|
"treefmt-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -525,11 +691,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784369104,
|
"lastModified": 1785945821,
|
||||||
"narHash": "sha256-47cxbcZODibHv3rELFQ9vZly0vUNkND/atn/U7HLeb0=",
|
"narHash": "sha256-NLSyTCW4K4ofhNBllt3omPasm6QpralXH1DBZOc91Dw=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "df3c0640565d04a0261253cdd89fce78ec50168a",
|
"rev": "ae7910970dddc408fe6ab1c8e4b277bb21d72dc0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -550,11 +716,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785147819,
|
"lastModified": 1786615403,
|
||||||
"narHash": "sha256-udwe836AZe8BYU+YqVc51QLuOeWKoGEmPIMF0ImUfTQ=",
|
"narHash": "sha256-U++y7nM/6xiEcWI7q4fQoPZjPvRaTwkSqzBOVoEBjUE=",
|
||||||
"owner": "pyproject-nix",
|
"owner": "pyproject-nix",
|
||||||
"repo": "uv2nix",
|
"repo": "uv2nix",
|
||||||
"rev": "0983f54345137c2dd528ce2c28cb56c2cbdceb2a",
|
"rev": "4b59abb2ae1896d2a0e1abfc47fbc9bf985ea730",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -564,6 +730,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uv2nix_2": {
|
"uv2nix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"hermes-agent",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pyproject-nix": [
|
||||||
|
"hermes-agent",
|
||||||
|
"pyproject-nix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1785277507,
|
||||||
|
"narHash": "sha256-9Tq3UDX2hD/aveW/HvkBlAmEwJTOlY5HQXJM+L5BGmE=",
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "uv2nix",
|
||||||
|
"rev": "5a836d395cbf5fc22670eb98dd4aa4fc4d406977",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "pyproject-nix",
|
||||||
|
"repo": "uv2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uv2nix_3": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"mediamanager-nix",
|
"mediamanager-nix",
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
};
|
};
|
||||||
authentik-nix.url = "github:nix-community/authentik-nix";
|
authentik-nix.url = "github:nix-community/authentik-nix";
|
||||||
nix-flatpak.url = "github:gmodena/nix-flatpak";
|
nix-flatpak.url = "github:gmodena/nix-flatpak";
|
||||||
|
# Hermes Agent (see hosts/jupiter/hermes-agent.nix) — Tier 2 platform
|
||||||
|
# per its own docs (best-effort Nix support, can break on any upstream
|
||||||
|
# commit), so pinned like everything else via flake.lock rather than
|
||||||
|
# followed loosely.
|
||||||
|
hermes-agent.url = "github:NousResearch/hermes-agent";
|
||||||
# Own Hyprland plugin (border + title bar), public repo, fetched over
|
# Own Hyprland plugin (border + title bar), public repo, fetched over
|
||||||
# https (no credentials needed, unlike tome below). `nixpkgs.follows` is
|
# https (no credentials needed, unlike tome below). `nixpkgs.follows` is
|
||||||
# what makes its packaged build ABI-correct — Hyprland plugins are
|
# what makes its packaged build ABI-correct — Hyprland plugins are
|
||||||
@@ -43,10 +48,19 @@
|
|||||||
url = "git+https://git.mgaction.town/darman/hypr-chrome.git";
|
url = "git+https://git.mgaction.town/darman/hypr-chrome.git";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
# NOTE: the `tome` input (private repo git+ssh://gitea@git.mgaction.town) was
|
# Tome (formerly AudibleLibrary) — darman's own .NET/Photino desktop app.
|
||||||
# removed temporarily — the credential-less installer-iso can't fetch it, so
|
# Private repo on our own gitea; fetched over ssh with darman's ambient key,
|
||||||
# `install terra localhost` failed at nixos-install (post-disko). Re-add it
|
# same as any other git flake input. `flake = false`: it's a plain source
|
||||||
# (input + packages.tome + desktop-apps.nix) once the installer can auth.
|
# tree, not itself a flake. See pkgs/tome.nix.
|
||||||
|
#
|
||||||
|
# NOTE: the credential-less installer-iso can't fetch this (git+ssh needs
|
||||||
|
# darman's key), so `./scripts/deploy install terra localhost` will fail
|
||||||
|
# at nixos-install (post-disko) while this input is present. Known
|
||||||
|
# tradeoff — re-removed this once before (4f79ec7) for the same reason.
|
||||||
|
tome = {
|
||||||
|
url = "git+ssh://gitea@git.mgaction.town:2222/darman/TOME.git";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, disko, nixos-anywhere, sops-nix, nixos-images, home-manager, mediamanager-nix, authentik-nix, ... }@inputs:
|
outputs = { self, nixpkgs, disko, nixos-anywhere, sops-nix, nixos-images, home-manager, mediamanager-nix, authentik-nix, ... }@inputs:
|
||||||
@@ -71,6 +85,7 @@
|
|||||||
modules = [
|
modules = [
|
||||||
disko.nixosModules.disko
|
disko.nixosModules.disko
|
||||||
sops-nix.nixosModules.sops
|
sops-nix.nixosModules.sops
|
||||||
|
inputs.hermes-agent.nixosModules.default
|
||||||
./hosts/jupiter/configuration.nix
|
./hosts/jupiter/configuration.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
./disk-config.nix # disko: OS-disk partitions + filesystems
|
./disk-config.nix # disko: OS-disk partitions + filesystems
|
||||||
./secrets.nix # sops-nix: samba password, tailscale key, ...
|
./secrets.nix # sops-nix: samba password, tailscale key, ...
|
||||||
|
./hermes-agent.nix # Hermes Agent, isolated instance (see file for why)
|
||||||
../../common.nix # shared base: user / ssh / nix / firewall
|
../../common.nix # shared base: user / ssh / nix / firewall
|
||||||
../../services/network/samba.nix
|
../../services/network/samba.nix
|
||||||
../../services/network/avahi.nix
|
../../services/network/avahi.nix
|
||||||
@@ -41,6 +42,21 @@
|
|||||||
# but at ~5G free on a 29G eMMC even 5 is too many — override down to 2.
|
# but at ~5G free on a 29G eMMC even 5 is too many — override down to 2.
|
||||||
boot.loader.systemd-boot.configurationLimit = lib.mkForce 2;
|
boot.loader.systemd-boot.configurationLimit = lib.mkForce 2;
|
||||||
|
|
||||||
|
# A `switch` pins the old generation as a GC root until the box reboots onto
|
||||||
|
# the new one (booted-system vs current-system) — common.nix's nix.gc is
|
||||||
|
# weekly, far too slow to catch that on a 29G eMMC. 2026-08-19: one switch
|
||||||
|
# alone took 14G -> 19G used; only reboot (releases the old root) + this GC
|
||||||
|
# brought it back to 14G. Run a full collect right after every boot instead
|
||||||
|
# of waiting on the weekly timer.
|
||||||
|
systemd.services.gc-on-boot = {
|
||||||
|
description = "Full nix-collect-garbage on every boot";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.nix}/bin/nix-collect-garbage -d";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Root lives on the ZimaBlade eMMC (mmcblk0). nixos-generate-config runs in
|
# Root lives on the ZimaBlade eMMC (mmcblk0). nixos-generate-config runs in
|
||||||
# the RAM installer and does NOT detect these, so pin them here (merged with
|
# the RAM installer and does NOT detect these, so pin them here (merged with
|
||||||
# hardware-configuration.nix) or stage-1 can't mount root and the box panics.
|
# hardware-configuration.nix) or stage-1 can't mount root and the box panics.
|
||||||
@@ -51,11 +67,48 @@
|
|||||||
# next value: acpi -> bios -> cold -> efi.
|
# next value: acpi -> bios -> cold -> efi.
|
||||||
boot.kernelParams = [ "reboot=pci" ];
|
boot.kernelParams = [ "reboot=pci" ];
|
||||||
|
|
||||||
|
# ---- GPU (jellyfin hardware transcoding) ----
|
||||||
|
# Apollo Lake N3450 / HD Graphics 500 (Gen9, pci 8086:5A85). The i915 KERNEL
|
||||||
|
# driver binds on its own — /dev/dri/{card1,renderD128} exist without this —
|
||||||
|
# but the libva USERSPACE driver only ships when hardware.graphics is on, and
|
||||||
|
# nothing else here pulled it in. Without it VAAPI init fails with "unknown
|
||||||
|
# libva error" and jellyfin-ffmpeg exits 251 on EVERY transcode, which the
|
||||||
|
# client shows as generic playback failure: the server log only says "FFmpeg
|
||||||
|
# exited with code 251", never that a driver is missing. Verified on the box:
|
||||||
|
# the same h264_vaapi encode goes 251 -> 0 once iHD is on LIBVA_DRIVERS_PATH.
|
||||||
|
#
|
||||||
|
# iHD (intel-media-driver) is the right one for Gen9; i965 is for Gen8 and
|
||||||
|
# older. Note the render node is 0666 but card1 is 0660 root:video, so the
|
||||||
|
# group membership in services/media/jellyfin.nix matters for the card node.
|
||||||
|
hardware.graphics = {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = [ pkgs.intel-media-driver ];
|
||||||
|
};
|
||||||
|
# ⚠️ This buys VAAPI only — jellyfin must be set to VAAPI, NOT QSV, in its
|
||||||
|
# web UI (Dashboard -> Playback -> Transcoding). QSV needs an MFX runtime on
|
||||||
|
# top of the libva driver: ffmpeg's `-init_hw_device qsv=qs@va` dies with
|
||||||
|
# "Error creating a MFX session: -9" -> exit 171, the SECOND failure hiding
|
||||||
|
# behind the first (fixing the missing driver only moved 251 -> 171).
|
||||||
|
# There is no good way to provide it here: vpl-gpu-rt is Gen12+, and the
|
||||||
|
# Gen9 runtime `intel-media-sdk` is marked INSECURE in nixpkgs (EOL, 5 CVEs
|
||||||
|
# incl. local privilege escalation) — not worth it when VAAPI does the same
|
||||||
|
# job on this chip at ~3.5x realtime for 1080p->720p.
|
||||||
|
#
|
||||||
|
# Also: 4K HDR (the 2160p HEVC/DV remuxes) can NOT be tone-mapped here.
|
||||||
|
# tonemap_opencl needs OpenCL, which has no platform on this box, and
|
||||||
|
# tonemap_vaapi is Gen11+ — both fail. Only a plain scale_vaapi=format=nv12
|
||||||
|
# succeeds, which drops HDR without tone-mapping (washed-out picture).
|
||||||
|
# Those files need to direct-play, or be kept as 1080p SDR versions.
|
||||||
|
|
||||||
# ---- NAS data array ----
|
# ---- NAS data array ----
|
||||||
# Existing ext4 on the mdadm RAID0 over sda+sdb (md0, 29.1T).
|
# Existing ext4 on the mdadm RAID0 over sda+sdb (md0, 29.1T).
|
||||||
# Mounted, NOT formatted; kept out of disko so it is never wiped.
|
# Mounted, NOT formatted; kept out of disko so it is never wiped.
|
||||||
# ⚠️ RAID0 = no redundancy: either 16TB disk failing loses ALL data.
|
# ⚠️ RAID0 = no redundancy: either 16TB disk failing loses ALL data.
|
||||||
boot.swraid.enable = true; # assemble the mdadm array at boot
|
boot.swraid.enable = true; # assemble the mdadm array at boot
|
||||||
|
# Silences "mdmon service will crash" eval warning. RAID0 here uses native
|
||||||
|
# superblocks so mdmon (external-metadata arrays only) never actually runs,
|
||||||
|
# but the module warns unconditionally without SOME MAILADDR/PROGRAM set.
|
||||||
|
boot.swraid.mdadmConf = "MAILADDR root";
|
||||||
fileSystems."/mnt/data" = {
|
fileSystems."/mnt/data" = {
|
||||||
# fs UUID (stable) — the array may enumerate as /dev/md127, so avoid /dev/md0.
|
# fs UUID (stable) — the array may enumerate as /dev/md127, so avoid /dev/md0.
|
||||||
device = "/dev/disk/by-uuid/dadbff6f-652e-49b2-bfed-eb1308ab8b78";
|
device = "/dev/disk/by-uuid/dadbff6f-652e-49b2-bfed-eb1308ab8b78";
|
||||||
@@ -63,6 +116,72 @@
|
|||||||
options = [ "nofail" ]; # don't block boot if the array is degraded/absent
|
options = [ "nofail" ]; # don't block boot if the array is degraded/absent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# `nofail` above is necessary but NOT sufficient — any mount layered on the
|
||||||
|
# array (prowlarr/seerr binds) is RequiredBy local-fs.target and will fail it
|
||||||
|
# regardless, and emergency mode on this box is a dead end: root is locked, so
|
||||||
|
# sulogin drops you at a prompt you cannot answer, with no ssh. 2026-08-06: a
|
||||||
|
# drive that failed to enumerate after the rack move did exactly this —
|
||||||
|
# "Timed out waiting for device /dev/disk/by-uuid/dadbff6f-…" -> Dependency
|
||||||
|
# failed for Local File Systems -> Reached target Emergency Mode, twice.
|
||||||
|
# Boot as far as possible instead and leave the failed units to be read over
|
||||||
|
# ssh. The array-backed services carry RequiresMountsFor=/mnt/data so they
|
||||||
|
# still refuse to start rather than writing to the eMMC.
|
||||||
|
systemd.enableEmergencyMode = false;
|
||||||
|
|
||||||
|
# ---- Heavy state moved off the eMMC ----
|
||||||
|
# A deploy holds TWO full closures (~9G each) on a 29G disk at once, so the
|
||||||
|
# OS disk has no room for state that grows on its own. 2026-08-09: it hit 0
|
||||||
|
# bytes free with both gen 39 and gen 40 resident, and postgres died on
|
||||||
|
# "No space left on device" — note ext4 reserves 5% for root, so non-root
|
||||||
|
# services see zero while df still shows ~300M free.
|
||||||
|
#
|
||||||
|
# Paths live under /mnt/data/AppData like every other service's state. Both
|
||||||
|
# settings below are jupiter-only on purpose: services/containers.nix stays
|
||||||
|
# engine- and host-agnostic (mercury runs pihole on podman with no array).
|
||||||
|
|
||||||
|
# podman: CI images dominate and keep growing — the gitea runner's
|
||||||
|
# act-latest is 1.7G, and the act-22.04 label in services/dev/gitea.nix
|
||||||
|
# pulls another ~1.7G the first time a job requests it.
|
||||||
|
# runroot stays on /run: it is per-boot tmpfs state, not a growing store.
|
||||||
|
virtualisation.containers.storage.settings.storage = {
|
||||||
|
driver = "overlay";
|
||||||
|
graphroot = "/mnt/data/AppData/containers/storage";
|
||||||
|
runroot = "/run/containers/storage";
|
||||||
|
};
|
||||||
|
|
||||||
|
# immich's postgres cluster. Version component mirrors the upstream default
|
||||||
|
# (`/var/lib/postgresql/${psqlSchema}`) so a major bump gets its own dir
|
||||||
|
# instead of silently reusing the old cluster's files.
|
||||||
|
# ⚠️ This puts the DB in the SAME failure domain as the photos it indexes:
|
||||||
|
# /mnt/data is RAID0, so either 16TB disk now loses both, where before an
|
||||||
|
# eMMC failure and an array failure each took only one. Chosen deliberately
|
||||||
|
# — the two are useless apart — but neither is backed up.
|
||||||
|
services.postgresql.dataDir =
|
||||||
|
"/mnt/data/AppData/postgresql/${config.services.postgresql.package.psqlSchema}";
|
||||||
|
|
||||||
|
# /mnt/data/AppData is drwx--x--- darman:users, so postgres needs group
|
||||||
|
# "users" just to TRAVERSE into its own dataDir — exactly the reason immich
|
||||||
|
# has the same line. The cluster dir itself keeps the mode it was initdb'd
|
||||||
|
# with (0750 postgres:postgres) — postgres only accepts 0700, or 0750 when
|
||||||
|
# the cluster was created with group access, and refuses to start otherwise.
|
||||||
|
users.users.postgres.extraGroups = [ "users" ];
|
||||||
|
|
||||||
|
# Neither path is under /var/lib, so no module creates it: the postgresql
|
||||||
|
# module's own tmpfiles entry only adjusts a dataDir that already exists,
|
||||||
|
# the same way immich's mediaLocation rule does.
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /mnt/data/AppData/postgresql 0750 postgres postgres -"
|
||||||
|
"d /mnt/data/AppData/containers 0700 root root -"
|
||||||
|
];
|
||||||
|
|
||||||
|
# graphroot is not a systemd path dependency the way dataDir is, so nothing
|
||||||
|
# derives a mount ordering from it. Without these, podman would recreate an
|
||||||
|
# empty store on the eMMC under the mountpoint when the array is late or
|
||||||
|
# absent, and the runner would re-pull every image into it.
|
||||||
|
# (podman-clonarr already carries this from services/media/clonarr.nix.)
|
||||||
|
systemd.services.podman.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
systemd.services.gitea-runner-jupiter.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
|
||||||
# ---- Caddy vhosts (LAN) ----
|
# ---- Caddy vhosts (LAN) ----
|
||||||
# Reached via pihole local-DNS names -> jupiter IP.
|
# Reached via pihole local-DNS names -> jupiter IP.
|
||||||
services.caddy.virtualHosts = {
|
services.caddy.virtualHosts = {
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
# Hermes Agent — a SEPARATE, isolated instance from terra's
|
||||||
|
# (services/desktop/hermes-agent.nix). Locked down harder than terra given
|
||||||
|
# jupiter's much bigger blast radius (irreplaceable immich photos on an
|
||||||
|
# unredundant RAID0, gitea/CI tokens, the whole media stack):
|
||||||
|
#
|
||||||
|
# - Own dedicated "hermes" system user (module default: user/group "hermes",
|
||||||
|
# createUser = true) — NOT darman. darman is in jupiter's "docker" group
|
||||||
|
# (services/containers.nix: rootful podman with dockerCompat), which is
|
||||||
|
# root-equivalent (`docker run -v /:/host --privileged ...`). Handing an
|
||||||
|
# LLM-driven agent that identity would mean a container escape = root on
|
||||||
|
# the whole NAS.
|
||||||
|
# - container.enable = true, backend = "podman": the ENTIRE gateway process
|
||||||
|
# runs inside a container (reusing jupiter's existing rootful podman
|
||||||
|
# instead of also standing up a second Docker daemon), not just the shell
|
||||||
|
# tool. Per upstream's own SECURITY.md this is "whole-process wrapping" —
|
||||||
|
# shell, file tools, MCP subprocesses, and the code-exec tool are all
|
||||||
|
# confined, unlike the lighter "terminal-backend"-only isolation.
|
||||||
|
# - Its own Telegram bot (own token, in secrets.nix) with an EXPLICIT
|
||||||
|
# TELEGRAM_ALLOWED_USERS rather than relying solely on the adapter's
|
||||||
|
# fail-closed default. Sharing terra's bot token would 409-conflict two
|
||||||
|
# long-pollers on the same token.
|
||||||
|
# - No container.extraVolumes into /mnt/data or the homelab repo — nothing
|
||||||
|
# valuable is in reach if a command goes wrong or gets injected via
|
||||||
|
# Telegram/tool output. stateDir/workingDirectory live on the array
|
||||||
|
# (below) purely because coding-task state (repo clones, npm/pip caches
|
||||||
|
# inside the container's writable layer) belongs off the 29G eMMC, same
|
||||||
|
# reasoning as postgres/containers.storage in configuration.nix — NOT
|
||||||
|
# because anything else on /mnt/data is exposed to the agent.
|
||||||
|
{
|
||||||
|
services.hermes-agent = {
|
||||||
|
enable = true;
|
||||||
|
addToSystemPackages = true; # `hermes` on darman's PATH for interactive
|
||||||
|
# debugging over ssh — routes through to the
|
||||||
|
# container, does not grant darman any group.
|
||||||
|
|
||||||
|
# Off the eMMC: stateDir bind-mounts into the container as /data, so this
|
||||||
|
# is where any future scoped repo clone (container.extraVolumes) and the
|
||||||
|
# container's own writable layer (npm/pip installs during coding tasks)
|
||||||
|
# actually land. RequiresMountsFor below (mirrors podman/sabnzbd/gitea-runner
|
||||||
|
# in configuration.nix) keeps the service from starting — and bind-mounting
|
||||||
|
# the wrong, empty eMMC path — before the nofail array is up.
|
||||||
|
stateDir = "/mnt/data/AppData/hermes";
|
||||||
|
workingDirectory = "/mnt/data/AppData/hermes/workspaces";
|
||||||
|
|
||||||
|
container = {
|
||||||
|
enable = true;
|
||||||
|
backend = "podman"; # jupiter already runs podman (services/containers.nix);
|
||||||
|
# default "docker" would stand up a second daemon.
|
||||||
|
};
|
||||||
|
|
||||||
|
# Same OpenCode Go provider account as terra (services/desktop/hermes-agent.nix)
|
||||||
|
# — just an API key, not a stateful identity like the Telegram bot token, so
|
||||||
|
# sharing it across hosts is fine.
|
||||||
|
settings.model = {
|
||||||
|
provider = "opencode-go";
|
||||||
|
base_url = "https://opencode.ai/zen/go/v1";
|
||||||
|
default = "gpt-5.6-luna";
|
||||||
|
api_mode = "codex_responses";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings.platforms.telegram = {
|
||||||
|
enabled = true;
|
||||||
|
home_channel = {
|
||||||
|
platform = "telegram";
|
||||||
|
chat_id = "15151223";
|
||||||
|
name = "Erik Simon";
|
||||||
|
user_id = "15151223";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFiles = [ config.sops.templates."hermes-agent.env".path ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.hermes-agent.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
}
|
||||||
@@ -42,4 +42,43 @@
|
|||||||
sops.templates."gitea-runner.env".content =
|
sops.templates."gitea-runner.env".content =
|
||||||
"TOKEN=${config.sops.placeholder.gitea_runner_token}";
|
"TOKEN=${config.sops.placeholder.gitea_runner_token}";
|
||||||
|
|
||||||
|
# provisioning access token for gitea used to setup ci-bot account + repo access
|
||||||
|
sops.secrets.gitea_provisioning_token.owner = "gitea";
|
||||||
|
|
||||||
|
# ci-bot access token to allow the ci-bot user to push to repos
|
||||||
|
sops.secrets.gitea_ci_bot_token.owner = "gitea";
|
||||||
|
|
||||||
|
# SABnzbd credentials (web UI login, API keys, eweka.nl usenet server) —
|
||||||
|
# migrated off the reused ini in services/media/sabnzbd.nix into
|
||||||
|
# services.sabnzbd.settings + secretValues. sabnzbd_api_key predates this
|
||||||
|
# migration (provisioned for mediamanager's future use, services/experimental/
|
||||||
|
# mediamanager.nix — not currently imported by any host); reused here as the
|
||||||
|
# same single source of truth rather than duplicating it.
|
||||||
|
# owner = sabnzbd: the module's preStart (replace-secret) runs as the
|
||||||
|
# service's own User=/Group=, and sops secrets default to root:root 0400 —
|
||||||
|
# without this, replace-secret gets Permission denied reading /run/secrets.
|
||||||
|
sops.secrets.sabnzbd_web_username.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_web_password.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_api_key.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_nzb_key.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_eweka_username.owner = "sabnzbd";
|
||||||
|
sops.secrets.sabnzbd_eweka_password.owner = "sabnzbd";
|
||||||
|
|
||||||
|
# Hermes Agent (hosts/jupiter/hermes-agent.nix) — a separate, isolated
|
||||||
|
# instance from terra's, with its OWN Telegram bot token (sharing terra's
|
||||||
|
# would 409-conflict two long-pollers on one token). opencode_go_api_key
|
||||||
|
# is the same provider account as terra (hosts/terra/secrets.nix) — a
|
||||||
|
# stateless API key, fine to duplicate across hosts. No owner override:
|
||||||
|
# sops.templates renders via a root-run activation script, which the
|
||||||
|
# hermes module's own activation script (also root) then reads — unlike
|
||||||
|
# sabnzbd's preStart, this doesn't run as the service's own user.
|
||||||
|
sops.secrets.opencode_go_api_key = { };
|
||||||
|
sops.secrets.telegram_bot_token = { };
|
||||||
|
sops.templates."hermes-agent.env".content = ''
|
||||||
|
OPENCODE_GO_API_KEY=${config.sops.placeholder.opencode_go_api_key}
|
||||||
|
TELEGRAM_BOT_TOKEN=${config.sops.placeholder.telegram_bot_token}
|
||||||
|
TELEGRAM_HOME_CHANNEL=15151223
|
||||||
|
TELEGRAM_ALLOWED_USERS=15151223
|
||||||
|
'';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ in
|
|||||||
./disk-config.nix
|
./disk-config.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
../../common.nix
|
../../common.nix
|
||||||
|
../../services/containers.nix
|
||||||
../../services/vpn/tailscale.nix
|
../../services/vpn/tailscale.nix
|
||||||
../../services/desktop/desktop-hyprland.nix
|
../../services/desktop/desktop-hyprland.nix
|
||||||
../../services/desktop/desktop-apps.nix
|
../../services/desktop/desktop-apps.nix
|
||||||
|
../../services/desktop/librechat.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
networking.hostName = "terra";
|
networking.hostName = "terra";
|
||||||
@@ -25,10 +27,10 @@ in
|
|||||||
remotes = [{ name = "flathub"; location = "https://dl.flathub.org/repo/flathub.flatpakrepo"; }];
|
remotes = [{ name = "flathub"; location = "https://dl.flathub.org/repo/flathub.flatpakrepo"; }];
|
||||||
packages = [
|
packages = [
|
||||||
{ appId = "com.github.tchx84.Flatseal"; origin = "flathub"; }
|
{ appId = "com.github.tchx84.Flatseal"; origin = "flathub"; }
|
||||||
{ appId = "com.blitzfc.qbz"; origin = "flathub"; }
|
{ appId = "com.blitzfc.qbz"; origin = "flathub"; }
|
||||||
{ appId = "com.discordapp.Discord"; origin = "flathub"; }
|
{ appId = "com.discordapp.Discord"; origin = "flathub"; }
|
||||||
{ appId = "org.telegram.desktop"; origin = "flathub"; }
|
{ appId = "org.telegram.desktop"; origin = "flathub"; }
|
||||||
{ appId = "com.bambulab.BambuStudio"; origin = "flathub"; }
|
{ appId = "com.bambulab.BambuStudio"; origin = "flathub"; }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +64,56 @@ in
|
|||||||
hardware.enableRedistributableFirmware = true;
|
hardware.enableRedistributableFirmware = true;
|
||||||
boot.initrd.kernelModules = [ "amdgpu" ];
|
boot.initrd.kernelModules = [ "amdgpu" ];
|
||||||
|
|
||||||
|
# /dev/dri/renderD128 is root:render 0660, so rootless podman containers can
|
||||||
|
# only reach the GPU if the *host* user is in render. Needed by the Vulkan
|
||||||
|
# whisper.cpp/llama.cpp containers in ~/Data/Dev/repos/content-trigger-scanner.
|
||||||
|
users.users.darman.extraGroups = [ "render" "video" ];
|
||||||
|
|
||||||
|
# ---- ollama (local LLM server, ROCm on the 6800 XT) ----
|
||||||
|
# Navi 21 is gfx1030 — officially supported by ROCm, so no
|
||||||
|
# rocmOverrideGfx/HSA_OVERRIDE_GFX_VERSION needed (that's for gpus ROCm
|
||||||
|
# doesn't recognize, e.g. RDNA1/gfx101x). The upstream module runs the
|
||||||
|
# service under DynamicUser with SupplementaryGroups=["render"] and
|
||||||
|
# DeviceAllow for char-kfd/char-drm/char-fb already, so unlike jellyfin's
|
||||||
|
# static user it needs no extraGroups wiring here.
|
||||||
|
services.ollama = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.ollama-rocm;
|
||||||
|
# keep in sync with services/desktop/librechat.nix's endpoints.custom
|
||||||
|
# default model — LibreChat's config schema needs a non-empty default
|
||||||
|
# even though fetch=true replaces it with whatever's actually pulled.
|
||||||
|
# gemma4:12b: general chat/coding daily driver, fits fully in 16G VRAM —
|
||||||
|
# also doubles as the memory-extraction agent (see librechat.nix): a
|
||||||
|
# 3b model (llama3.2:3b, dropped) couldn't reliably tell the user's
|
||||||
|
# stated facts apart from its own boilerplate, e.g. saving "I am an AI
|
||||||
|
# assistant with tool calling capabilities" as the user's personal_info
|
||||||
|
# after "Hi I'm Erik Simon". Reusing gemma4:12b for both roles also means
|
||||||
|
# no second model needs to swap into VRAM while it's already the active
|
||||||
|
# chat model.
|
||||||
|
# qwen3.6:35b-a3b: MoE (3B active/36B total), ~24GB Q4_K_M — doesn't fit
|
||||||
|
# in VRAM alone, so ollama offloads the inactive experts to CPU RAM.
|
||||||
|
# Sparse activation makes that far less painful than it'd be for a dense
|
||||||
|
# model this size, but still expect it to run slower than the two above.
|
||||||
|
loadModels = [ "gemma4:12b" "qwen3.6:35b-a3b" ];
|
||||||
|
# Ollama truncates context far below the model's real window unless
|
||||||
|
# told otherwise (the OpenAI-compat /v1 route it's reached through has
|
||||||
|
# no way to set this per-request). 131072 chosen as the practical
|
||||||
|
# ceiling after load-testing with real prompts, not just idle
|
||||||
|
# `ollama ps` checks:
|
||||||
|
# 32768 (31.6k-token prompt) and 65536 (40.8k-token prompt) both stayed
|
||||||
|
# 100% GPU with VRAM barely moving (~10.1G / ~10.67G of 16G) — KV cache
|
||||||
|
# cost barely grows with context, likely sliding-window/local attention
|
||||||
|
# on most of gemma4:12b's layers. At 131072 that stopped being true: a
|
||||||
|
# ~108k-token prompt pushed VRAM to ~11.4G/16G (still 100% GPU, no CPU
|
||||||
|
# spillover, negligible GTT) but with visibly shrinking headroom, and
|
||||||
|
# prefill throughput measurably dropped (~490 -> ~460 tok/s) over just
|
||||||
|
# the last 13k tokens — filling the full window would take minutes of
|
||||||
|
# pure prompt processing. Stopped here rather than push further: next
|
||||||
|
# doubling would risk CPU spillover under any concurrent GPU load
|
||||||
|
# (desktop compositor, jellyfin transcode) for diminishing benefit.
|
||||||
|
environmentVariables.OLLAMA_CONTEXT_LENGTH = "131072";
|
||||||
|
};
|
||||||
|
|
||||||
# ---- Dev-data disks — NOT in disko, mounted read-write, never wiped ----
|
# ---- Dev-data disks — NOT in disko, mounted read-write, never wiped ----
|
||||||
fileSystems."/mnt/hdd_01" = {
|
fileSystems."/mnt/hdd_01" = {
|
||||||
device = "/dev/disk/by-uuid/b8445126-ec6d-4f88-818a-d9e13031d9a4";
|
device = "/dev/disk/by-uuid/b8445126-ec6d-4f88-818a-d9e13031d9a4";
|
||||||
|
|||||||
+22
-2
@@ -1,5 +1,7 @@
|
|||||||
{ pkgs, unstable, ... }:
|
{ pkgs, unstable, inputs, ... }:
|
||||||
|
let
|
||||||
|
tome = pkgs.callPackage ../../pkgs/tome.nix { src = inputs.tome; };
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./home/hyprland.nix ./home/theme.nix ];
|
imports = [ ./home/hyprland.nix ./home/theme.nix ];
|
||||||
|
|
||||||
@@ -17,6 +19,22 @@
|
|||||||
|
|
||||||
programs.zsh.enable = true;
|
programs.zsh.enable = true;
|
||||||
|
|
||||||
|
# direnv + nix-direnv: lets per-repo devShells (e.g. ~/Data/Dev/repos/Tome's
|
||||||
|
# flake.nix) auto-load in the shell AND in Rider via its "direnv
|
||||||
|
# integration" plugin, instead of every dev repo needing its own
|
||||||
|
# jetbrains-toolbox SDK wiring by hand.
|
||||||
|
programs.direnv = {
|
||||||
|
enable = true;
|
||||||
|
nix-direnv.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Rootless podman: containers run as darman, not root. services/containers.nix
|
||||||
|
# gives us the `docker` CLI shim (dockerCompat), but compose v2 is a separate
|
||||||
|
# binary and talks to a socket rather than the CLI — the NixOS podman module
|
||||||
|
# enables the *user* socket (systemd.user.sockets.podman), so point compose at
|
||||||
|
# it instead of the root /var/run/docker.sock.
|
||||||
|
home.sessionVariables.DOCKER_HOST = "unix:///run/user/1000/podman/podman.sock";
|
||||||
|
|
||||||
xdg.userDirs = {
|
xdg.userDirs = {
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
@@ -33,9 +51,11 @@
|
|||||||
pkgs.quickshell
|
pkgs.quickshell
|
||||||
pkgs.github-cli
|
pkgs.github-cli
|
||||||
pkgs.tea
|
pkgs.tea
|
||||||
|
pkgs.docker-compose
|
||||||
pkgs.hyprcursor
|
pkgs.hyprcursor
|
||||||
pkgs.bibata-cursors
|
pkgs.bibata-cursors
|
||||||
pkgs.papirus-icon-theme
|
pkgs.papirus-icon-theme
|
||||||
|
tome
|
||||||
];
|
];
|
||||||
|
|
||||||
xdg.desktopEntries.btop = {
|
xdg.desktopEntries.btop = {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ let
|
|||||||
# here rather than at runtime, since hyprpaper has no built-in "random"
|
# here rather than at runtime, since hyprpaper has no built-in "random"
|
||||||
# mode; re-pick and rebuild (or swap in real per-monitor selection) when
|
# mode; re-pick and rebuild (or swap in real per-monitor selection) when
|
||||||
# this stops being a placeholder.
|
# this stops being a placeholder.
|
||||||
wallpaper = "/mnt/hdd_01/data/Pictures/Wallhaven/wallhaven-g7jg63.png";
|
wallpaper = "/mnt/hdd_01/data/Pictures/Wallhaven/wallhaven-4yjyd4.png";
|
||||||
|
|
||||||
# Dispatchers → the new hl.dsp.* API (signatures verified against hyprland
|
# Dispatchers → the new hl.dsp.* API (signatures verified against hyprland
|
||||||
# 0.55's src/config/lua/bindings/LuaBindingsDispatchers.cpp).
|
# 0.55's src/config/lua/bindings/LuaBindingsDispatchers.cpp).
|
||||||
@@ -93,9 +93,10 @@ in
|
|||||||
settings = {
|
settings = {
|
||||||
# ---- colours (from colors.conf) ----
|
# ---- colours (from colors.conf) ----
|
||||||
fg_color = { _var = "rgba(eeeeeeff)"; };
|
fg_color = { _var = "rgba(eeeeeeff)"; };
|
||||||
fg_accent = { _var = "rgba(FFD063ff)"; };
|
fg_accent = { _var = "rgba(ffd063ff)"; };
|
||||||
bg_color = { _var = "rgba(0F1012ff)"; };
|
fg_accent_alt = { _var = "rgba(ff9d42ff)"; };
|
||||||
bg_accent = { _var = "rgba(40382fff)"; };
|
bg_color = { _var = "rgba(0f1012ff)"; };
|
||||||
|
bg_accent = { _var = "rgba(963c38ff)"; };
|
||||||
|
|
||||||
# ---- monitors ----
|
# ---- monitors ----
|
||||||
monitor = [
|
monitor = [
|
||||||
@@ -115,16 +116,11 @@ in
|
|||||||
debug.disable_logs = false;
|
debug.disable_logs = false;
|
||||||
|
|
||||||
general = {
|
general = {
|
||||||
border_size = 1;
|
border_size = 0;
|
||||||
col = {
|
col = {
|
||||||
inactive_border = lua "bg_accent";
|
inactive_border = lua "bg_accent";
|
||||||
# Gradient: hyprland's lua gradient type (CLuaConfigGradient::parse)
|
|
||||||
# takes either a plain colour string or a table with a `colors`
|
|
||||||
# array (1+ entries) and an optional `angle` in DEGREES. The list
|
|
||||||
# entries may be literal "rgba(...)" strings or, as here, the
|
|
||||||
# `_var` locals declared above rendered raw via mkLuaInline.
|
|
||||||
active_border = {
|
active_border = {
|
||||||
colors = [ (lua "fg_accent") (lua "bg_accent") ];
|
colors = [ (lua "fg_accent") (lua "fg_accent_alt") ];
|
||||||
angle = 45;
|
angle = 45;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -162,7 +158,16 @@ in
|
|||||||
allow_workspace_cycles = true;
|
allow_workspace_cycles = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
plugin.hyprchrome.enabled = true;
|
plugin.hyprchrome = {
|
||||||
|
enabled = true;
|
||||||
|
glow_size = 12;
|
||||||
|
glow_strength = 0.85;
|
||||||
|
shadow_size = 24;
|
||||||
|
shadow_color = lua "bg_color";
|
||||||
|
shadow_offset = lua "{ 4, 8 }";
|
||||||
|
outline_size = lua "2";
|
||||||
|
outline_color = lua "fg_color";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# ---- animations ----
|
# ---- animations ----
|
||||||
|
|||||||
@@ -21,4 +21,14 @@
|
|||||||
username=darman
|
username=darman
|
||||||
password=${config.sops.placeholder.samba_password}
|
password=${config.sops.placeholder.samba_password}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# LibreChat's CREDS_KEY/IV encrypt stored user credentials (linked 3rd-party
|
||||||
|
# API keys etc) at rest in mongo; JWT_SECRET/JWT_REFRESH_SECRET sign session
|
||||||
|
# tokens. All four are random, generated once with `sops --set` (see
|
||||||
|
# CLAUDE.md) — losing/rotating them just invalidates existing sessions and
|
||||||
|
# any saved per-user API keys, nothing else depends on their value.
|
||||||
|
sops.secrets.librechat_creds_key = { };
|
||||||
|
sops.secrets.librechat_creds_iv = { };
|
||||||
|
sops.secrets.librechat_jwt_secret = { };
|
||||||
|
sops.secrets.librechat_jwt_refresh_secret = { };
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-20
@@ -41,23 +41,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Ref",
|
"pname": "Microsoft.AspNetCore.App.Ref",
|
||||||
"version": "8.0.27",
|
"version": "8.0.29",
|
||||||
"hash": "sha256-nwBrMFATFwpJS1iq9Bf+vvWQ1dDGergMuY809tUqo60="
|
"hash": "sha256-0wK5Lsa4a1ani/gvSzsqyuY3R4MBuH/9XOyZeWCUWoU="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Ref",
|
"pname": "Microsoft.AspNetCore.App.Ref",
|
||||||
"version": "9.0.16",
|
"version": "9.0.18",
|
||||||
"hash": "sha256-lBbgyPyZOrPsRMtd0UOHJuB5dbMQFysVIk4RAFx2Rk0="
|
"hash": "sha256-0qkb9Gbxlyyw8rKxDTm9OqdW89DIi1qolGK85HLwc2k="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
||||||
"version": "8.0.27",
|
"version": "8.0.29",
|
||||||
"hash": "sha256-7DX4XBTx8a6sFRnTrJ3zJhJzQVC81OwzOZnYOo+nO20="
|
"hash": "sha256-iiDWZa7MkybKwozVKIV4Eq0PGzirxeyjFLnoxPyAHiQ="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
|
||||||
"version": "9.0.16",
|
"version": "9.0.18",
|
||||||
"hash": "sha256-JDdPuh01rffoWnKekJU34/QKWFFnyTljmqq6DM5vYs8="
|
"hash": "sha256-ETNi+pMo8nNSgHMLKgi/VFMla/duHI2CQlJlm1FdyKc="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.Bcl.TimeProvider",
|
"pname": "Microsoft.Bcl.TimeProvider",
|
||||||
@@ -216,33 +216,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
||||||
"version": "8.0.27",
|
"version": "8.0.29",
|
||||||
"hash": "sha256-ZI5ByoSqJIcQAnH1dyGcK8uyvPB7yUNznD6BLY2V8Hs="
|
"hash": "sha256-T/eXkzT3V3T1Yasc1cUZ/jLOWJY3zp/GxJTCT24gnAw="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
"pname": "Microsoft.NETCore.App.Host.linux-x64",
|
||||||
"version": "9.0.16",
|
"version": "9.0.18",
|
||||||
"hash": "sha256-HaIx6pwpKUwdseu3tOhuVtqnpGCubhWbi9BEvijHd+M="
|
"hash": "sha256-h36uAGfY36RKon6QU3lP3tsVlMVZ842lvdGiLcHO7Ko="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Ref",
|
"pname": "Microsoft.NETCore.App.Ref",
|
||||||
"version": "8.0.27",
|
"version": "8.0.29",
|
||||||
"hash": "sha256-F/FL0ptluwCfxN4S93/UAKs4fRtyL+D4NoSPc5CGyJo="
|
"hash": "sha256-dxAuEUU1VOElQ4CpL9HLhV4KVFtRDAcZS8W0GT2Di6g="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Ref",
|
"pname": "Microsoft.NETCore.App.Ref",
|
||||||
"version": "9.0.16",
|
"version": "9.0.18",
|
||||||
"hash": "sha256-VLwChaPID3roiQw6qU8IuPaUOPAHl3wzCIjVtG4D6ZM="
|
"hash": "sha256-ZFU4lXz/BjJiqU4sykZEYR5j5e745KMf4ZPo0GHnicU="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
||||||
"version": "8.0.27",
|
"version": "8.0.29",
|
||||||
"hash": "sha256-GejthwcyJAmNTFvWEZXis6lRM2sJyNrKHYsq79Fn/WI="
|
"hash": "sha256-eA5x9NMfCg6JhRmOKKqfBL8+TwtQW/1ONvJeANPJOFA="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
|
||||||
"version": "9.0.16",
|
"version": "9.0.18",
|
||||||
"hash": "sha256-3nDEdBN1jHIy2PiLffnE4+Snt7MiGP2hEDEi924DXww="
|
"hash": "sha256-DV5iYmqH8j7JIcem8Qu/HLbZuHPVmaTTQuZc8OoDZBk="
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pname": "Microsoft.SourceLink.Common",
|
"pname": "Microsoft.SourceLink.Common",
|
||||||
|
|||||||
+18
-2
@@ -3,11 +3,13 @@
|
|||||||
, buildNpmPackage
|
, buildNpmPackage
|
||||||
, importNpmLock
|
, importNpmLock
|
||||||
, dotnetCorePackages
|
, dotnetCorePackages
|
||||||
|
, glib
|
||||||
, gtk3
|
, gtk3
|
||||||
, webkitgtk_4_1
|
, webkitgtk_4_1
|
||||||
, libnotify
|
, libnotify
|
||||||
, makeDesktopItem
|
, makeDesktopItem
|
||||||
, copyDesktopItems
|
, copyDesktopItems
|
||||||
|
, wrapGAppsHook3
|
||||||
, src
|
, src
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -45,15 +47,29 @@ buildDotnetModule (finalAttrs: {
|
|||||||
nugetDeps = ./tome-deps.json;
|
nugetDeps = ./tome-deps.json;
|
||||||
|
|
||||||
dotnet-sdk = dotnetCorePackages.sdk_10_0;
|
dotnet-sdk = dotnetCorePackages.sdk_10_0;
|
||||||
dotnet-runtime = dotnetCorePackages.runtime_10_0;
|
# aspnetcore_10_0, not runtime_10_0: Tome.App's runtimeconfig.json requires
|
||||||
|
# both Microsoft.NETCore.App AND Microsoft.AspNetCore.App (Photino hosts a
|
||||||
|
# local Kestrel server), and only the aspnetcore bundle ships the latter.
|
||||||
|
dotnet-runtime = dotnetCorePackages.aspnetcore_10_0;
|
||||||
|
|
||||||
dotnetFlags = [ "-p:SkipNpmBuild=true" ];
|
dotnetFlags = [ "-p:SkipNpmBuild=true" ];
|
||||||
|
|
||||||
executables = [ "Tome.App" ];
|
executables = [ "Tome.App" ];
|
||||||
|
|
||||||
nativeBuildInputs = [ copyDesktopItems ];
|
# wrapGAppsHook3: buildDotnetModule sets dontWrapGApps = true by default (to
|
||||||
|
# avoid double-wrapping) but its own wrap step still splices gappsWrapperArgs
|
||||||
|
# in when the hook is present (see nixpkgs' libation package, same pattern).
|
||||||
|
# Without it the binary never gets XDG_DATA_DIRS/GSETTINGS_SCHEMA_DIR set, so
|
||||||
|
# GTK/WebKitGTK can't find the icon theme or GTK settings from the desktop
|
||||||
|
# session — symptoms: missing icons and a denser default UI font/size than
|
||||||
|
# when launched from an already-fully-initialized session (e.g. via Rider).
|
||||||
|
nativeBuildInputs = [ copyDesktopItems wrapGAppsHook3 ];
|
||||||
|
|
||||||
runtimeDeps = [
|
runtimeDeps = [
|
||||||
|
# glib: not pulled in via gtk3/webkitgtk's own RPATH here, because the
|
||||||
|
# thing that needs it — Photino.Native.so — is a prebuilt binary shipped
|
||||||
|
# in the Photino.Native nuget package, not something Nix built/patched.
|
||||||
|
glib
|
||||||
gtk3
|
gtk3
|
||||||
webkitgtk_4_1
|
webkitgtk_4_1
|
||||||
libnotify
|
libnotify
|
||||||
|
|||||||
+12
-3
@@ -7,6 +7,15 @@ prowlarr_api_key: ENC[AES256_GCM,data:ab1QACaagI8ACJXFUy8r9X9lgYwRo1byUmhBPSRWwK
|
|||||||
cinephage_better_auth_secret: ENC[AES256_GCM,data:S1ilcQeC2HmXe/4xdLi6wm5RNz954SL3qVur6JCn5ekBVCbXMd1DGCafjhU=,iv:9rS5gDuazMOAq/hWp0onvHZPzKJgQM3oWIrtplJN/9I=,tag:xNqf/unY2v/98p4v52vUqw==,type:str]
|
cinephage_better_auth_secret: ENC[AES256_GCM,data:S1ilcQeC2HmXe/4xdLi6wm5RNz954SL3qVur6JCn5ekBVCbXMd1DGCafjhU=,iv:9rS5gDuazMOAq/hWp0onvHZPzKJgQM3oWIrtplJN/9I=,tag:xNqf/unY2v/98p4v52vUqw==,type:str]
|
||||||
immich_oauth_client_secret: ENC[AES256_GCM,data:+NbUnwImwFTYNjz3luzczpCf7oMetzYBkj5ZnuG2QQf0Wpm6OtYS3amTC8dwoh9F/DAos5224etncfEgEu2k2iMUACLADnlCGppIx0F7Gl1Ve7UF2VzKJ3xQpgCDrXklU+o5NxfU/YBn1Vfa3580wT3tr2++SCSrcKq1XGtfhv4=,iv:tjaPDQbrA6TxsDebgNOtO/ITfXzU5wTKU9SkfC0TQcY=,tag:AnlJR0tLExkB7Aeo/ZVTng==,type:str]
|
immich_oauth_client_secret: ENC[AES256_GCM,data:+NbUnwImwFTYNjz3luzczpCf7oMetzYBkj5ZnuG2QQf0Wpm6OtYS3amTC8dwoh9F/DAos5224etncfEgEu2k2iMUACLADnlCGppIx0F7Gl1Ve7UF2VzKJ3xQpgCDrXklU+o5NxfU/YBn1Vfa3580wT3tr2++SCSrcKq1XGtfhv4=,iv:tjaPDQbrA6TxsDebgNOtO/ITfXzU5wTKU9SkfC0TQcY=,tag:AnlJR0tLExkB7Aeo/ZVTng==,type:str]
|
||||||
gitea_runner_token: ENC[AES256_GCM,data:8ji4Nia7GMCBBsemUeGZRqzhlk1RnzzOLLdo7+to85KIC5Kz4AtDsQ==,iv:2wotlB1B/Co/NrZVcIVB4AlwL7DF9KnEVKe32FJNErU=,tag:zLzKPgGmXCkGfK7P/74pyw==,type:str]
|
gitea_runner_token: ENC[AES256_GCM,data:8ji4Nia7GMCBBsemUeGZRqzhlk1RnzzOLLdo7+to85KIC5Kz4AtDsQ==,iv:2wotlB1B/Co/NrZVcIVB4AlwL7DF9KnEVKe32FJNErU=,tag:zLzKPgGmXCkGfK7P/74pyw==,type:str]
|
||||||
|
gitea_provisioning_token: ENC[AES256_GCM,data:aVzD+3qb0eAuGCNIXgzR338jMz9MqXun3nbgfZAirewDwT3D7T5T0Q==,iv:OOeDRk+4CHQyRh09qgUp7I4vcrvuqaAPAh5HgJ10Uvo=,tag:2Yey9e9WZteERDoqkIppWQ==,type:str]
|
||||||
|
gitea_ci_bot_token: ENC[AES256_GCM,data:isgOYuA8S6w7WCUr2i2tW4F+b8mCRh8e+rjFJtM1fXEkkUIRNaiADA==,iv:W5IoxhuPCoTP2wLhedu4RYKrC8tBFFJ4B+QvNW3jjTc=,tag:ztxEwDdGJLc/JEkls1jrOA==,type:str]
|
||||||
|
sabnzbd_web_username: ENC[AES256_GCM,data:yNU=,iv:t6Ev0bTLovn3gYtOltS14Y/ElUVCYGxxz8wGsgl9R44=,tag:k2yXYQ2yot1HaohCNDgycg==,type:str]
|
||||||
|
sabnzbd_web_password: ENC[AES256_GCM,data:9Lo=,iv:H0Kz8A534RxX+7/Aue8Q87gCzSY5e/TrdDjeVYgC+Tg=,tag:tvVN6g5DheTN67oWsSBLHQ==,type:str]
|
||||||
|
sabnzbd_nzb_key: ENC[AES256_GCM,data:DNVenqhJ7wf5Ng0XRA1gJN95e+90e6D9NImOSHJv/Us=,iv:eqFn0stB5pqh0ls4/impD8gc/lOkORwEJzRP6m7u1XU=,tag:Zs8ogLBZEZLyMvFBqhfpIA==,type:str]
|
||||||
|
sabnzbd_eweka_username: ENC[AES256_GCM,data:eLsTZoM8T8fAlGaXWlDaoQ==,iv:eawyGhN7+d6UfBIbI3y1qgq+MYBGrXP6VfAkSOK6llA=,tag:ELOfQGHU5NOxZFhKOKf8LA==,type:str]
|
||||||
|
sabnzbd_eweka_password: ENC[AES256_GCM,data:Mt3ZHAe2wzacCQq3x9Uy8WxjrVNad1SmU6sl8ZgrkMLymfq2eP4JzO/uPdD33A==,iv:PnFT95Zxqz4QBpPF5PRloKpoa15AU7Ef/Owwy+iDotw=,tag:/uRX00RzHLJN3gws5Qz8SA==,type:str]
|
||||||
|
opencode_go_api_key: ENC[AES256_GCM,data:7kgWiye0wHCxzKFsrzX2WQNDkSVpuvJN6w5Zw9tuyYj5ysDRnWDjCvQtWEJlBalq+Fz7HfT28uFLFtrjFornGEPPdQ==,iv:9Ue/nMpJozVy7oHvhvHwKNuMlsb3tXjwnpC3jok5IWs=,tag:K2UqXBnMH3lpnATa2A/Agg==,type:str]
|
||||||
|
telegram_bot_token: ENC[AES256_GCM,data:wU3CgKqbO1twJMIAlVi6rzVP5IUu34l1JOBVnlvTzhGL+Teq/sodQ9nlZOkzfg==,iv:8WxWDkGitljLa8aiwiT8td/3WeEnZAvz38oVPF5TQ4I=,tag:x9ldeAJOsTUJqAE4YZMPNg==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
@@ -27,7 +36,7 @@ sops:
|
|||||||
CzjSDQZTcseEXZNwuzZcfB5Mvq0BQvjOj7lGuxzuE4qwWkdJWGfVLQ==
|
CzjSDQZTcseEXZNwuzZcfB5Mvq0BQvjOj7lGuxzuE4qwWkdJWGfVLQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt
|
recipient: age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt
|
||||||
lastmodified: "2026-07-29T19:00:26Z"
|
lastmodified: "2026-08-19T19:44:22Z"
|
||||||
mac: ENC[AES256_GCM,data:D5FLrembFUqs5yC2bHuhw2w8XUhF46LWpN5LF/VJKtLOo3A3KUdGV0HhA9k6gaTWyn5Zd7O9YSuJLknI62fbOtJkqOhMVUgj/VZXgrcQg4hlU53gDppryYNGi/7Vc9lxLfsJU4Yvk6yQfh7Gt19U+Mj6s7ETn6lJxC5FBnIkS3I=,iv:EJAM1cCD4rTq0XjIbo66di73hnOoklx5UoTrpdyKHq0=,tag:wEhFnwlXiYKyu/k/ED75TA==,type:str]
|
mac: ENC[AES256_GCM,data:ZgHrvBYUeUDo7ZydN3K5CbIUXDvbvj1whnSWuzc+x5TlejqQH89zXRMKBuDD2DgzS+ET6PFHgN+0KoQchOI9SVGmdzz0b3mKpAMZc1BXYWOy5OcxF2xToA2Gub+QV3KQ1VsLSuR6bHcPKR6h4vAs1iwRKYAguQqo7LJMKAduuWs=,iv:EhUYS1iZOT9AbTk2A0EfLFjqIjLU7KYd1xHWwbwzUpU=,tag:Lh2yKA2AELEZk8axXdJJiA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.13.2
|
version: 3.13.3
|
||||||
|
|||||||
+6
-2
@@ -1,6 +1,10 @@
|
|||||||
tailscale_authkey: ENC[AES256_GCM,data:DgxbMrKTxC//cWd+ZckCbwZ9j/FgjYwNwadU5cWnnPz+nPEFhXLla2TuUE0pJKpcx9pKZCQmxcGtzQvIB+mbgxjgB0X2CRTp4Yy3WR+PBYMghKx36F8KBg==,iv:hJr6O41OTdMa4wkBXEsRUXgV4zWt21h1kx/CWArLU7o=,tag:IEpMIwKBHakvVYEFdEUe7A==,type:str]
|
tailscale_authkey: ENC[AES256_GCM,data:DgxbMrKTxC//cWd+ZckCbwZ9j/FgjYwNwadU5cWnnPz+nPEFhXLla2TuUE0pJKpcx9pKZCQmxcGtzQvIB+mbgxjgB0X2CRTp4Yy3WR+PBYMghKx36F8KBg==,iv:hJr6O41OTdMa4wkBXEsRUXgV4zWt21h1kx/CWArLU7o=,tag:IEpMIwKBHakvVYEFdEUe7A==,type:str]
|
||||||
darman_password: ENC[AES256_GCM,data:G3ZM+NMxvKq5twblcBvyC+MiUX+X7nz+s1GqBHBkJQy1YgW4KN6rpbdj10F9jWxHph5dJ9j9fG6L7UlEg5W5zUYeLFdEx2xJGE1fxksch+6BB9FT6LKkhJ7MmbJmhNHYwJOZO3LM1f/oRw==,iv:abQTRe9kRyYj+TL0rtoxM6JFBaIBmxu7y77qt1h4eME=,tag:OgKrB3SUgSNJjmtJyrmh/Q==,type:str]
|
darman_password: ENC[AES256_GCM,data:G3ZM+NMxvKq5twblcBvyC+MiUX+X7nz+s1GqBHBkJQy1YgW4KN6rpbdj10F9jWxHph5dJ9j9fG6L7UlEg5W5zUYeLFdEx2xJGE1fxksch+6BB9FT6LKkhJ7MmbJmhNHYwJOZO3LM1f/oRw==,iv:abQTRe9kRyYj+TL0rtoxM6JFBaIBmxu7y77qt1h4eME=,tag:OgKrB3SUgSNJjmtJyrmh/Q==,type:str]
|
||||||
samba_password: ENC[AES256_GCM,data:UkJLUa2hW1iZ++sfJAcg6G1RJMM=,iv:/HbZ9F+GxCydUP50PNBtJknPlmDWh1DAE26N9FJUyb0=,tag:z1cbwUaVdyfwEIU8LINEcw==,type:str]
|
samba_password: ENC[AES256_GCM,data:UkJLUa2hW1iZ++sfJAcg6G1RJMM=,iv:/HbZ9F+GxCydUP50PNBtJknPlmDWh1DAE26N9FJUyb0=,tag:z1cbwUaVdyfwEIU8LINEcw==,type:str]
|
||||||
|
librechat_creds_key: ENC[AES256_GCM,data:e2Ptf41yHu0KxfzjW4DP04CSJfbtdsJ8bwrgJyv9up4/JSCbBzjPFmOi7jsHUL4jT0AGVuHG5w3y+YOil9EeNA==,iv:zneozNSkXsb4Vy/sq21b8HWCKpDkXVTxyLY2Zh0bwP0=,tag:6CxR5sIspbfzHlfdx+45Iw==,type:str]
|
||||||
|
librechat_creds_iv: ENC[AES256_GCM,data:iuW1Rxfu7Ei8zHVG7wsBTYYznbtErj7DC9B71OiiWGA=,iv:Ngs88C44gcSzxQZU1lMiy5kn9mM/ckuWKRRGOFJeeoQ=,tag:Q9B8vM4YMkIP3RyYVWSQqg==,type:str]
|
||||||
|
librechat_jwt_secret: ENC[AES256_GCM,data:f/gljrQPZIXeLHXtqKCCYGEu2pXgbZd69CjyEhSVJ+AMuaVj7DWVclD5IMsEVERBHw5a7OndBSfLvgXRaKN6Gg==,iv:DG5CwCbQLCTv1++APCdFzAiWGKiEJl+9MhKIM2ykJQM=,tag:wdpY6l+5h4UtsluIncsyhA==,type:str]
|
||||||
|
librechat_jwt_refresh_secret: ENC[AES256_GCM,data:N/yAPtaodJX4t2C2A0bZ/LQaVc8SBMgg3KYJrWRyXCM6HAmgHtSQ9nWJO2fl+7A966mMtYvpATYcBIPj/K5nig==,iv:k5ZYi2OKjL9Z+lhVgdNsmGVQm+iCqmF15H3TUTbofWk=,tag:NVTdvlL1lpwVhjo2c2phpg==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
@@ -21,7 +25,7 @@ sops:
|
|||||||
sHjKfw8VrrmAR4pQf1dsY+wcyh4FsZxhP3Q+QIVq3eCIXS9PeJkGAg==
|
sHjKfw8VrrmAR4pQf1dsY+wcyh4FsZxhP3Q+QIVq3eCIXS9PeJkGAg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1rfcmu6zh40v4260l9hnf8ajs9vly0s06rx3ey76eu78dp9t7getqyhmkut
|
recipient: age1rfcmu6zh40v4260l9hnf8ajs9vly0s06rx3ey76eu78dp9t7getqyhmkut
|
||||||
lastmodified: "2026-07-28T20:18:44Z"
|
lastmodified: "2026-08-19T20:48:49Z"
|
||||||
mac: ENC[AES256_GCM,data:TRh/Idd8MEq/5QRb7r+g6msdaObtVzsX5hK5gacJI6seZ5BLW5tcBO/E2m8ya4t50lTofxkwObWskMbCOucLDYt+lnYwHSqDvAeQRvswxaCZgiFOn0upqCi8H4OomvIK475SIH6bniyN9bCQ2gaSS0D9aecyCSmdUARkmcZEYLc=,iv:PBoEUZG0QNyj4B0c6zuWr6Y39EdL1CoAsVExU86P6Tw=,tag:NPxsWkxdojXNsoxVVMb5TQ==,type:str]
|
mac: ENC[AES256_GCM,data:YbsOhLmzynV7S86A9w1XqDEe1/soZPewSrsc+5eR49naQNHUrv2j40J271hMh7r2c+gBZkkHAvq/tQy4USaWPdVn44h6+fPYc11lSlfuIGeIDX6KuXcIw4PdrJr5NgHpQ6+l5XASO0vKOifdqUxENEL8Uu5QJflgg6dA+y6RU94=,iv:ZALmRHzSFx9wzgapZVd27/JRCdTYzVdQsqs0eO9+6ko=,tag:9Un6iiPTxy61yNlms+K8Uw==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.13.2
|
version: 3.13.2
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ in
|
|||||||
"claude-code"
|
"claude-code"
|
||||||
"proton-pass-cli"
|
"proton-pass-cli"
|
||||||
"vivaldi"
|
"vivaldi"
|
||||||
|
"mongodb" # librechat's local db (services/desktop/librechat.nix) — SSPL
|
||||||
];
|
];
|
||||||
|
|
||||||
programs.steam = {
|
programs.steam = {
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
# LibreChat — web chat UI, talking to the local ollama server (see
|
||||||
|
# hosts/terra/configuration.nix) over its OpenAI-compatible /v1 route.
|
||||||
|
# Only reachable over the tailnet (networking.firewall.trustedInterfaces =
|
||||||
|
# [ "tailscale0" ] in services/vpn/tailscale.nix) — openFirewall stays off.
|
||||||
|
{
|
||||||
|
services.librechat = {
|
||||||
|
enable = true;
|
||||||
|
enableLocalDB = true; # spins up a local, unauthenticated-on-localhost mongodb
|
||||||
|
|
||||||
|
# LibreChat's isEnabled() treats an UNSET var as false, not true — so
|
||||||
|
# registration is closed unless this is explicit, despite .env.example
|
||||||
|
# suggesting true is the default. Only reachable over the tailnet
|
||||||
|
# (trusted interface, see module comment below), so leaving it open is
|
||||||
|
# fine; flip to false once your account exists if you want it locked down.
|
||||||
|
env.ALLOW_REGISTRATION = true;
|
||||||
|
|
||||||
|
credentials = {
|
||||||
|
CREDS_KEY = config.sops.secrets.librechat_creds_key.path;
|
||||||
|
CREDS_IV = config.sops.secrets.librechat_creds_iv.path;
|
||||||
|
JWT_SECRET = config.sops.secrets.librechat_jwt_secret.path;
|
||||||
|
JWT_REFRESH_SECRET = config.sops.secrets.librechat_jwt_refresh_secret.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
version = "1.2.1";
|
||||||
|
endpoints.custom = [
|
||||||
|
{
|
||||||
|
name = "Ollama";
|
||||||
|
# required field but unchecked by ollama's OpenAI-compat shim
|
||||||
|
apiKey = "ollama";
|
||||||
|
baseURL = "http://127.0.0.1:11434/v1";
|
||||||
|
models = {
|
||||||
|
# schema requires >=1 entry even though fetch=true overwrites it
|
||||||
|
# at runtime with whatever's pulled (see loadModels in
|
||||||
|
# hosts/terra/configuration.nix) — kept roughly in sync anyway
|
||||||
|
# so the UI has sane names before the first fetch completes.
|
||||||
|
default = [ "gemma4:12b" "qwen3.6:35b-a3b" ];
|
||||||
|
fetch = true; # pull the model list from ollama at startup
|
||||||
|
};
|
||||||
|
titleConvo = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Persistent memory is opt-in at the CONFIG level — omitting this block
|
||||||
|
# (as before) leaves the feature entirely off, no matter what a user
|
||||||
|
# toggles in Settings > Personalization. `agent.provider` must match
|
||||||
|
# endpoints.custom[].name above exactly ("Ollama"), which is how the
|
||||||
|
# memory-extraction agent picks a backend/model.
|
||||||
|
memory = {
|
||||||
|
personalize = true; # still needs a per-user opt-in toggle in the UI
|
||||||
|
# instructions REPLACES the default extraction prompt entirely (not
|
||||||
|
# appended to it) — the 3b model (llama3.2:3b, dropped) was
|
||||||
|
# defaulting to saving things like its own "I am a helpful
|
||||||
|
# assistant..." boilerplate under an invented "user_conversation"
|
||||||
|
# key, and even after adding this prompt, still saved "I am an AI
|
||||||
|
# assistant with tool calling capabilities" as personal_info after
|
||||||
|
# the user introduced THEMSELVES — a capability ceiling, not a
|
||||||
|
# prompting problem. validKeys constrains it to a fixed whitelist
|
||||||
|
# and instructions spells out the bar for each one.
|
||||||
|
validKeys = [ "user_preferences" "personal_info" "ongoing_projects" "technical_context" ];
|
||||||
|
agent = {
|
||||||
|
enabled = true;
|
||||||
|
provider = "Ollama";
|
||||||
|
# same model as the chat endpoint's primary driver — when that's
|
||||||
|
# the active chat model, extraction needs no second model swapped
|
||||||
|
# into VRAM alongside it.
|
||||||
|
model = "gemma4:12b";
|
||||||
|
instructions = ''
|
||||||
|
Save memory ONLY using the keys below, and only when the user's
|
||||||
|
message states something durable and genuinely useful to recall
|
||||||
|
in a LATER, unrelated conversation. Small talk, greetings, and
|
||||||
|
anything about what the assistant said or is capable of are NOT
|
||||||
|
memories — if nothing meets the bar, save nothing.
|
||||||
|
|
||||||
|
set_memory REPLACES the entire value stored at a key — it does
|
||||||
|
NOT append to it. Before calling set_memory for a key, check the
|
||||||
|
"Existing memory" section below. If that key already has a
|
||||||
|
value, your new value MUST merge the old and new information
|
||||||
|
into one complete sentence or short paragraph — calling
|
||||||
|
set_memory with only the newest fact silently ERASES everything
|
||||||
|
already stored under that key. Only drop prior details if the
|
||||||
|
user is explicitly correcting or replacing them.
|
||||||
|
|
||||||
|
- user_preferences: explicitly stated preferences (tools, formats, style).
|
||||||
|
- personal_info: durable facts about the user (name, role, timezone).
|
||||||
|
- ongoing_projects: projects or tasks the user is actively working on.
|
||||||
|
- technical_context: durable facts about the user's setup/stack
|
||||||
|
relevant to future answers (e.g. "runs NixOS", "GPU is AMD ROCm").
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+101
-1
@@ -1,4 +1,4 @@
|
|||||||
{ config, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
# Gitea — self-hosted git. stateDir/repositories were migrated from the old
|
# Gitea — self-hosted git. stateDir/repositories were migrated from the old
|
||||||
# ZimaOS docker instance straight into stateDir's default layout, so no
|
# ZimaOS docker instance straight into stateDir's default layout, so no
|
||||||
@@ -9,6 +9,12 @@
|
|||||||
# HTTP is reverse-proxied through Caddy (hosts/jupiter/configuration.nix).
|
# HTTP is reverse-proxied through Caddy (hosts/jupiter/configuration.nix).
|
||||||
# SSH uses gitea's own built-in server on :2222 (not the host's :22, and not
|
# SSH uses gitea's own built-in server on :2222 (not the host's :22, and not
|
||||||
# :222 — the unpriv gitea user can't bind <1024).
|
# :222 — the unpriv gitea user can't bind <1024).
|
||||||
|
let
|
||||||
|
# Repos where the ci-bot account (see below) should be a Write collaborator
|
||||||
|
# and whitelisted to push past branch protection. Add a repo here and
|
||||||
|
# redeploy — no manual UI clicking needed.
|
||||||
|
ciBotRepos = [ "darman/hypr-chrome" ];
|
||||||
|
in
|
||||||
{
|
{
|
||||||
services.gitea = {
|
services.gitea = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -66,4 +72,98 @@
|
|||||||
"ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04"
|
"ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# ci-bot: dedicated account CI workflows push as (kept separate from any
|
||||||
|
# human account so its own PAT can be scoped/rotated/revoked independently).
|
||||||
|
# Collaborator access + branch-protection push-whitelisting have no CLI or
|
||||||
|
# config-file surface in gitea — only the HTTP API — so this is the one
|
||||||
|
# part of the setup that stays imperative even though it's nix-triggered:
|
||||||
|
# a oneshot that PUTs/PATCHes the API into the desired state on every
|
||||||
|
# deploy where its script changed (adding a repo to `ciBotRepos` and
|
||||||
|
# redeploying is enough to pick it up; it won't self-heal a manual revert
|
||||||
|
# done via the web UI unless the unit is also restarted).
|
||||||
|
#
|
||||||
|
# Auth for those API calls is darman's OWN token (named
|
||||||
|
# "jupiter-ci-bot-provisioning" in gitea, scopes write:repository +
|
||||||
|
# write:user — see hosts/jupiter/secrets.nix), since darman owns the repos
|
||||||
|
# in ciBotRepos and only an owner-scoped token clears the reqOwnerCheck on
|
||||||
|
# the collaborator/branch-protection endpoints; write:user is additionally
|
||||||
|
# needed to push ci-bot's token below as a secret on darman's own account.
|
||||||
|
# It is NOT ci-bot's own push token — ci-bot can't grant itself access.
|
||||||
|
#
|
||||||
|
# ci-bot's own push token (separate secret, ci_bot_token) is generated
|
||||||
|
# once via:
|
||||||
|
# su gitea -s /bin/sh -c \
|
||||||
|
# 'GITEA_WORK_DIR=/mnt/data/AppData/gitea gitea admin user generate-access-token \
|
||||||
|
# --username ci-bot --scopes write:repository'
|
||||||
|
# and this service pushes it into gitea itself as a user-level Actions
|
||||||
|
# secret (CI_BOT_TOKEN, on darman's account — see the PUT below) so
|
||||||
|
# workflows in ciBotRepos can push as ci-bot without a per-repo secret.
|
||||||
|
systemd.services.gitea-ci-bot-provision = {
|
||||||
|
description = "Provision ci-bot gitea account + repo access";
|
||||||
|
after = [ "gitea.service" ];
|
||||||
|
requires = [ "gitea.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pkgs.curl pkgs.jq config.services.gitea.package ];
|
||||||
|
environment = {
|
||||||
|
TOKEN_FILE = config.sops.secrets.gitea_provisioning_token.path;
|
||||||
|
CI_BOT_TOKEN_FILE = config.sops.secrets.gitea_ci_bot_token.path;
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
User = config.services.gitea.user;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
api=http://127.0.0.1:${toString config.services.gitea.settings.server.HTTP_PORT}/api/v1
|
||||||
|
admin_token="$(cat "$TOKEN_FILE")"
|
||||||
|
auth=(-H "Authorization: token $admin_token")
|
||||||
|
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
curl -fs "$api/version" >/dev/null 2>&1 && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! curl -fs "''${auth[@]}" "$api/users/ci-bot" >/dev/null 2>&1; then
|
||||||
|
GITEA_WORK_DIR=${config.services.gitea.stateDir} gitea admin user create \
|
||||||
|
--username ci-bot \
|
||||||
|
--email ci-bot@${config.services.gitea.settings.server.DOMAIN} \
|
||||||
|
--random-password --must-change-password=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No instance-wide secret scope exists in Gitea (it's an open feature
|
||||||
|
# request) - a user-level secret on darman's own account is the closest
|
||||||
|
# equivalent, since every repo below is owned directly by darman, not
|
||||||
|
# an org, and repo-level secrets fall back to user-level when unset.
|
||||||
|
ci_bot_token="$(cat "$CI_BOT_TOKEN_FILE")"
|
||||||
|
curl -fsS "''${auth[@]}" -H 'Content-Type: application/json' \
|
||||||
|
-X PUT "$api/user/actions/secrets/CI_BOT_TOKEN" \
|
||||||
|
-d "$(jq -n --arg data "$ci_bot_token" '{data: $data}')"
|
||||||
|
|
||||||
|
${lib.concatMapStringsSep "\n" (repo: ''
|
||||||
|
curl -fsS "''${auth[@]}" -H 'Content-Type: application/json' \
|
||||||
|
-X PUT "$api/repos/${repo}/collaborators/ci-bot" \
|
||||||
|
-d '{"permission":"write"}'
|
||||||
|
|
||||||
|
default_branch="$(curl -fs "''${auth[@]}" "$api/repos/${repo}" | jq -r .default_branch)"
|
||||||
|
|
||||||
|
# ci-bot needs push access on every branch a workflow might commit
|
||||||
|
# back to (currently just `develop`, where version-bump.yml pushes),
|
||||||
|
# in addition to whatever the repo's actual default branch is.
|
||||||
|
branches="$(printf '%s\n' "$default_branch" develop | sort -u)"
|
||||||
|
for branch in $branches; do
|
||||||
|
if curl -fs "''${auth[@]}" "$api/repos/${repo}/branch_protections/$branch" >/dev/null 2>&1; then
|
||||||
|
curl -fsS "''${auth[@]}" -H 'Content-Type: application/json' \
|
||||||
|
-X PATCH "$api/repos/${repo}/branch_protections/$branch" \
|
||||||
|
-d '{"enable_push":true,"enable_push_whitelist":true,"push_whitelist_usernames":["ci-bot"]}'
|
||||||
|
else
|
||||||
|
curl -fsS "''${auth[@]}" -H 'Content-Type: application/json' \
|
||||||
|
-X POST "$api/repos/${repo}/branch_protections" \
|
||||||
|
-d "{\"branch_name\":\"$branch\",\"enable_push\":true,\"enable_push_whitelist\":true,\"push_whitelist_usernames\":[\"ci-bot\"]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'') ciBotRepos}
|
||||||
|
'';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,9 @@
|
|||||||
port = 8000;
|
port = 8000;
|
||||||
};
|
};
|
||||||
users.users.audiobookshelf.extraGroups = [ "users" ];
|
users.users.audiobookshelf.extraGroups = [ "users" ];
|
||||||
|
|
||||||
|
# Its state dir is on the eMMC, so systemd sees no reason to wait for the
|
||||||
|
# array — but every library path points into /mnt/data. Starting without it
|
||||||
|
# means an empty library and rescans against nothing.
|
||||||
|
systemd.services.audiobookshelf.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,10 @@
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /mnt/data/AppData/clonarr 0755 darman users -"
|
"d /mnt/data/AppData/clonarr 0755 darman users -"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# podman bind-mounts /mnt/data/AppData/clonarr into the container, but the
|
||||||
|
# generated unit only knows about /run/clonarr — with the array absent podman
|
||||||
|
# would create the source path on the eMMC and the container would run
|
||||||
|
# against an empty config.
|
||||||
|
systemd.services.podman-clonarr.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,4 +119,10 @@ in
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /mnt/data/AppData/immich 0700 immich immich -"
|
"d /mnt/data/AppData/immich 0700 immich immich -"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# The unit's automatic RequiresMountsFor covers /run/immich and /var/lib/immich
|
||||||
|
# only — nothing points it at mediaLocation. Without this immich starts with
|
||||||
|
# the array missing and writes uploaded photos onto the 29G eMMC, into a
|
||||||
|
# directory that becomes invisible the moment /mnt/data mounts over it.
|
||||||
|
systemd.services.immich-server.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,14 @@
|
|||||||
dataDir = "/mnt/data/AppData/jellyfin";
|
dataDir = "/mnt/data/AppData/jellyfin";
|
||||||
cacheDir = "/mnt/data/AppData/jellyfin/cache";
|
cacheDir = "/mnt/data/AppData/jellyfin/cache";
|
||||||
};
|
};
|
||||||
users.users.jellyfin.extraGroups = [ "users" ];
|
# "users" so the shared library stays readable (see the UMask note below);
|
||||||
|
# "video"/"render" for the DRI nodes used by hardware transcoding. renderD128
|
||||||
|
# happens to be 0666 so VAAPI alone would work without this, but card1 is
|
||||||
|
# 0660 root:video — and neither mode is guaranteed, so don't rely on it. The
|
||||||
|
# groups are harmless on a host with no GPU: they exist regardless, and this
|
||||||
|
# module stays host-agnostic (the DRIVER is enabled per-host, e.g. jupiter's
|
||||||
|
# hardware.graphics + intel-media-driver).
|
||||||
|
users.users.jellyfin.extraGroups = [ "users" "video" "render" ];
|
||||||
|
|
||||||
# The upstream module hardcodes UMask=0077 — root cause of jellyfin writing
|
# The upstream module hardcodes UMask=0077 — root cause of jellyfin writing
|
||||||
# trickplay thumbnails into stray new show folders it invented itself,
|
# trickplay thumbnails into stray new show folders it invented itself,
|
||||||
|
|||||||
@@ -15,9 +15,21 @@
|
|||||||
{
|
{
|
||||||
services.prowlarr.enable = true;
|
services.prowlarr.enable = true;
|
||||||
|
|
||||||
|
# `nofail` is NOT optional here: without it this bind is RequiredBy
|
||||||
|
# local-fs.target, so an unassembled RAID array fails that target and drops
|
||||||
|
# jupiter into emergency mode — which is a dead end, since root is locked and
|
||||||
|
# sulogin has nothing to offer on a headless box. It defeats the `nofail` on
|
||||||
|
# /mnt/data itself (a mount layered on the array is what actually took the
|
||||||
|
# target down). Let this bind fail alone instead.
|
||||||
fileSystems."/var/lib/private/prowlarr" = {
|
fileSystems."/var/lib/private/prowlarr" = {
|
||||||
device = "/mnt/data/AppData/prowlarr/config";
|
device = "/mnt/data/AppData/prowlarr/config";
|
||||||
fsType = "none";
|
fsType = "none";
|
||||||
options = [ "bind" ];
|
options = [ "bind" "nofail" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# systemd derives RequiresMountsFor from the unit's own paths, which here is
|
||||||
|
# only /var/lib/prowlarr on the eMMC — so without this prowlarr starts happily
|
||||||
|
# with the array absent and writes its state onto the 29G OS disk. Pin it to
|
||||||
|
# the array so it fails loudly instead.
|
||||||
|
systemd.services.prowlarr.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,84 @@
|
|||||||
{ ... }:
|
{ config, ... }:
|
||||||
|
|
||||||
# SABnzbd — usenet downloader. Reuses the config migrated from the old
|
# SABnzbd — usenet downloader. Migrated off a reused hand-authored ini
|
||||||
# ZimaOS docker stack (servers/API key/history already set up) by pointing
|
# (servers/API key/history originally imported from the old ZimaOS docker
|
||||||
# straight at the real ini instead of generating a fresh NixOS-managed one.
|
# stack) onto NixOS-managed `settings`, per the module's own deprecation
|
||||||
# Runs as the module's default dedicated `sabnzbd` system user — after first
|
# notice for `configFile`. Only the values that differ from SABnzbd's own
|
||||||
# deploy, chown the migrated config dir to it (see README/CLAUDE notes):
|
# built-in defaults are declared here — everything else falls back to the
|
||||||
# chown -R sabnzbd:sabnzbd /mnt/data/AppData/sabnzbd/config
|
# same defaults SABnzbd was already using.
|
||||||
|
#
|
||||||
|
# `admin_dir`/`log_dir` MUST stay absolute: the module writes the merged ini
|
||||||
|
# to /var/lib/sabnzbd/sabnzbd.ini (eMMC), and both dirs are otherwise
|
||||||
|
# relative to wherever the ini lives. Pointing them back at the ORIGINAL
|
||||||
|
# /mnt/data location keeps the existing download queue/history database
|
||||||
|
# (admin_dir) intact — a relative default here would silently "reset"
|
||||||
|
# SABnzbd to an empty queue on first switch, even though nothing was deleted.
|
||||||
{
|
{
|
||||||
services.sabnzbd = {
|
services.sabnzbd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
configFile = "/mnt/data/AppData/sabnzbd/config/sabnzbd.ini";
|
allowConfigWrite = true; # let sabnzbd keep saving state (queue, wizard flags, ...)
|
||||||
allowConfigWrite = true; # real pre-existing state — let sabnzbd keep saving it
|
settings = {
|
||||||
|
misc = {
|
||||||
|
host = "::";
|
||||||
|
port = 8085;
|
||||||
|
web_color = "Night";
|
||||||
|
enable_https = false;
|
||||||
|
url_base = "/sabnzbd";
|
||||||
|
cache_limit = "1G";
|
||||||
|
download_dir = "/mnt/data/HighSeas/Downloads/Incomplete";
|
||||||
|
complete_dir = "/mnt/data/HighSeas/Downloads";
|
||||||
|
admin_dir = "/mnt/data/AppData/sabnzbd/config/admin";
|
||||||
|
log_dir = "/mnt/data/AppData/sabnzbd/config/logs";
|
||||||
|
# Verbatim from the migrated ini — includes a pre-existing "izma ace"
|
||||||
|
# (missing comma) left as-is rather than silently "fixed" here.
|
||||||
|
unwanted_extensions = "exe, com, bat, ink, js, vbs, ps1, sh, py, php, pl, rb, jar, class, swf, scr, hta, msi, msp, msu, pif, ink, chm, vb, vba, ws, wsf, wsh, xll, docm, dotm, xlsm, xltm, pptm, potm, ppsm, sldm, thmx, xlam, ppam, docb, dotb, xltb, mht, mhtml, url, iqylink, deamon, elf, dmg, iso, cue, nrg, img, udf, wim, vhd, vhdx, vmdk, ova, tf, pb, savedmodel, h5, ckpt, meta, index, data-00000-of-00001, vocab, config, model, pt, tgz, tar.gz, bz2, xz, izma ace, arc, cab, jar, izh, pea, sit, sitx, sqx, zoo, pak, upk, bsa, dat, nzb, nzbs, nzb.gz, nzb.bz2";
|
||||||
|
host_whitelist = "cd1a98d07ece, helium, sabnzbd.jupiter.sol, localhost, jupiter, jupiter.sol";
|
||||||
|
username = "@sabnzbd_web_username@";
|
||||||
|
password = "@sabnzbd_web_password@";
|
||||||
|
api_key = "@sabnzbd_api_key@";
|
||||||
|
nzb_key = "@sabnzbd_nzb_key@";
|
||||||
|
};
|
||||||
|
servers."news.eweka.nl" = {
|
||||||
|
name = "news.eweka.nl";
|
||||||
|
displayname = "news.eweka.nl";
|
||||||
|
host = "news.eweka.nl";
|
||||||
|
port = 563;
|
||||||
|
connections = 8;
|
||||||
|
ssl = true;
|
||||||
|
ssl_verify = "strict";
|
||||||
|
username = "@sabnzbd_eweka_username@";
|
||||||
|
password = "@sabnzbd_eweka_password@";
|
||||||
|
};
|
||||||
|
categories = {
|
||||||
|
"*" = { name = "*"; order = 0; pp = 3; };
|
||||||
|
movies = { name = "movies"; order = 1; script = "Default"; priority = -100; };
|
||||||
|
tv = { name = "tv"; order = 2; script = "Default"; priority = -100; };
|
||||||
|
audio = { name = "audio"; order = 3; script = "Default"; priority = -100; };
|
||||||
|
software = { name = "software"; order = 4; script = "Default"; priority = -100; };
|
||||||
|
prowlarr = { name = "prowlarr"; order = 5; script = "Default"; priority = -100; };
|
||||||
|
xxx = { name = "xxx"; order = 6; script = "Default"; priority = -100; };
|
||||||
|
readarr = { name = "readarr"; order = 7; script = "Default"; priority = -100; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
secretValues = {
|
||||||
|
"@sabnzbd_web_username@" = config.sops.secrets.sabnzbd_web_username.path;
|
||||||
|
"@sabnzbd_web_password@" = config.sops.secrets.sabnzbd_web_password.path;
|
||||||
|
"@sabnzbd_api_key@" = config.sops.secrets.sabnzbd_api_key.path;
|
||||||
|
"@sabnzbd_nzb_key@" = config.sops.secrets.sabnzbd_nzb_key.path;
|
||||||
|
"@sabnzbd_eweka_username@" = config.sops.secrets.sabnzbd_eweka_username.path;
|
||||||
|
"@sabnzbd_eweka_password@" = config.sops.secrets.sabnzbd_eweka_password.path;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Write access to the shared downloads dir (owned darman:users on disk).
|
# Write access to the shared downloads dir (owned darman:users on disk).
|
||||||
users.users.sabnzbd.extraGroups = [ "users" ];
|
users.users.sabnzbd.extraGroups = [ "users" ];
|
||||||
|
|
||||||
|
# download/complete/admin dirs all live on the array, but systemd only
|
||||||
|
# derives RequiresMountsFor from /var/lib/sabnzbd (eMMC) — so with the array
|
||||||
|
# absent sabnzbd would start and download onto the 29G OS disk.
|
||||||
|
systemd.services.sabnzbd.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
systemd.services.fix-downloads-perms.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
|
||||||
# SABnzbd hardcodes completed job folders to 0700 on every job, ignoring
|
# SABnzbd hardcodes completed job folders to 0700 on every job, ignoring
|
||||||
# the ini's `umask` (that only covers files during unpack, not the job
|
# the ini's `umask` (that only covers files during unpack, not the job
|
||||||
# dir itself). setgid on Downloads keeps the group as "users" but perm
|
# dir itself). setgid on Downloads keeps the group as "users" but perm
|
||||||
|
|||||||
@@ -11,12 +11,18 @@
|
|||||||
{
|
{
|
||||||
services.seerr.enable = true;
|
services.seerr.enable = true;
|
||||||
|
|
||||||
|
# `nofail` for the same reason as prowlarr.nix — see the comment there: an
|
||||||
|
# array-backed bind without it fails local-fs.target and boots to an
|
||||||
|
# unusable emergency shell.
|
||||||
fileSystems."/var/lib/private/seerr" = {
|
fileSystems."/var/lib/private/seerr" = {
|
||||||
device = "/mnt/data/AppData/seerr";
|
device = "/mnt/data/AppData/seerr";
|
||||||
fsType = "none";
|
fsType = "none";
|
||||||
options = [ "bind" ];
|
options = [ "bind" "nofail" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Only /var/lib/seerr (eMMC) is picked up automatically; pin to the array.
|
||||||
|
systemd.services.seerr.unitConfig.RequiresMountsFor = [ "/mnt/data" ];
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /mnt/data/AppData/seerr 0755 darman users -"
|
"d /mnt/data/AppData/seerr 0755 darman users -"
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,19 +5,6 @@
|
|||||||
# to declare `sops.secrets.tailscale_authkey` (see each host's secrets.nix).
|
# to declare `sops.secrets.tailscale_authkey` (see each host's secrets.nix).
|
||||||
# Not for the VM (no sops).
|
# Not for the VM (no sops).
|
||||||
{
|
{
|
||||||
# TEMPORARY: nixpkgs bumped tailscale 1.98.8->1.98.9 without updating
|
|
||||||
# vendorHash (NixOS/nixpkgs#545860, fixed on the `release-26.05` branch
|
|
||||||
# 2026-07-26 but not yet promoted to the `nixos-26.05` channel branch this
|
|
||||||
# flake tracks). Remove this override once `nix flake lock --update-input
|
|
||||||
# nixpkgs` picks up a nixos-26.05 rev at/after that fix.
|
|
||||||
nixpkgs.overlays = [
|
|
||||||
(final: prev: {
|
|
||||||
tailscale = prev.tailscale.overrideAttrs (old: {
|
|
||||||
vendorHash = "sha256-Sd2iLJ7eDfDYdIRuW4xuiKgzhQWJWGAnz97FJWrVRlE=";
|
|
||||||
});
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
services.tailscale = {
|
services.tailscale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = true; # UDP 41641 for direct connections
|
openFirewall = true; # UDP 41641 for direct connections
|
||||||
|
|||||||
Reference in New Issue
Block a user