diff --git a/flake.lock b/flake.lock
index d37dc7e..b3a1c22 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,5 +1,27 @@
{
"nodes": {
+ "agenix": {
+ "inputs": {
+ "darwin": "darwin",
+ "home-manager": "home-manager",
+ "nixpkgs": "nixpkgs",
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1770165109,
+ "narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
+ "owner": "ryantm",
+ "repo": "agenix",
+ "rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ryantm",
+ "repo": "agenix",
+ "rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
+ "type": "github"
+ }
+ },
"bun2nix": {
"inputs": {
"flake-parts": [
@@ -34,11 +56,29 @@
"type": "github"
}
},
+ "catppuccin": {
+ "inputs": {
+ "nixpkgs": "nixpkgs_2"
+ },
+ "locked": {
+ "lastModified": 1789553013,
+ "narHash": "sha256-W5dvgFOuVs24X3G5tUb8C2IHU7ICXNvyGPiWWFjfbuo=",
+ "owner": "catppuccin",
+ "repo": "nix",
+ "rev": "89b3eacf59d6b5eefbc2d69c3a4eb5aaf66d63bc",
+ "type": "github"
+ },
+ "original": {
+ "owner": "catppuccin",
+ "repo": "nix",
+ "type": "github"
+ }
+ },
"chaotic": {
"inputs": {
"flake-schemas": "flake-schemas",
- "home-manager": "home-manager",
- "nixpkgs": "nixpkgs"
+ "home-manager": "home-manager_2",
+ "nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1788855501,
@@ -70,6 +110,59 @@
"type": "github"
}
},
+ "darwin": {
+ "inputs": {
+ "nixpkgs": [
+ "agenix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1744478979,
+ "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=",
+ "owner": "lnl7",
+ "repo": "nix-darwin",
+ "rev": "43975d782b418ebf4969e9ccba82466728c2851b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "lnl7",
+ "ref": "master",
+ "repo": "nix-darwin",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1767039857,
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
+ "owner": "NixOS",
+ "repo": "flake-compat",
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_2": {
+ "locked": {
+ "lastModified": 1767039857,
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
@@ -91,6 +184,42 @@
"type": "github"
}
},
+ "flake-parts_2": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib"
+ },
+ "locked": {
+ "lastModified": 1788450739,
+ "narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "flake-parts_3": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib_2"
+ },
+ "locked": {
+ "lastModified": 1772408722,
+ "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
"flake-schemas": {
"locked": {
"lastModified": 1780327564,
@@ -107,7 +236,7 @@
},
"flake-utils": {
"inputs": {
- "systems": "systems_2"
+ "systems": "systems_3"
},
"locked": {
"lastModified": 1731533236,
@@ -123,7 +252,53 @@
"type": "github"
}
},
+ "git-hooks": {
+ "inputs": {
+ "flake-compat": [
+ "nix-gaming",
+ "flake-compat"
+ ],
+ "nixpkgs": [
+ "nix-gaming",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1790091288,
+ "narHash": "sha256-2dUuLTiQrf2gUFVLlayDq/hgluitplAMv6Y9bYwQQ+c=",
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "rev": "0d3997c4d3253505f77c9bcea63904bb575da3c5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "type": "github"
+ }
+ },
"home-manager": {
+ "inputs": {
+ "nixpkgs": [
+ "agenix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1745494811,
+ "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "type": "github"
+ }
+ },
+ "home-manager_2": {
"inputs": {
"nixpkgs": [
"chaotic",
@@ -144,12 +319,33 @@
"type": "github"
}
},
+ "home-manager_3": {
+ "inputs": {
+ "nixpkgs": [
+ "zen-browser",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1789430117,
+ "narHash": "sha256-t+U1mijItLJ64xZOifpfQ17kpAL73nU7pDI48ScacVc=",
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "rev": "cda90fd8838825c689fde9d3f3b4e937937790df",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "type": "github"
+ }
+ },
"llm-agents": {
"inputs": {
"bun2nix": "bun2nix",
"flake-parts": "flake-parts",
- "nixpkgs": "nixpkgs_2",
- "systems": "systems",
+ "nixpkgs": "nixpkgs_4",
+ "systems": "systems_2",
"treefmt-nix": "treefmt-nix"
},
"locked": {
@@ -169,7 +365,7 @@
"llm-bridge": {
"inputs": {
"flake-utils": "flake-utils",
- "nixpkgs": "nixpkgs_3"
+ "nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1790067159,
@@ -187,7 +383,7 @@
},
"meowtd": {
"inputs": {
- "nixpkgs": "nixpkgs_4"
+ "nixpkgs": "nixpkgs_6"
},
"locked": {
"lastModified": 1785527457,
@@ -203,7 +399,141 @@
"url": "https://git.koi.rip/koi/meowtd"
}
},
+ "niri": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1790353586,
+ "narHash": "sha256-oEvDG8PTqiy6lvKKWj9D+XZyPzYcl4rm5Qs7qKR0pSk=",
+ "owner": "niri-wm",
+ "repo": "niri",
+ "rev": "1f03391ea644c2a43597de7f637269e26d1e1b49",
+ "type": "github"
+ },
+ "original": {
+ "owner": "niri-wm",
+ "repo": "niri",
+ "type": "github"
+ }
+ },
+ "nix-gaming": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "flake-parts": "flake-parts_2",
+ "git-hooks": "git-hooks",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1790483338,
+ "narHash": "sha256-srUVCAD22Bcbg6GJbZEijDI22HMD6Lo2qOoUKtH22dg=",
+ "owner": "fufexan",
+ "repo": "nix-gaming",
+ "rev": "b193ce18777d01469dbeb3b9c4110de2426e0edf",
+ "type": "github"
+ },
+ "original": {
+ "owner": "fufexan",
+ "repo": "nix-gaming",
+ "type": "github"
+ }
+ },
"nixpkgs": {
+ "locked": {
+ "lastModified": 1754028485,
+ "narHash": "sha256-IiiXB3BDTi6UqzAZcf2S797hWEPCRZOwyNThJIYhUfk=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "59e69648d345d6e8fef86158c555730fa12af9de",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-25.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-lib": {
+ "locked": {
+ "lastModified": 1788057806,
+ "narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "nixpkgs-lib_2": {
+ "locked": {
+ "lastModified": 1772328832,
+ "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "nixpkgs-libvncserver": {
+ "locked": {
+ "lastModified": 1750111231,
+ "narHash": "sha256-3a7Tha/RwYlzH/v3PJrG7+HjOj4c6YOv2K8sqdGsHVQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "e6f23dc08d3624daab7094b701aa3954923c6bbb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "e6f23dc08d3624daab7094b701aa3954923c6bbb",
+ "type": "github"
+ }
+ },
+ "nixpkgs-stable": {
+ "locked": {
+ "lastModified": 1787753485,
+ "narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-26.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1789044656,
+ "narHash": "sha256-sDGcZgdRVR58ceKz0RmkBtdUomBvu5vzbf6Aw5rUCo0=",
+ "rev": "1927682e0d808b4a695910f76562b11e2ddecab4",
+ "type": "tarball",
+ "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1070934.1927682e0d80/nixexprs.tar.xz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"
+ }
+ },
+ "nixpkgs_3": {
"locked": {
"lastModified": 1788752844,
"narHash": "sha256-VaWGJ6+cIYN2erfSecbRV+4ljI185Ty2wUrXyvQbgOw=",
@@ -219,7 +549,7 @@
"type": "github"
}
},
- "nixpkgs_2": {
+ "nixpkgs_4": {
"locked": {
"lastModified": 1788894124,
"narHash": "sha256-guyexwrrF5GBKqjO0eg9LNIvrXn4j2frNak63sDr8zg=",
@@ -235,7 +565,7 @@
"type": "github"
}
},
- "nixpkgs_3": {
+ "nixpkgs_5": {
"locked": {
"lastModified": 1779560665,
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
@@ -251,7 +581,7 @@
"type": "github"
}
},
- "nixpkgs_4": {
+ "nixpkgs_6": {
"locked": {
"lastModified": 1781074563,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
@@ -266,7 +596,7 @@
"type": "indirect"
}
},
- "nixpkgs_5": {
+ "nixpkgs_7": {
"locked": {
"lastModified": 1788881743,
"narHash": "sha256-151taSq/21cxagiIu7hyMVl68+FbHfwBP4lh6TB6KOM=",
@@ -279,6 +609,59 @@
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
}
},
+ "nixpkgs_8": {
+ "locked": {
+ "lastModified": 1790323409,
+ "narHash": "sha256-m4DGo58Fza5ImOegtWOeE18nhpSO1IGzsVA6Lpsb4zw=",
+ "rev": "e94cb152ed51bd6e24eb4a41f1460252beb52cd2",
+ "type": "tarball",
+ "url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1079315.e94cb152ed51/nixexprs.tar.zst"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst"
+ }
+ },
+ "noctalia": {
+ "inputs": {
+ "nixpkgs": "nixpkgs_8"
+ },
+ "locked": {
+ "lastModified": 1790523212,
+ "narHash": "sha256-Z8SuI0YgAZaF0sumKPBF85PxPtTjpo8jvtphbgoITv8=",
+ "owner": "noctalia-dev",
+ "repo": "noctalia",
+ "rev": "08c30392b1350b4f3d3fb77e23732560559f1638",
+ "type": "github"
+ },
+ "original": {
+ "owner": "noctalia-dev",
+ "ref": "cachix",
+ "repo": "noctalia",
+ "type": "github"
+ }
+ },
+ "proxmox-nixos": {
+ "inputs": {
+ "flake-compat": "flake-compat_2",
+ "nixpkgs-libvncserver": "nixpkgs-libvncserver",
+ "nixpkgs-stable": "nixpkgs-stable",
+ "utils": "utils"
+ },
+ "locked": {
+ "lastModified": 1789217472,
+ "narHash": "sha256-5+iviW11rRXppx5/oTkErbauwk+9e3XhENhdKTG6QJI=",
+ "owner": "SaumonNet",
+ "repo": "proxmox-nixos",
+ "rev": "fc773dcf59bf188fcc806c78af635e5917ca2983",
+ "type": "github"
+ },
+ "original": {
+ "owner": "SaumonNet",
+ "repo": "proxmox-nixos",
+ "type": "github"
+ }
+ },
"ratlogin": {
"inputs": {
"crane": "crane",
@@ -304,12 +687,20 @@
},
"root": {
"inputs": {
+ "agenix": "agenix",
+ "catppuccin": "catppuccin",
"chaotic": "chaotic",
"llm-agents": "llm-agents",
"llm-bridge": "llm-bridge",
"meowtd": "meowtd",
- "nixpkgs": "nixpkgs_5",
- "ratlogin": "ratlogin"
+ "niri": "niri",
+ "nix-gaming": "nix-gaming",
+ "nixpkgs": "nixpkgs_7",
+ "noctalia": "noctalia",
+ "proxmox-nixos": "proxmox-nixos",
+ "ratlogin": "ratlogin",
+ "vscode-server": "vscode-server",
+ "zen-browser": "zen-browser"
}
},
"rust-overlay": {
@@ -363,6 +754,36 @@
"type": "github"
}
},
+ "systems_3": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_4": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
"tranquil": {
"inputs": {
"nixpkgs": [
@@ -405,6 +826,63 @@
"repo": "treefmt-nix",
"type": "github"
}
+ },
+ "utils": {
+ "inputs": {
+ "systems": "systems_4"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "vscode-server": {
+ "inputs": {
+ "flake-parts": "flake-parts_3"
+ },
+ "locked": {
+ "lastModified": 1784312229,
+ "narHash": "sha256-2uHCSUw341o3my1R0U0YCfbnMEazylxb58evWsjGL50=",
+ "owner": "nix-community",
+ "repo": "nixos-vscode-server",
+ "rev": "2f984dfbe7e5271b5c413d3e734374cc1306c921",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixos-vscode-server",
+ "type": "github"
+ }
+ },
+ "zen-browser": {
+ "inputs": {
+ "home-manager": "home-manager_3",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1790568199,
+ "narHash": "sha256-h3AHjsOZr9iBEkNfK+Zh7/DoN5iMRpJd/6h/5NxCz9I=",
+ "owner": "0xc000022070",
+ "repo": "zen-browser-flake",
+ "rev": "e50ed94ebf28bae95397e482dbb1c020f50c20c1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "0xc000022070",
+ "repo": "zen-browser-flake",
+ "type": "github"
+ }
}
},
"root": "root",
diff --git a/flake.nix b/flake.nix
index 3431e75..629e9f7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -17,6 +17,18 @@
inputs.ratlogin.url = "git+https://tangled.org/ptr.pet/ratlogin";
inputs.ratlogin.inputs.nixpkgs.follows = "nixpkgs";
+ inputs.agenix.url = "github:ryantm/agenix/b027ee29d959fda4b60b57566d64c98a202e0feb";
+ inputs.proxmox-nixos.url = "github:SaumonNet/proxmox-nixos";
+ inputs.vscode-server.url = "github:nix-community/nixos-vscode-server";
+ inputs.catppuccin.url = "github:catppuccin/nix";
+ inputs.noctalia.url = "github:noctalia-dev/noctalia/cachix";
+ inputs.niri.url = "github:niri-wm/niri";
+ inputs.niri.inputs.nixpkgs.follows = "nixpkgs";
+ inputs.nix-gaming.url = "github:fufexan/nix-gaming";
+ inputs.nix-gaming.inputs.nixpkgs.follows = "nixpkgs";
+ inputs.zen-browser.url = "github:0xc000022070/zen-browser-flake";
+ inputs.zen-browser.inputs.nixpkgs.follows = "nixpkgs";
+
outputs =
flakeInputs:
let
diff --git a/hosts/default.nix b/hosts/default.nix
index b0d23b6..b8389ff 100644
--- a/hosts/default.nix
+++ b/hosts/default.nix
@@ -36,6 +36,12 @@ let
chernobog = allPkgsSets.x86_64-linux;
trimounts = allPkgsSets.x86_64-linux;
pupos = allPkgsSets.x86_64-linux;
+ focalor = allPkgsSets.x86_64-linux;
+ valefar = allPkgsSets.x86_64-linux // {
+ pkgs = allPkgsSets.x86_64-linux.pkgs.appendOverlays [
+ allPkgsSets.x86_64-linux.inputs.proxmox-nixos.overlays.x86_64-linux
+ ];
+ };
};
in
lib.mapAttrs mkSystem systems
diff --git a/hosts/focalor/default.nix b/hosts/focalor/default.nix
new file mode 100644
index 0000000..d59c6cb
--- /dev/null
+++ b/hosts/focalor/default.nix
@@ -0,0 +1,115 @@
+{
+ inputs,
+ lib,
+ pkgs,
+ tlib,
+ ...
+}:
+let
+ system = pkgs.stdenv.hostPlatform.system;
+in
+{
+ imports = [
+ "${inputs.home}/nixos"
+ inputs.catppuccin.nixosModules.catppuccin
+ inputs.nix-gaming.nixosModules.platformOptimizations
+ inputs.noctalia.nixosModules.default
+ inputs.vscode-server.nixosModules.default
+ ../../modules
+ ../../modules/stylix-null.nix
+ ../../users/regent
+ ./hardware.nix
+ ]
+ ++ (tlib.importFolder (toString ./modules));
+
+ home-manager = {
+ useGlobalPkgs = true;
+ backupFileExtension = "HMBackup";
+ extraSpecialArgs = { inherit inputs system; };
+ users.regent.imports = [
+ ../../users/regent/home.nix
+ inputs.catppuccin.homeModules.catppuccin
+ inputs.noctalia.homeModules.default
+ ];
+ };
+
+ modules.llama-cpp.enable = true;
+
+ system.stateVersion = "25.05";
+ catppuccin = {
+ enable = false;
+ autoEnable = false;
+ };
+
+ boot = {
+ binfmt.emulatedSystems = [ "aarch64-linux" ];
+ kernelModules = [ "nct6775" ];
+ loader = {
+ systemd-boot.enable = true;
+ efi.canTouchEfiVariables = true;
+ };
+ supportedFilesystems = [ "nfs" ];
+ };
+ boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
+ nix.settings = {
+ trusted-users = lib.mkForce [ "root" ];
+ extra-platforms = [ "aarch64-linux" ];
+ extra-substituters = [
+ "https://noctalia.cachix.org"
+ "https://cache.numtide.com"
+ ];
+ extra-trusted-public-keys = [
+ "noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
+ "niks3.numtide.com-1:DTx8wZduET09hRmMtKdQDxNNthLQETkc/yaX7M4qK0g="
+ ];
+ };
+
+ time.timeZone = "America/New_York";
+ i18n.defaultLocale = "en_US.UTF-8";
+ environment.variables.EDITOR = lib.mkForce "vim";
+ programs.nix-ld.enable = true;
+
+ environment.systemPackages = with pkgs; [
+ inputs.agenix.packages.${system}.default
+ (prismlauncher.override {
+ jdks = [
+ jdk8
+ jdk11
+ jdk17
+ jdk21
+ jdk25
+ ];
+ })
+ temurin-bin
+ signal-desktop
+ google-chrome
+ osu-lazer-bin
+ qpwgraph
+ easyeffects
+ pavucontrol
+ inputs.llm-agents.packages.${system}.omp
+ inputs.llm-agents.packages.${system}.pi
+ inputs.llm-agents.packages.${system}.beads
+ imagemagick
+ smartmontools
+ ffmpeg
+ nodejs_26
+ vim
+ wget
+ fastfetch
+ lsof
+ btop
+ git
+ openssl
+ stdenv
+ gnumake
+ parted
+ zfs
+ nixos-generators
+ sqlite
+ bun
+ inputs.llm-agents.packages.${system}.prime-agent
+ unzip
+ uv
+ ];
+}
diff --git a/hosts/focalor/hardware.nix b/hosts/focalor/hardware.nix
new file mode 100644
index 0000000..7d131a0
--- /dev/null
+++ b/hosts/focalor/hardware.nix
@@ -0,0 +1,58 @@
+{
+ config,
+ lib,
+ modulesPath,
+ ...
+}:
+{
+ imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
+
+ boot.initrd.availableKernelModules = [
+ "nvme"
+ "xhci_pci"
+ "ahci"
+ "uas"
+ "usbhid"
+ "sd_mod"
+ ];
+ boot.kernelModules = [ "kvm-amd" ];
+
+ fileSystems = {
+ "/" = {
+ device = "/dev/sda2";
+ fsType = "btrfs";
+ options = [
+ "subvol=root"
+ "compress=zstd:3"
+ "noatime"
+ ];
+ };
+ "/home" = {
+ device = "/dev/sda2";
+ fsType = "btrfs";
+ options = [
+ "subvol=home"
+ "compress=zstd:3"
+ "noatime"
+ ];
+ };
+ "/nix" = {
+ device = "/dev/sda2";
+ fsType = "btrfs";
+ options = [
+ "subvol=nix"
+ "compress=zstd:3"
+ "noatime"
+ ];
+ };
+ "/boot" = {
+ device = "/dev/disk/by-uuid/3F27-30E5";
+ fsType = "vfat";
+ options = [ "umask=0077" ];
+ };
+ };
+
+ swapDevices = [ ];
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/focalor/modules/dawn.nix b/hosts/focalor/modules/dawn.nix
new file mode 100644
index 0000000..a2f29b1
--- /dev/null
+++ b/hosts/focalor/modules/dawn.nix
@@ -0,0 +1,14 @@
+{ pkgs, ... }:
+{
+ users.users.dawn = {
+ isNormalUser = true;
+ createHome = true;
+ home = "/home/dawn";
+ extraGroups = [ "wheel" ];
+ shell = pkgs.bashInteractive;
+ hashedPassword = "$y$j9T$TxLlqj0RWsBtIrEhOTyqh1$mfvSCn5j7VAUymWe2/qUTB7.JdwXbqF5qWqUjqQCMu3";
+ openssh.authorizedKeys.keys = [
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDUeDBW4hnHgnT0SjVFeGDztht9owObSmiyWXmmIEGQp2IMPqFpCxkOU61osvfCf4ZT92Ok9iJTohLwFBvHJRD/+CH8/b54sgFAx1lLObkJOMLz4iWZ5y4fNtBYuIA2McQSQoMDpDz6TDym7v7HF7zoUyBmfHMT/9WiX/z6Ft9hY63eh9DcF7WVURzeUvXLApt9wUYUxxdC2KZ/VrDPrIOxCcOgj3le+1zTiD8zwfAGhkzRD3IEx0yCYK6oztrh6WTwA5ZW+cLziH2sVEvSHFa2O398gIvZpzsdYTcQt06d/oyZIvftcpxD8IvjpGgHEsN/mAg0ovexyqAVk+TV/1XySKaoPPVCekap0R50CVD9kEk+GlD78XBYi++aAMIq0/D+NOXkgksfODt3yJPPzQx4KH8gcn0dQJM5zeyTwDfclzMRqCwL1eVHY00EbtG9IcLmMsWk/lM6vpHfyHqHlqNJ3CnUuDBccz9p5ORC1cuj4r9CmXPPmh7OYk7gGiQb4oxuqsYClzp93qmU7qMvGwmxBJaVagNIJgBqb5fsne0OMlcer5CH4L31ozszkSkzCXtFWNoTdgQHU1J3DxxL9WQJCfKku4EPJadYOh80USnauOke5CqfsGtf6uMq4l5Ylcc1QcNhRqxpeTLAIZx0EYDhmQ4eGjAZbiv6ddUp9dAdiQ=="
+ ];
+ };
+}
diff --git a/hosts/focalor/modules/desktop.nix b/hosts/focalor/modules/desktop.nix
new file mode 100644
index 0000000..b3fed41
--- /dev/null
+++ b/hosts/focalor/modules/desktop.nix
@@ -0,0 +1,155 @@
+{
+ config,
+ inputs,
+ lib,
+ pkgs,
+ ...
+}:
+let
+ system = pkgs.stdenv.hostPlatform.system;
+in
+{
+ programs = {
+ niri = {
+ enable = true;
+ package = inputs.niri.packages.${system}.niri;
+ };
+ noctalia = {
+ enable = true;
+ recommendedServices.enable = true;
+ };
+ steam = {
+ enable = true;
+ platformOptimizations.enable = true;
+ };
+ gamescope.enable = true;
+ dconf.enable = true;
+ obs-studio = {
+ enable = true;
+ enableVirtualCamera = true;
+ plugins = [ pkgs.obs-studio-plugins.droidcam-obs ];
+ };
+ };
+
+ services = {
+ greetd = {
+ enable = true;
+ settings.default_session = {
+ command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd niri-session";
+ user = "greeter";
+ };
+ };
+ xserver.enable = true;
+ displayManager.sddm.enable = true;
+ displayManager.defaultSession = lib.mkForce "niri";
+ desktopManager.plasma6.enable = true;
+ xrdp = {
+ enable = true;
+ defaultWindowManager = "startplasma-x11";
+ openFirewall = true;
+ };
+ dbus.enable = true;
+ gvfs.enable = true;
+ gnome.gnome-keyring.enable = true;
+ upower.enable = true;
+ power-profiles-daemon.enable = true;
+ blueman.enable = true;
+ pipewire = {
+ enable = true;
+ alsa.enable = true;
+ alsa.support32Bit = true;
+ pulse.enable = true;
+ };
+ };
+
+ security = {
+ polkit.enable = true;
+ rtkit.enable = true;
+ pam.services.greetd.enableGnomeKeyring = true;
+ };
+
+ hardware = {
+ bluetooth = {
+ enable = true;
+ powerOnBoot = true;
+ };
+ graphics.enable = true;
+ nvidia = {
+ modesetting.enable = true;
+ powerManagement.enable = false;
+ powerManagement.finegrained = false;
+ open = true;
+ nvidiaSettings = true;
+ package = config.boot.kernelPackages.nvidiaPackages.latest;
+ };
+ };
+ services.xserver.videoDrivers = [ "nvidia" ];
+
+ environment = {
+ variables = {
+ GBM_BACKEND = "nvidia-drm";
+ __GLX_VENDOR_LIBRARY_NAME = "nvidia";
+ };
+ sessionVariables.NIXOS_OZONE_WL = "1";
+ systemPackages = with pkgs; [
+ kitty
+ vscode
+ zed-editor
+ fastfetch
+ hyfetch
+ pamixer
+ zellij
+ firefox
+ chromium
+ kpcli
+ eyedropper
+ krita
+ thunar
+ libreoffice
+ signal-desktop
+ haruna
+ (symlinkJoin {
+ name = "equibop-wrapped";
+ paths = [ equibop ];
+ nativeBuildInputs = [ makeWrapper ];
+ postBuild = ''
+ wrapProgram $out/bin/equibop \
+ --add-flags "--disable-features=WebRtcAllowInputVolumeAdjustment"
+ '';
+ })
+ inputs.zen-browser.packages.${system}.default
+ grim
+ slurp
+ wl-clipboard
+ xwayland-satellite
+ ];
+ };
+
+ fonts = {
+ packages = with pkgs; [
+ nerd-fonts.fira-code
+ comic-neue
+ comic-mono
+ corefonts
+ ];
+ fontconfig.defaultFonts = {
+ sansSerif = [
+ "Comic Neue"
+ "Comic Sans MS"
+ ];
+ serif = [
+ "Comic Neue"
+ "Comic Sans MS"
+ ];
+ monospace = [ "Comic Mono" ];
+ };
+ };
+
+ xdg.portal = {
+ enable = true;
+ extraPortals = with pkgs; [
+ xdg-desktop-portal-gtk
+ xdg-desktop-portal-gnome
+ ];
+ };
+}
diff --git a/hosts/focalor/modules/llama-cpp.nix b/hosts/focalor/modules/llama-cpp.nix
new file mode 100644
index 0000000..ef05d89
--- /dev/null
+++ b/hosts/focalor/modules/llama-cpp.nix
@@ -0,0 +1,48 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+
+let
+ cfg = config.modules.llama-cpp;
+ llamaCppCuda = pkgs.llama-cpp.override { cudaSupport = true; };
+in
+{
+ options.modules.llama-cpp.enable = lib.mkEnableOption "the llama.cpp Gemma service";
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [ llamaCppCuda ];
+
+ networking.firewall.interfaces.br0.allowedTCPPorts = [ 8080 ];
+
+ systemd.services.llama-gemma = {
+ description = "Gemma 4 12B Unified via llama.cpp";
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ environment = {
+ HOME = "/home/regent";
+ XDG_CACHE_HOME = "/home/regent/.cache";
+ CUDA_VISIBLE_DEVICES = "1";
+ LD_LIBRARY_PATH = "/run/opengl-driver/lib:/run/opengl-driver-32/lib";
+ };
+
+ serviceConfig = {
+ Type = "simple";
+ User = "regent";
+ Group = "users";
+ WorkingDirectory = "/home/regent";
+ ExecStart = ''
+ /home/regent/Developer/llama.cpp-gemma4/build-cuda-gcc14/bin/llama-server -m /home/regent/models/gemma-4-12b-unsloth-2026-07-17/gemma-4-12b-it-Q4_K_M.gguf --mmproj /home/regent/models/gemma-4-12b-unsloth-2026-07-17/mmproj-F16.gguf --lora-scaled /home/regent/Developer/web-extract-sft/artifacts/runs/query-preview-gemma4-12b-r16a8-v11/adapters/step192-f16.gguf:0.5 --model-draft /home/regent/models/gemma-4-12b/gemma-4-12B-it-qat-assistant-MTP-Q8_0.gguf --spec-type draft-mtp --spec-draft-n-max 4 --alias gemma-4-12b,gemma4 --host 10.0.0.13 --port 8080 --device CUDA0 --split-mode none --n-gpu-layers all --ctx-size 12288 --flash-attn on --parallel 1 --threads 2 --threads-batch 4 --batch-size 2048 --ubatch-size 512 --cont-batching --jinja --reasoning off --cache-ram 0 --metrics --no-webui
+ '';
+ Restart = "on-failure";
+ RestartSec = "5s";
+ TimeoutStartSec = "infinity";
+ LimitNOFILE = 1048576;
+ };
+ };
+ };
+}
diff --git a/hosts/focalor/modules/network.nix b/hosts/focalor/modules/network.nix
new file mode 100644
index 0000000..f47454a
--- /dev/null
+++ b/hosts/focalor/modules/network.nix
@@ -0,0 +1,79 @@
+{
+ networking = {
+ hostName = "focalor";
+ hostId = "84bdc587";
+ useDHCP = false;
+ nameservers = [
+ "10.0.0.210"
+ "1.1.1.1"
+ ];
+ firewall = {
+ enable = true;
+ trustedInterfaces = [ "tailscale0" ];
+ allowedTCPPorts = [
+ 22
+ 3002
+ ];
+ };
+ networkmanager = {
+ enable = true;
+ unmanaged = [
+ "interface-name:enp5s0"
+ "interface-name:br0"
+ ];
+ };
+ };
+
+ systemd.network = {
+ enable = true;
+ wait-online.extraArgs = [ "--interface=enp4s0" ];
+ netdevs.br0.netdevConfig = {
+ Name = "br0";
+ Kind = "bridge";
+ };
+ networks = {
+ "10-lan" = {
+ matchConfig.Name = [
+ "enp5s0"
+ "vm-*"
+ ];
+ networkConfig.Bridge = "br0";
+ };
+ "10-lan-bridge" = {
+ matchConfig.Name = "br0";
+ networkConfig = {
+ Address = [ "10.0.0.13/24" ];
+ Gateway = "10.0.0.1";
+ DNS = [
+ "10.0.0.210"
+ "1.1.1.1"
+ ];
+ IPv6AcceptRA = true;
+ };
+ linkConfig.RequiredForOnline = "routable";
+ };
+ };
+ };
+
+ services = {
+ openssh.enable = true;
+ printing.enable = true;
+ tailscale = {
+ enable = true;
+ useRoutingFeatures = "both";
+ extraUpFlags = [ "--login-server=https://vpn.klbr.net" ];
+ };
+ resolved = {
+ enable = true;
+ settings.Resolve = {
+ DNSSEC = "true";
+ Domains = [ "~." ];
+ FallbackDNS = [
+ "10.0.0.210"
+ "1.0.0.1#one.one.one.one"
+ ];
+ DNSOverTLS = "true";
+ };
+ };
+ };
+}
diff --git a/hosts/focalor/modules/services.nix b/hosts/focalor/modules/services.nix
new file mode 100644
index 0000000..aa3ac94
--- /dev/null
+++ b/hosts/focalor/modules/services.nix
@@ -0,0 +1,61 @@
+{ pkgs, ... }:
+{
+ services = {
+ syncthing = {
+ enable = true;
+ openDefaultPorts = true;
+ user = "regent";
+ dataDir = "/home/regent";
+ configDir = "/home/regent/.config/syncthing";
+ };
+ vscode-server = {
+ enable = true;
+ nodejsPackage = pkgs.nodejs_24;
+ };
+ udev.extraRules = ''
+ KERNEL=="hidraw*", ATTRS{idVendor}=="1b1c", MODE="0666"
+ '';
+ };
+
+ systemd.services = {
+ custom-fan-control = {
+ description = "Custom Ryzen CPU Fan Control Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.python3}/bin/python -u /home/regent/Developer/fan_control.py";
+ Restart = "always";
+ RestartSec = 5;
+ };
+ };
+ osu-keysounds = {
+ description = "osu! Typing Sounds Daemon";
+ wantedBy = [ "multi-user.target" ];
+ after = [
+ "sound.target"
+ "pipewire.service"
+ ];
+ serviceConfig = {
+ Type = "simple";
+ User = "regent";
+ SupplementaryGroups = [ "input" ];
+ Environment = [
+ "XDG_RUNTIME_DIR=/run/user/1000"
+ "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
+ "HOME=/home/regent"
+ "OSUCLACK_VOLUME=1.0"
+ ];
+ WorkingDirectory = "/home/regent/Developer";
+ ExecStart = "/home/regent/Developer/osuclack";
+ Restart = "always";
+ RestartSec = 3;
+ };
+ };
+ };
+
+ virtualisation.docker = {
+ enable = true;
+ enableOnBoot = true;
+ };
+}
diff --git a/hosts/valefar/boot-resilience.nix b/hosts/valefar/boot-resilience.nix
new file mode 100644
index 0000000..6083fd8
--- /dev/null
+++ b/hosts/valefar/boot-resilience.nix
@@ -0,0 +1,64 @@
+# Keep valefar reachable over the network through a bad boot: it lives
+# headless, so a boot that stalls at a local console is a boot we cannot fix.
+{ config, lib, ... }:
+let
+ lanMac = "54:fb:66:01:74:ca";
+in
+{
+ # Vaultwarden, sonarr, prowlarr, radarr and several host-network containers
+ # bind 100.64.0.11, which only exists once tailscaled is up. Without this
+ # they fail with EADDRNOTAVAIL at boot and vaultwarden hits its start limit.
+ boot.kernel.sysctl = {
+ "net.ipv4.ip_nonlocal_bind" = 1;
+ "net.ipv6.ip_nonlocal_bind" = 1;
+ };
+
+ # These write under /storage. Order them after zfs-mount and refuse to
+ # start if /storage is not actually mounted, rather than writing into the
+ # root filesystem underneath the mountpoint.
+ systemd.services =
+ lib.genAttrs
+ [
+ "docker"
+ "jellyfin"
+ "sonarr"
+ "radarr"
+ "container@pia-qbittorrent"
+ "radio"
+ ]
+ (_: {
+ after = [ "zfs-mount.service" ];
+ requires = [ "zfs-mount.service" ];
+ unitConfig.AssertPathIsMountPoint = "/storage";
+ });
+
+ # A failed fstab mount would otherwise stop in emergency.target with no
+ # network. Carry on to multi-user so sshd and tailscale come up.
+ systemd.enableEmergencyMode = false;
+
+ # Stage-1 SSH on the LAN (port 2222, root, regent's keys) for boots that
+ # stall before switch-root. Host key is generated once on the host:
+ # ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
+ boot.initrd.systemd.enable = true;
+ boot.initrd.availableKernelModules = [ "r8169" ];
+ boot.initrd.network = {
+ enable = true;
+ # Drop the stage-1 address so stage 2 can enslave the NIC to vmbr0 cleanly.
+ flushBeforeStage2 = true;
+ ssh = {
+ enable = true;
+ port = 2222;
+ hostKeys = [ "/etc/secrets/initrd/ssh_host_ed25519_key" ];
+ authorizedKeys = config.users.users.regent.openssh.authorizedKeys.keys;
+ };
+ };
+ boot.initrd.systemd.network = {
+ enable = true;
+ networks."10-lan" = {
+ matchConfig.PermanentMACAddress = lanMac;
+ address = [ "10.0.0.30/24" ];
+ gateway = [ "10.0.0.1" ];
+ linkConfig.RequiredForOnline = "routable";
+ };
+ };
+}
diff --git a/hosts/valefar/default.nix b/hosts/valefar/default.nix
new file mode 100644
index 0000000..28bc7b5
--- /dev/null
+++ b/hosts/valefar/default.nix
@@ -0,0 +1,381 @@
+{
+ config,
+ lib,
+ pkgs,
+ inputs,
+ ...
+}:
+{
+ imports = [
+ "${inputs.agenix}/modules/age.nix"
+ "${inputs.home}/nixos"
+ inputs.proxmox-nixos.nixosModules.proxmox-ve
+ ../../modules
+ ../../users/regent
+ ./hardware.nix
+ ./secrets.nix
+ ./boot-resilience.nix
+ ./nat-guard.nix
+ ./pia-qbittorrent.nix
+ ./pia-exit.nix
+ ./wg-mesh.nix
+ ];
+
+ services.proxmox-ve = {
+ enable = true;
+ ipAddress = "10.0.0.30";
+ bridges = [ "vmbr0" ];
+ };
+
+ nix.gc = {
+ automatic = lib.mkForce true;
+ dates = "weekly";
+ options = "--delete-older-than 14d";
+ };
+ nix.settings = {
+ auto-optimise-store = true;
+ trusted-users = lib.mkForce [
+ "root"
+ "regent"
+ ];
+ substituters = [ "https://cache.saumon.network/proxmox-nixos" ];
+ trusted-public-keys = [ "proxmox-nixos:D9RYSWpQQC/msZUWphOY2I5RLH5Dd6yQcaHIuug7dWM=" ];
+ };
+ services.journald.settings.Journal.SystemMaxUse = "1G";
+ boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
+ time.timeZone = "America/New_York";
+ i18n.defaultLocale = "en_US.UTF-8";
+ programs.nix-ld.enable = true;
+ services.openssh.enable = true;
+ services.tailscale = {
+ enable = true;
+ useRoutingFeatures = "both";
+ extraUpFlags = [ "--login-server=https://vpn.klbr.net" ];
+ };
+ boot.loader = {
+ systemd-boot.enable = true;
+ efi.canTouchEfiVariables = true;
+ };
+ fileSystems."/boot".options = [ "umask=0077" ];
+ hardware.graphics.enable = true;
+ services.xserver.videoDrivers = [ "nvidia" ];
+ hardware.nvidia = {
+ modesetting.enable = true;
+ powerManagement.enable = false;
+ powerManagement.finegrained = false;
+ open = true;
+ nvidiaSettings = true;
+ package = config.boot.kernelPackages.nvidiaPackages.latest;
+ };
+ environment.variables = {
+ GBM_BACKEND = "nvidia-drm";
+ __GLX_VENDOR_LIBRARY_NAME = "nvidia";
+ };
+
+ services.pocket-id = {
+ enable = true;
+ dataDir = "/var/lib/pocket-id";
+ credentials = {
+ ENCRYPTION_KEY = config.age.secrets."pocket-id-encryption-key".path;
+ MAXMIND_LICENSE_KEY = config.age.secrets."pocket-id-maxmind-license-key".path;
+ };
+ settings = {
+ APP_URL = "https://pocketid.nekomimi.pet";
+ DB_CONNECTION_STRING = "pocket-id.db";
+ GEOLITE_DB_PATH = "GeoLite2-City.mmdb";
+ PORT = 3000;
+ TRUST_PROXY = true;
+ UPLOAD_PATH = "uploads";
+ };
+ };
+
+ networking = {
+ useNetworkd = true;
+ useDHCP = false;
+ hostName = "valefar";
+ hostId = "2a07da90";
+ firewall.enable = false;
+ };
+ systemd.network = {
+ enable = true;
+ links."10-lan" = {
+ matchConfig.PermanentMACAddress = "54:fb:66:01:74:ca";
+ linkConfig = {
+ NamePolicy = "keep kernel database onboard slot path";
+ AlternativeNamesPolicy = "database onboard slot path";
+ MACAddressPolicy = "persistent";
+ WakeOnLan = "magic";
+ };
+ };
+ networks = {
+ "10-lan" = {
+ matchConfig.PermanentMACAddress = "54:fb:66:01:74:ca";
+ networkConfig = {
+ Bridge = "vmbr0";
+ DHCP = "no";
+ LinkLocalAddressing = "no";
+ IPv6AcceptRA = false;
+ };
+ };
+ "11-lan-by-name" = {
+ matchConfig.Name = "enp5s0";
+ networkConfig = {
+ Bridge = "vmbr0";
+ DHCP = "no";
+ LinkLocalAddressing = "no";
+ IPv6AcceptRA = false;
+ };
+ };
+ "10-lan-bridge" = {
+ matchConfig.Name = "vmbr0";
+ networkConfig = {
+ Address = [
+ "10.0.0.30/24"
+ "2601:5c2:8400:26c0::30/64"
+ ];
+ Gateway = "10.0.0.1";
+ DNS = [
+ "10.0.0.210"
+ "1.1.1.1"
+ "1.0.0.1"
+ ];
+ IPv6AcceptRA = true;
+ };
+ routes = [
+ {
+ Destination = "0.0.0.0/0";
+ Gateway = "10.0.0.1";
+ }
+ ];
+ linkConfig.RequiredForOnline = "routable";
+ };
+ };
+ netdevs.br0.netdevConfig = {
+ Name = "vmbr0";
+ Kind = "bridge";
+ };
+ };
+ services.resolved = {
+ enable = true;
+ settings.Resolve = {
+ DNSSEC = "false";
+ Domains = [ "~." ];
+ FallbackDNS = [
+ "10.0.0.210"
+ "1.1.1.1"
+ ];
+ DNSOverTLS = "false";
+ };
+ };
+
+ boot = {
+ supportedFilesystems = [ "zfs" ];
+ kernelModules = [
+ "nct6775"
+ "coretemp"
+ ];
+ zfs = {
+ extraPools = [ "storage" ];
+ devNodes = "/dev/disk/by-id";
+ forceImportAll = true;
+ forceImportRoot = true;
+ };
+ };
+ systemd.services.zfs-import-cache.enable = false;
+ services.zfs = {
+ autoScrub.enable = true;
+ trim.enable = true;
+ };
+
+ services.jellyfin = {
+ enable = true;
+ dataDir = "/storage/jellyfin";
+ };
+ services.prowlarr = {
+ enable = true;
+ settings.server = {
+ bindaddress = "100.64.0.11";
+ port = 9696;
+ };
+ };
+ services.sonarr = {
+ enable = true;
+ dataDir = "/storage/sonarr";
+ settings = {
+ server = {
+ bindAddress = "100.64.0.11";
+ port = 8989;
+ };
+ update = {
+ automatically = false;
+ mechanism = "external";
+ };
+ log.analyticsEnabled = false;
+ };
+ };
+ services.radarr = {
+ enable = true;
+ dataDir = "/storage/radarr";
+ settings = {
+ server = {
+ bindAddress = "100.64.0.11";
+ port = 7878;
+ };
+ update = {
+ automatically = false;
+ mechanism = "external";
+ };
+ log.analyticsEnabled = false;
+ };
+ };
+ systemd.services.radarr.serviceConfig.PrivateUsers = lib.mkForce false;
+ systemd.services.sonarr.serviceConfig.PrivateUsers = lib.mkForce false;
+ users.users.radarr = {
+ isSystemUser = true;
+ group = "radarr";
+ home = "/storage/radarr";
+ uid = config.ids.uids.radarr;
+ extraGroups = [ "jellyfin" ];
+ };
+ users.groups.radarr.gid = config.ids.gids.radarr;
+ users.users.sonarr = {
+ isSystemUser = true;
+ group = "sonarr";
+ home = "/storage/sonarr";
+ uid = config.ids.uids.sonarr;
+ extraGroups = [ "jellyfin" ];
+ };
+ users.groups.sonarr.gid = config.ids.gids.sonarr;
+ services.vaultwarden = {
+ enable = true;
+ config = {
+ DOMAIN = "https://vault.nekomimi.pet";
+ ROCKET_ADDRESS = "100.64.0.11";
+ ROCKET_PORT = 8222;
+ SIGNUPS_ALLOWED = false;
+ SSO_ENABLED = true;
+ SSO_ONLY = true;
+ SSO_PKCE = true;
+ SSO_SCOPES = "email profile groups offline_access";
+ SSO_AUTHORITY = "https://pocketid.nekomimi.pet";
+ };
+ environmentFile = config.age.secrets."vaultwarden-oidc.env".path;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /storage/tm_share 0755 regent users"
+ "d /storage/media 0755 jellyfin jellyfin -"
+ "d /storage/media/.incoming 2775 jellyfin jellyfin -"
+ "d /storage/media/tv 2775 jellyfin jellyfin -"
+ ];
+ services.samba = {
+ enable = true;
+ settings = {
+ global = {
+ "workgroup" = "WORKGROUP";
+ "server string" = "valefar";
+ "netbios name" = "valefar";
+ "security" = "user";
+ "hosts allow" = "100.64.0.0/10 10.0.0.0/24 127.0.0.1 localhost";
+ "hosts deny" = "0.0.0.0/0";
+ "guest account" = "nobody";
+ "map to guest" = "bad user";
+ };
+ tm_share = {
+ path = "/storage/tm_share";
+ "valid users" = "regent";
+ public = "yes";
+ writeable = "yes";
+ "force user" = "regent";
+ "fruit:aapl" = "yes";
+ "fruit:time machine" = "yes";
+ "vfs objects" = "catia fruit streams_xattr";
+ };
+ };
+ };
+ services.netatalk = {
+ enable = true;
+ settings.time-machine = {
+ path = "/storage/timemachine";
+ "valid users" = "regent";
+ "time machine" = true;
+ };
+ };
+ services.avahi = {
+ enable = true;
+ nssmdns4 = true;
+ publish = {
+ enable = true;
+ userServices = true;
+ };
+ extraServiceFiles.timemachine = ''
+
+
+
+ %h
+
+ _smb._tcp
+ 445
+
+
+ _device-info._tcp
+ 0
+ model=TimeCapsule8,119
+
+
+ _adisk._tcp
+ dk0=adVN=tm_share,adVF=0x82
+ sys=waMa=0,adVF=0x100
+
+
+ '';
+ };
+
+ users.users.niri = {
+ isSystemUser = true;
+ uid = 988;
+ group = "users";
+ shell = pkgs.bashInteractive;
+ extraGroups = [ "wheel" ];
+ openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ7Y9Je7H3gC72cgdEH4wifUDsmhKMeU5Z4oL1s1WcSE niri@nekomimi.pet"
+ ];
+ };
+ systemd.services.radio = {
+ description = "faint signal fm";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ environment.NO_COLOR = "1";
+ path = [
+ pkgs.ffmpeg
+ pkgs.yt-dlp
+ ];
+ serviceConfig = {
+ User = "regent";
+ Group = "users";
+ WorkingDirectory = "/home/regent/radio";
+ ExecStart = "/home/regent/radio/target/release/radio";
+ Restart = "always";
+ RestartSec = "5s";
+ };
+ };
+ services.syncthing = {
+ guiAddress = "0.0.0.0:8384";
+ enable = true;
+ };
+ virtualisation.docker = {
+ enable = true;
+ enableOnBoot = true;
+ };
+ environment.systemPackages = with pkgs; [
+ code-server
+ ffmpeg
+ yt-dlp
+ nodejs
+ python3
+ smartmontools
+ ];
+
+ system.stateVersion = "24.11";
+}
diff --git a/hosts/valefar/hardware.nix b/hosts/valefar/hardware.nix
new file mode 100644
index 0000000..0533d98
--- /dev/null
+++ b/hosts/valefar/hardware.nix
@@ -0,0 +1,44 @@
+{
+ config,
+ lib,
+ modulesPath,
+ ...
+}:
+
+{
+ imports = [
+ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [
+ "xhci_pci"
+ "ahci"
+ "mpt3sas"
+ "nvme"
+ "usbhid"
+ "uas"
+ "sd_mod"
+ ];
+ boot.kernelModules = [ "kvm-amd" ];
+
+ fileSystems."/" = {
+ device = "/dev/disk/by-uuid/e02d1d07-3bc8-4d1d-a301-6d589f4b4b6d";
+ fsType = "ext4";
+ };
+
+ fileSystems."/boot" = {
+ device = "/dev/disk/by-uuid/B3DE-0187";
+ fsType = "vfat";
+ options = [
+ "fmask=0022"
+ "dmask=0022"
+ ];
+ };
+
+ swapDevices = [
+ { device = "/dev/disk/by-uuid/c8f24f31-49e0-486c-9f63-1d31b2e36ce9"; }
+ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/valefar/nat-guard.nix b/hosts/valefar/nat-guard.nix
new file mode 100644
index 0000000..eafa6d7
--- /dev/null
+++ b/hosts/valefar/nat-guard.nix
@@ -0,0 +1,107 @@
+{ config, lib, ... }:
+let
+ nat = config.networking.nat;
+ outgoing = lib.optionals (nat.externalInterface != null) [
+ "-o"
+ nat.externalInterface
+ ];
+ translation =
+ if nat.externalIP == null then
+ [
+ "-j"
+ "MASQUERADE"
+ ]
+ else
+ [
+ "-j"
+ "SNAT"
+ "--to-source"
+ nat.externalIP
+ ];
+in
+{
+ config =
+ lib.mkIf (nat.enable && !config.networking.firewall.enable && !config.networking.nftables.enable)
+ {
+ assertions = [
+ {
+ assertion = lib.all (name: builtins.stringLength name < 16) (
+ nat.internalInterfaces ++ lib.optional (nat.externalInterface != null) nat.externalInterface
+ );
+ message = "valefar NAT interface names must be at most 15 bytes; use internalIPs for long container names.";
+ }
+ ];
+
+ systemd.services.nat = {
+ wantedBy = [ "multi-user.target" ];
+ # Restart in the new generation, not stop before activation then start later.
+ stopIfChanged = false;
+ serviceConfig = {
+ Restart = "on-failure";
+ RestartSec = "5s";
+ };
+ };
+
+ # A successful oneshot can be stopped or retain stale kernel rules without
+ # becoming failed. Check the live rules against the merged NAT configuration.
+ systemd.services.nat-healthcheck = {
+ description = "Check and repair container NAT";
+ after = [ "nat.service" ];
+ path = [
+ config.networking.firewall.package
+ config.systemd.package
+ ];
+ serviceConfig = {
+ Type = "oneshot";
+ TimeoutStartSec = "60s";
+ };
+ enableStrictShellChecks = true;
+ script = ''
+ check_nat() {
+ systemctl is-active --quiet nat.service || return 1
+ iptables -w 5 -t nat -C POSTROUTING -j nixos-nat-post || return 1
+ iptables -w 5 -t filter -C FORWARD -j nixos-filter-forward || return 1
+ ${lib.concatMapStringsSep "\n" (subnet: ''
+ iptables -w 5 -t nat -C nixos-nat-post ${
+ lib.escapeShellArgs (
+ [
+ "-s"
+ subnet
+ ]
+ ++ outgoing
+ ++ translation
+ )
+ } || return 1
+ iptables -w 5 -t filter -C nixos-filter-forward ${
+ lib.escapeShellArgs (
+ [
+ "-s"
+ subnet
+ ]
+ ++ outgoing
+ ++ [
+ "-j"
+ "ACCEPT"
+ ]
+ )
+ } || return 1
+ '') nat.internalIPs}
+ }
+
+ if ! check_nat; then
+ echo "container NAT is inactive or incomplete; restarting nat.service" >&2
+ systemctl restart nat.service
+ check_nat
+ fi
+ '';
+ };
+ systemd.timers.nat-healthcheck = {
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnBootSec = "30s";
+ OnUnitInactiveSec = "60s";
+ AccuracySec = "1s";
+ };
+ };
+ };
+}
diff --git a/hosts/valefar/pia-exit.nix b/hosts/valefar/pia-exit.nix
new file mode 100644
index 0000000..0b21e3a
--- /dev/null
+++ b/hosts/valefar/pia-exit.nix
@@ -0,0 +1,297 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+let
+ wgAuth = config.age.secrets."pia-wireguard-auth.env".path;
+ piaSource = ./pia-manual;
+ # PIA rotates and repurposes endpoint fleets without notice. 2026-09-21:
+ # the old WireGuard endpoint was retired and OpenVPN tcp/443 adopted.
+ # 2026-09-24: the entire 45.88.217.x DC fleet vanished and its :443 began
+ # answering as a plain nginx front, so openvpn looped on "Bad encapsulated
+ # packet length" (it was reading ASCII "HT" from an HTTP response).
+ # Rotation recipe: fetch https://serverlist.piaservers.net/vpninfo/servers/v6
+ # (JSON is line 1), pick an "ovpntcp" server, verify it handshakes, change
+ # `endpoint` here and in pia-qbittorrent.nix, rebuild.
+ endpoint = "206.206.95.51"; # us-washingtondc / ovpntcp
+ gateway = "192.168.173.1";
+ headscale = "94.237.26.47"; # vpn.klbr.net
+ # One-time Tailnet enrollment bootstrap. Kept mounted so a wiped node state
+ # re-enrolls itself. Read-only; the autoconnect script only reads it when the
+ # node has no valid state, so it is not consulted on a healthy boot.
+ authKey = "/home/regent/.pia-exit-authkey";
+ # Host-side bootstrap: token + OpenVPN config + credentials land under
+ # /run/pia-exit and are bind-mounted read-only into the guest. The container
+ # never needs clear-net DNS or HTTPS.
+ prepare = pkgs.writeShellScript "pia-exit-openvpn-prepare" ''
+ set -euo pipefail
+ umask 077
+ export PATH=${
+ lib.makeBinPath [
+ pkgs.bash
+ pkgs.coreutils
+ pkgs.curl
+ pkgs.jq
+ ]
+ }
+ set -a
+ . ${wgAuth}
+ set +a
+ token=$(curl --fail --silent --show-error --max-time 30 \
+ --form "username=$PIA_USER" --form "password=$PIA_PASS" \
+ https://www.privateinternetaccess.com/api/client/v2/token \
+ | jq -er '.token | strings | select(length > 0)')
+ # OpenVPN token auth: username is the first 62 chars, password the rest.
+ printf '%s\n%s\n' "''${token:0:62}" "''${token:62}" > /run/pia-exit/pia-creds
+ {
+ echo "client"
+ echo "dev pia"
+ echo "dev-type tun"
+ echo "proto tcp"
+ echo "remote ${endpoint} 443"
+ echo "resolv-retry infinite"
+ echo "nobind"
+ echo "persist-key"
+ echo "persist-tun"
+ echo "remote-cert-tls server"
+ echo "redirect-gateway def1"
+ echo "reneg-sec 0"
+ echo "verb 1"
+ echo "auth-user-pass /etc/openvpn/pia-creds"
+ echo ""
+ cat ${piaSource}/ca.rsa.4096.crt
+ echo ""
+ } > /run/pia-exit/pia.ovpn
+ '';
+in
+{
+ # Host fetches PIA bootstrap material; the container never needs clear-net DNS/HTTPS.
+ #
+ # NixOS adds the host-side gateway and brings the veth up in ITS OWN postStart,
+ # after guest readiness. So the tunnel must NOT live in the guest's boot path:
+ # start it from here instead, after the host side exists.
+ systemd.services."container@pia-exit" = {
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ preStart = lib.mkBefore "${prepare}";
+ postStart = lib.mkAfter ''
+ ${config.systemd.package}/bin/systemctl -M pia-exit start --no-block tailscaled-autoconnect.service
+ ${config.systemd.package}/bin/systemctl -M pia-exit start --no-block pia-openvpn.service
+ '';
+ serviceConfig.RuntimeDirectory = "pia-exit";
+ serviceConfig.RuntimeDirectoryMode = "0700";
+ serviceConfig.TimeoutStartSec = lib.mkForce "4min";
+ };
+ # NixOS owns this veth; stop networkd's generic container DHCP/NAT handling.
+ systemd.network.networks."40-pia-exit" = {
+ matchConfig.Name = "ve-pia-exit";
+ linkConfig.Unmanaged = true;
+ };
+ containers.pia-exit = {
+ autoStart = true;
+ privateNetwork = true;
+ enableTun = true;
+ hostAddress = gateway;
+ localAddress = "192.168.173.2";
+ bindMounts = {
+ "/etc/openvpn/pia.ovpn" = {
+ hostPath = "/run/pia-exit/pia.ovpn";
+ isReadOnly = true;
+ };
+ "/etc/openvpn/pia-creds" = {
+ hostPath = "/run/pia-exit/pia-creds";
+ isReadOnly = true;
+ };
+ # Enrollment key, read-only. Kept so a wiped node state can re-enroll itself.
+ "/run/tailscale-authkey" = {
+ hostPath = authKey;
+ isReadOnly = true;
+ };
+ };
+ config =
+ {
+ config,
+ lib,
+ pkgs,
+ ...
+ }:
+ {
+ networking.useDHCP = false;
+ networking.enableIPv6 = false;
+ # Static resolver: resolvconf would otherwise inherit the host's 127.0.0.53
+ # stub, and systemd-resolved is not running in here.
+ networking.nameservers = [ "1.1.1.1" ];
+ networking.resolvconf.enable = false;
+ environment.etc."resolv.conf".text = "nameserver 1.1.1.1\n";
+ # Pin the coordination server so enrollment never depends on DNS at all.
+ networking.hosts."${headscale}" = [ "vpn.klbr.net" ];
+ networking.interfaces.eth0.ipv4.routes = [
+ {
+ address = endpoint;
+ prefixLength = 32;
+ via = gateway;
+ }
+ ];
+ environment.systemPackages = with pkgs; [
+ bash
+ coreutils
+ curl
+ gnugrep
+ gnused
+ jq
+ openvpn
+ iproute2
+ ];
+ boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
+ networking.firewall = {
+ enable = true;
+ checkReversePath = "loose";
+ allowedUDPPorts = [ 41641 ];
+ # Default-deny OUTPUT. Everything leaves through the tunnel, except a
+ # narrow control plane so the node can enroll and stay reachable.
+ extraCommands = ''
+ ${pkgs.iptables}/bin/iptables-restore --noflush <<'RULES'
+ *filter
+ :OUTPUT DROP [0:0]
+ :FORWARD DROP [0:0]
+ -F OUTPUT
+ -F FORWARD
+ -A OUTPUT -o lo -j ACCEPT
+ -A OUTPUT -o pia -j ACCEPT
+ -A OUTPUT -o tailscale0 -j ACCEPT
+ -A OUTPUT -o eth0 -d ${endpoint}/32 -p tcp --dport 443 -j ACCEPT
+ -A OUTPUT -o eth0 -d ${headscale}/32 -p tcp --dport 443 -j ACCEPT
+ -A OUTPUT -o eth0 -d ${headscale}/32 -p udp --dport 3478 -j ACCEPT
+ -A OUTPUT -o eth0 -d 1.1.1.1/32 -p udp --dport 53 -j ACCEPT
+ -A OUTPUT -o eth0 -d 1.1.1.1/32 -p tcp --dport 53 -j ACCEPT
+ -A OUTPUT -o eth0 -d ${gateway}/32 -j ACCEPT
+ -A FORWARD -i tailscale0 -o pia -j ACCEPT
+ -A FORWARD -i pia -o tailscale0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
+ COMMIT
+ *nat
+ :POSTROUTING ACCEPT [0:0]
+ -A POSTROUTING -o pia -s 100.64.0.0/10 -j MASQUERADE
+ COMMIT
+ RULES
+ ${pkgs.iptables}/bin/ip6tables -P OUTPUT DROP
+ ${pkgs.iptables}/bin/ip6tables -P FORWARD DROP
+ '';
+ };
+ # Enrollment also needs the host-side gateway, which NixOS only wires after
+ # guest readiness. Keep it out of the boot transaction; the host starts it.
+ systemd.services.tailscaled-autoconnect.wantedBy = lib.mkForce [ ];
+ services.tailscale = {
+ enable = true;
+ useRoutingFeatures = "server";
+ authKeyFile = "/run/tailscale-authkey";
+ extraUpFlags = [
+ "--login-server=https://vpn.klbr.net"
+ "--hostname=pia-exit"
+ "--accept-dns=false"
+ "--netfilter-mode=off"
+ "--advertise-exit-node"
+ ];
+ };
+ # Deliberately NOT wantedBy multi-user.target: the host veth is not wired
+ # until after guest readiness. Started from the host postStart instead.
+ systemd.services.pia-openvpn = {
+ requires = [ "firewall.service" ];
+ after = [
+ "network-online.target"
+ "firewall.service"
+ ];
+ wants = [ "network-online.target" ];
+ path = [
+ pkgs.curl
+ pkgs.iproute2
+ pkgs.gnugrep
+ ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+ RestartSec = "15s";
+ # The endpoint must leave via eth0; the static pin is applied by
+ # networkd at guest boot. Wait it out rather than race it.
+ ExecStartPre = pkgs.writeShellScript "pia-openvpn-pin" ''
+ for i in $(seq 1 30); do
+ if ip -4 route get ${endpoint} 2>/dev/null | grep -q " dev eth0"; then
+ exit 0
+ fi
+ sleep 1
+ done
+ exit 1
+ '';
+ ExecStart = "${pkgs.openvpn}/bin/openvpn --config /etc/openvpn/pia.ovpn";
+ };
+ # Fail closed: the unit only counts as up once the tunnel really
+ # carries traffic. A failing probe restarts openvpn (Restart=always).
+ postStart = ''
+ ok=0
+ for i in $(seq 1 30); do
+ if curl --fail --silent --max-time 5 --interface pia \
+ https://1.1.1.1/cdn-cgi/trace | grep -q '^ip='; then
+ ok=1
+ break
+ fi
+ sleep 1
+ done
+ [ "$ok" = 1 ] || exit 1
+ systemctl start --no-block pia-tailscale-kick.service
+ '';
+ };
+ # tailscaled holds one long-lived control connection to the headscale
+ # server. When the tunnel's default route flips (endpoint rotation or
+ # reconnect), that TCP conn silently dies and the backend wedges in
+ # NoState forever. Give autoconnect its full 90s window first, then
+ # restart tailscaled once if the backend did not reach Running.
+ systemd.services.pia-tailscale-kick = {
+ description = "Restart tailscaled when its control connection goes stale across a tunnel flip";
+ after = [
+ "pia-openvpn.service"
+ "tailscaled.service"
+ ];
+ path = [
+ pkgs.tailscale
+ pkgs.jq
+ ];
+ serviceConfig = {
+ Type = "oneshot";
+ };
+ script = ''
+ sleep 95
+ state=$(tailscale status --json --peers=false 2>/dev/null | jq -r '.BackendState')
+ if [ "$state" != "Running" ]; then
+ systemctl restart tailscaled.service
+ fi
+ for i in $(seq 1 30); do
+ state=$(tailscale status --json --peers=false 2>/dev/null | jq -r '.BackendState')
+ [ "$state" = "Running" ] && exit 0
+ sleep 2
+ done
+ exit 1
+ '';
+ };
+ # Tailnet traffic must not follow the tunnel's redirect-gateway routes
+ # (0.0.0.0/1, 128.0.0.0/1 in main); the /10 route is more specific and
+ # wins, keeping tailscale0 reachable for exit-node clients.
+ systemd.services.pia-exit-tailnet-route = {
+ description = "Route tailnet traffic out tailscale0, ahead of the tunnel default";
+ after = [ "tailscaled.service" ];
+ wants = [ "tailscaled.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.iproute2 ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script = ''
+ ip route replace 100.64.0.0/10 dev tailscale0 table main
+ '';
+ };
+ system.stateVersion = "26.05";
+ };
+ };
+ networking.nat.internalIPs = [ "192.168.173.0/24" ];
+}
diff --git a/hosts/valefar/pia-manual/SOURCE b/hosts/valefar/pia-manual/SOURCE
new file mode 100644
index 0000000..1f224c7
--- /dev/null
+++ b/hosts/valefar/pia-manual/SOURCE
@@ -0,0 +1 @@
+vendor copy from pia-foss/manual-connections master, retrieved 2026-09-12; source: https://github.com/pia-foss/manual-connections
diff --git a/hosts/valefar/pia-manual/ca.rsa.4096.crt b/hosts/valefar/pia-manual/ca.rsa.4096.crt
new file mode 100644
index 0000000..82dec69
--- /dev/null
+++ b/hosts/valefar/pia-manual/ca.rsa.4096.crt
@@ -0,0 +1,43 @@
+-----BEGIN CERTIFICATE-----
+MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
+BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
+dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
+IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
+FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQw
+MzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
+QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
+AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
+ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
+bmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVk
+hjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULN
+De4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9K
+V2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ
+25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SND
+fCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZyl
+p7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7p
+Kwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzj
+tRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wi
+jSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNz
+meGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz
+1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNV
+HQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRt
+yWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
+EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
+cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
+HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
+ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
+aW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkq
+hkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCt
+pXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dv
+Em89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1G
+tF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZu
+LfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs
+6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj3
+5xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnX
+JUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJ
+iyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l
+8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW
++no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=
+-----END CERTIFICATE-----
diff --git a/hosts/valefar/pia-qbittorrent.nix b/hosts/valefar/pia-qbittorrent.nix
new file mode 100644
index 0000000..4e24c4c
--- /dev/null
+++ b/hosts/valefar/pia-qbittorrent.nix
@@ -0,0 +1,241 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+let
+ wgAuth = config.age.secrets."pia-wireguard-auth.env".path;
+ piaSource = ./pia-manual;
+ # Same story as pia-exit.nix: PIA rotates endpoint fleets without notice;
+ # rotate `endpoint` in lockstep with pia-exit.nix.
+ endpoint = "206.206.95.51"; # us-washingtondc / ovpntcp
+ gateway = "192.168.172.1";
+ # Host-side bootstrap: token + OpenVPN config + credentials under
+ # /run/pia-qbittorrent, bind-mounted read-only into the guest.
+ prepare = pkgs.writeShellScript "pia-qbittorrent-openvpn-prepare" ''
+ set -euo pipefail
+ umask 077
+ export PATH=${
+ lib.makeBinPath [
+ pkgs.bash
+ pkgs.coreutils
+ pkgs.curl
+ pkgs.jq
+ ]
+ }
+ set -a
+ . ${wgAuth}
+ set +a
+ token=$(curl --fail --silent --show-error --max-time 30 \
+ --form "username=$PIA_USER" --form "password=$PIA_PASS" \
+ https://www.privateinternetaccess.com/api/client/v2/token \
+ | jq -er '.token | strings | select(length > 0)')
+ # OpenVPN token auth: username is the first 62 chars, password the rest.
+ printf '%s\n%s\n' "''${token:0:62}" "''${token:62}" > /run/pia-qbittorrent/pia-creds
+ {
+ echo "client"
+ echo "dev pia"
+ echo "dev-type tun"
+ echo "proto tcp"
+ echo "remote ${endpoint} 443"
+ echo "resolv-retry infinite"
+ echo "nobind"
+ echo "persist-key"
+ echo "persist-tun"
+ echo "remote-cert-tls server"
+ echo "redirect-gateway def1"
+ echo "reneg-sec 0"
+ echo "verb 1"
+ echo "auth-user-pass /etc/openvpn/pia-creds"
+ echo ""
+ cat ${piaSource}/ca.rsa.4096.crt
+ echo ""
+ } > /run/pia-qbittorrent/pia.ovpn
+ '';
+in
+{
+ # Bootstrap on the host: the container never needs clear-net DNS or HTTPS.
+ systemd.services."container@pia-qbittorrent" = {
+ wants = [ "network-online.target" ];
+ after = [ "network-online.target" ];
+ preStart = lib.mkBefore "${prepare}";
+ # NixOS adds the host-side gateway in its postStart, after guest readiness.
+ postStart = lib.mkAfter "${config.systemd.package}/bin/systemctl -M pia-qbittorrent start pia-openvpn.service";
+ serviceConfig.RuntimeDirectory = "pia-qbittorrent";
+ serviceConfig.RuntimeDirectoryMode = "0700";
+ serviceConfig.TimeoutStartSec = lib.mkForce "2min";
+ };
+ # NixOS owns this veth; prevent networkd's generic container DHCP/NAT setup.
+ # networkd matches the full altname too; Unmanaged=true reports Network File: n/a.
+ # Unlike this match, iptables must never use the overlong altname.
+ systemd.network.networks."40-pia-qbittorrent" = {
+ matchConfig.Name = "ve-pia-qbittorrent";
+ linkConfig.Unmanaged = true;
+ };
+ containers.pia-qbittorrent = {
+ autoStart = true;
+ privateNetwork = true;
+ enableTun = true;
+ hostAddress = gateway;
+ localAddress = "192.168.172.2";
+ forwardPorts = [
+ {
+ containerPort = 8080;
+ hostPort = 8081;
+ protocol = "tcp";
+ }
+ ];
+ bindMounts."/etc/openvpn/pia.ovpn" = {
+ hostPath = "/run/pia-qbittorrent/pia.ovpn";
+ isReadOnly = true;
+ };
+ bindMounts."/etc/openvpn/pia-creds" = {
+ hostPath = "/run/pia-qbittorrent/pia-creds";
+ isReadOnly = true;
+ };
+ # qBittorrent's ONLY writable host path: the constrained staging shelf.
+ bindMounts."/downloads" = {
+ hostPath = "/storage/media/.incoming";
+ isReadOnly = false;
+ };
+ config =
+ {
+ config,
+ lib,
+ pkgs,
+ ...
+ }:
+ {
+ networking.useDHCP = false;
+ networking.enableIPv6 = false;
+ networking.nameservers = [ "1.1.1.1" ];
+ networking.resolvconf.enable = false;
+ environment.etc."resolv.conf".text = "nameserver 1.1.1.1\n";
+ environment.systemPackages = with pkgs; [
+ bash
+ coreutils
+ curl
+ gnugrep
+ gnused
+ jq
+ openvpn
+ ];
+ networking.interfaces.eth0.ipv4.routes = [
+ {
+ address = endpoint;
+ prefixLength = 32;
+ via = gateway;
+ }
+ ];
+ # The bind-mounted staging shelf is Jellyfin-owned on the host (gid 983).
+ # Match that numeric group inside the container; qBittorrent only sees
+ # the .incoming bind mount, never the final library.
+ users.groups.qbittorrent = { };
+ users.groups.jellyfin.gid = 983;
+ users.users.qbittorrent = {
+ isSystemUser = true;
+ group = "qbittorrent";
+ extraGroups = [ "jellyfin" ];
+ home = "/var/lib/qbittorrent";
+ createHome = true;
+ };
+ networking.firewall = {
+ enable = true;
+ allowedTCPPorts = [ 8080 ];
+ # Replace OUTPUT atomically; retain default-deny even on firewall stop.
+ # Do not allow arbitrary ESTABLISHED flows to fall back onto eth0.
+ extraCommands = ''
+ ${pkgs.iptables}/bin/iptables-restore --noflush <<'RULES'
+ *filter
+ :OUTPUT DROP [0:0]
+ :FORWARD DROP [0:0]
+ -F OUTPUT
+ -A OUTPUT -o lo -j ACCEPT
+ -A OUTPUT -o pia -j ACCEPT
+ -A OUTPUT -o eth0 -d ${endpoint}/32 -p tcp --dport 443 -j ACCEPT
+ -A OUTPUT -o eth0 -d 100.64.0.0/10 -p tcp --sport 8080 -m conntrack --ctstate ESTABLISHED --ctdir REPLY -j ACCEPT
+ -A OUTPUT -o eth0 -d ${gateway}/32 -p tcp --sport 8080 -m conntrack --ctstate ESTABLISHED --ctdir REPLY -j ACCEPT
+ COMMIT
+ RULES
+ ${pkgs.iptables}/bin/ip6tables -P OUTPUT DROP
+ ${pkgs.iptables}/bin/ip6tables -P FORWARD DROP
+ '';
+ };
+ systemd.services.qbittorrent = {
+ wantedBy = [ "pia-openvpn.service" ];
+ requires = [ "pia-openvpn.service" ];
+ after = [ "pia-openvpn.service" ];
+ partOf = [ "pia-openvpn.service" ];
+ serviceConfig = {
+ User = "qbittorrent";
+ Group = "qbittorrent";
+ StateDirectory = "qbittorrent";
+ WorkingDirectory = "/var/lib/qbittorrent";
+ ExecStart = "${pkgs.qbittorrent-nox}/bin/qbittorrent-nox --profile=/var/lib/qbittorrent --webui-port=8080";
+ Restart = "on-failure";
+ RestartSec = "5s";
+ };
+ };
+ systemd.services.pia-openvpn = {
+ requires = [ "firewall.service" ];
+ after = [
+ "network-online.target"
+ "firewall.service"
+ ];
+ wants = [ "network-online.target" ];
+ path = [
+ pkgs.curl
+ pkgs.iproute2
+ pkgs.gnugrep
+ ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+ RestartSec = "15s";
+ ExecStartPre = pkgs.writeShellScript "pia-openvpn-pin" ''
+ for i in $(seq 1 30); do
+ if ip -4 route get ${endpoint} 2>/dev/null | grep -q " dev eth0"; then
+ exit 0
+ fi
+ sleep 1
+ done
+ exit 1
+ '';
+ ExecStart = "${pkgs.openvpn}/bin/openvpn --config /etc/openvpn/pia.ovpn";
+ # Replies to tailnet clients must leave via eth0, not the tunnel:
+ # the host forwards webui connections from tailnet sources, and the
+ # host's conntrack only reverses flows that come back through it.
+ # A dedicated table escapes redirect-gateway's 0.0.0.0/1 in main.
+ ExecStartPost = pkgs.writeShellScript "pia-openvpn-tailnet-route" ''
+ ip rule add to 100.64.0.0/10 lookup 100 priority 100 2>/dev/null || true
+ ip route replace default via ${gateway} dev eth0 table 100
+ '';
+ ExecStopPost = pkgs.writeShellScript "pia-openvpn-tailnet-undo" ''
+ ip rule del to 100.64.0.0/10 lookup 100 priority 100 2>/dev/null || true
+ '';
+ };
+ # Fail closed: the unit only counts as up once the tunnel really
+ # carries traffic, and qbittorrent requires this unit, so the client
+ # never starts on a dead tunnel.
+ postStart = ''
+ for i in $(seq 1 30); do
+ if curl --fail --silent --max-time 5 --interface pia \
+ https://1.1.1.1/cdn-cgi/trace | grep -q '^ip='; then
+ exit 0
+ fi
+ sleep 1
+ done
+ exit 1
+ '';
+ };
+ system.stateVersion = "26.05";
+ };
+ };
+ # systemd shortens long veth names; match the private subnet, not that name.
+ networking.nat = {
+ enable = true;
+ externalInterface = "vmbr0";
+ internalIPs = [ "192.168.172.0/24" ];
+ };
+}
diff --git a/hosts/valefar/secrets.nix b/hosts/valefar/secrets.nix
new file mode 100644
index 0000000..4deb735
--- /dev/null
+++ b/hosts/valefar/secrets.nix
@@ -0,0 +1,22 @@
+{
+ age.secrets = {
+ "pocket-id-encryption-key" = {
+ file = ../../secrets/pocket-id-encryption-key.age;
+ mode = "0400";
+ };
+ "pocket-id-maxmind-license-key" = {
+ file = ../../secrets/pocket-id-maxmind-license-key.age;
+ mode = "0400";
+ };
+ "pia-wireguard-auth.env" = {
+ file = ../../secrets/pia-wireguard-auth.env.age;
+ mode = "0400";
+ };
+ "vaultwarden-oidc.env" = {
+ file = ../../secrets/vaultwarden-oidc.env.age;
+ owner = "vaultwarden";
+ group = "vaultwarden";
+ mode = "0400";
+ };
+ };
+}
diff --git a/hosts/valefar/tests/nat-guard.nix b/hosts/valefar/tests/nat-guard.nix
new file mode 100644
index 0000000..fa750e4
--- /dev/null
+++ b/hosts/valefar/tests/nat-guard.nix
@@ -0,0 +1,70 @@
+# Run with the flake's pinned nixpkgs:
+# nix build --impure --expr 'let f = builtins.getFlake (toString ./.); in import ./hosts/valefar/tests/nat-guard.nix { pkgs = f.inputs.nixpkgs.legacyPackages.x86_64-linux; }'
+{ pkgs }:
+pkgs.testers.runNixOSTest {
+ name = "valefar-nat-guard";
+ nodes.machine = { lib, ... }: {
+ imports = [ ../nat-guard.nix ];
+ networking.firewall.enable = false;
+ networking.nat = {
+ enable = true;
+ externalInterface = "eth0";
+ internalIPs = [
+ "192.168.172.0/24"
+ "192.168.173.0/24"
+ ];
+ };
+ systemd.services.nat = {
+ preStart = "test ! -e /run/fail-nat";
+ serviceConfig.RestartSec = lib.mkForce "1s";
+ };
+ systemd.timers.nat-healthcheck.timerConfig = {
+ OnBootSec = lib.mkForce "1s";
+ OnUnitInactiveSec = lib.mkForce "2s";
+ };
+ };
+ testScript = ''
+ start_all()
+ rules = " && ".join([
+ "systemctl is-active --quiet nat.service",
+ "iptables -t nat -C POSTROUTING -j nixos-nat-post",
+ "iptables -t filter -C FORWARD -j nixos-filter-forward",
+ *[
+ f"iptables -t nat -C nixos-nat-post -s {subnet} -o eth0 -j MASQUERADE"
+ for subnet in ["192.168.172.0/24", "192.168.173.0/24"]
+ ],
+ ])
+
+ with subtest("boot installs both subnets"):
+ machine.wait_for_unit("nat.service")
+ machine.wait_for_unit("nat-healthcheck.timer")
+ machine.succeed(rules)
+
+ with subtest("healthy checks do not restart NAT"):
+ invocation = machine.succeed("systemctl show nat -p InvocationID --value")
+ machine.succeed("systemctl start nat-healthcheck")
+ assert machine.succeed("systemctl show nat -p InvocationID --value") == invocation
+
+ with subtest("timer recovers a successfully stopped unit"):
+ machine.succeed("systemctl stop nat")
+ machine.wait_until_succeeds(rules)
+
+ with subtest("timer repairs a missing subnet in an active unit"):
+ machine.succeed("iptables -t nat -D nixos-nat-post -s 192.168.173.0/24 -o eth0 -j MASQUERADE")
+ machine.wait_until_succeeds(rules)
+
+ with subtest("timer repairs a detached chain"):
+ machine.succeed("iptables -t nat -D POSTROUTING -j nixos-nat-post")
+ machine.wait_until_succeeds(rules)
+
+ with subtest("failed repair is visible and retried"):
+ machine.succeed("systemctl stop nat-healthcheck.timer; touch /run/fail-nat")
+ machine.fail("systemctl restart nat")
+ machine.fail("systemctl start nat-healthcheck")
+ machine.succeed("systemctl is-failed nat-healthcheck")
+ machine.succeed("mv /run/fail-nat /run/nat-failure-tested")
+ machine.wait_until_succeeds(rules)
+ machine.succeed("systemctl start nat-healthcheck.timer")
+ machine.wait_until_succeeds("test $(systemctl show nat-healthcheck -p Result --value) = success")
+ '';
+}
diff --git a/hosts/valefar/wg-mesh.nix b/hosts/valefar/wg-mesh.nix
new file mode 100644
index 0000000..ff58ad9
--- /dev/null
+++ b/hosts/valefar/wg-mesh.nix
@@ -0,0 +1,63 @@
+# wisp.place mesh: full-mesh WireGuard between the wisp servers, 10.88.0.0/24.
+# valefar sits behind home NAT, so it dials every peer and keeps the paths open.
+# Names resolve through *.mesh.wisp.place; the peer list lives in ~/fleet/mesh.md.
+{ pkgs, ... }:
+{
+ environment.systemPackages = [ pkgs.wireguard-tools ];
+
+ # Containers publish ports on 10.88.0.10; start docker once wg0 exists.
+ systemd.services.docker = {
+ after = [ "wg-quick-wg0.service" ];
+ wants = [ "wg-quick-wg0.service" ];
+ };
+
+ networking.wg-quick.interfaces.wg0 = {
+ address = [ "10.88.0.10/24" ];
+ listenPort = 51820;
+ privateKeyFile = "/etc/wireguard/wg0.key";
+ peers = [
+ {
+ # baal
+ publicKey = "ooHJ1tE5WVfrC4bAX/japIwNahg71tTSNy94k9d5A0Q=";
+ allowedIPs = [ "10.88.0.1/32" ];
+ endpoint = "150.136.127.67:51820";
+ persistentKeepalive = 25;
+ }
+ {
+ # stolas
+ publicKey = "BYA0fbXxWvgK4uYoYiNGLscki4lTGAsKn98AKlw32B8=";
+ allowedIPs = [ "10.88.0.2/32" ];
+ endpoint = "152.53.121.97:51820";
+ persistentKeepalive = 25;
+ }
+ {
+ # sjo1
+ publicKey = "myd1WecGLdP/DtU198anvcPtEk8Iusa1CASru5kall0=";
+ allowedIPs = [ "10.88.0.3/32" ];
+ endpoint = "152.44.44.138:51820";
+ persistentKeepalive = 25;
+ }
+ {
+ # sin1
+ publicKey = "9mAzm703TerlCQoZP61dyJNnMATGVgVuNtdi+JmKWAo=";
+ allowedIPs = [ "10.88.0.4/32" ];
+ endpoint = "213.163.207.16:51820";
+ persistentKeepalive = 25;
+ }
+ {
+ # sharkgirl
+ publicKey = "3rgSbuy6oz8ePF55yF8ZBS03MDhj3aOq83/PgO+DYng=";
+ allowedIPs = [ "10.88.0.5/32" ];
+ endpoint = "15.204.225.63:51820";
+ persistentKeepalive = 25;
+ }
+ {
+ # vine (status page)
+ publicKey = "Iucesg9GHk9AkjewUIdgOiIBMdLBVzfvrTqLQiNecRI=";
+ allowedIPs = [ "10.88.0.6/32" ];
+ endpoint = "144.225.80.116:51820";
+ persistentKeepalive = 25;
+ }
+ ];
+ };
+}
diff --git a/secrets/pia-wireguard-auth.env.age b/secrets/pia-wireguard-auth.env.age
new file mode 100644
index 0000000..57a4e55
--- /dev/null
+++ b/secrets/pia-wireguard-auth.env.age
@@ -0,0 +1,22 @@
+age-encryption.org/v1
+-> ssh-ed25519 i9wBeA oUdGLb/WpJmjxW7QHnKrcoXdEaX3vlAIh3PMuEMTFXU
+xM29aQByEm1QlZe0RAbNfs4w8nVFVnFnoEADqKJLZMQ
+-> ssh-ed25519 rgtFBg Dh9lxSdhL5/GwesWVz6iic1oB9hSQaY9a1lJ+MzbXRI
+DGVzyVeCHLz35iVwQtIQ7WWNbkOSvrtXh43JM0B62x8
+-> ssh-rsa J32+GA
+TMQkZcTbx4u9aOfjrGxMRqI+yZdGJlgG1M1Zl+cOAZQxfGlZcFMRmpK+XfiD72No
+k5lGctvMFe/yCjrOwoNN/NiE/JmGVjjXh93V3IoSI743C8KAmoHsgt7XgF0bwwuq
++HU2uWp6KjoraZQ8XaQCiD9w87HQqptMFhMUVRXxxDPVVGF8sO9SdN5EX5hpOb9t
+eiA8MtyYOnSkwXhsep6Uwac+/OPoc7TOaMWvtIkT1NsMECB4xZ3B8FcFDjwO+jtT
+1eUe0lazmGNkyUf9dSGApfA/c02IUKVqb9drlvYHw0Uj03WDHPLqnGB32i++/Ks+
+Ui/c93wJG743YpeqPadSeZ0UHWFoEPKzgG7NU1rxHq+mvpgMRx/Guc2pkBf9MSrb
+9xL29TMIOxsWiJLjxsun2u9r9AfZoHU/fUJ8Lt1RAOIwV28Jh8tsmF5e+ZaN9NKM
+CkIva/aJOjrGtAknbzPTeX6PY9O7D5YQkzEwUD90Qj4d0CmlLxfbTn6RdsVxsPXR
+gDV77GqCOYKkScsE+QiRwcjPzWR4kv6Su2IOV77/cHewftgfdCYzMytiDSTI4uDq
+1SQRB2HhjHiT9K1otbCJT2dQNPsLxgkZsKWEgmMoCVGl82IOKOT3EEDz6AFiULRD
+bgcMF+S94WbtS8Q/PBdPjIpC54+HzPswlusoOSr1GWI
+-> ssh-ed25519 du7llw qFql+PH0TSCLdumpFTVsTNUOsKQbJO6K7nUUXC4Vrzo
+NvA2H0/6MQ63J2gPqG0mf8txq+/UNNstXdTHyXlrC/U
+--- okqLzInxo2gNlLt6QaamSRGmeYwmMhu0bSTmVYfOPSs
+W�͎%���
+�}�Jw���i�,bl����-����4s�k������RˌG�&&q��j�t����a��P�
\ No newline at end of file
diff --git a/secrets/pocket-id-encryption-key.age b/secrets/pocket-id-encryption-key.age
new file mode 100644
index 0000000000000000000000000000000000000000..23fe8e0c37cf16bfd328e0e4a9a13dd4b98d487d
GIT binary patch
literal 576
zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCU7v@CZ@byP5_$V|_-
za5Z%F^w9Px&$KYjNiFrL^vj5{tVk~iaZU5{PxUl)HOdUB2<37uO?L}VDs*!y&vi6S
zEGy3}GcYs=GD`|IFe@(e3d~7Mi8A&HPjYk4jYPLCrPMqpr(7Y?KPpi_Fg>q4(Iqu2
z(xo`9q%1Vj+$+N~%rzs##5^U}C|5hk-QTn{*psW&*TcoTEGs1`$U7t}tkfmZ)WS0>
z*x1L*H?c6xGQy`U!#KyK$kI2>IUC)!$jB;3^FReB=k#C$k9125SL3wIuypOpD1Czx
zcf+jYvh))33{SJ-K_e3{SKc9l^#ER5{@X)fN3in*YfK2Zo@6f=g
z^Z?^3%gnN%Y-7Xp99MMPjDx}p0|FH+tNhdTLkyhDqjL2V-HftBEM1BN%JR!9O@s4_
z^ov3rle~+KynI43Bg(n71I;}O!V>d}BC@A5JEfR2qg{MA->`b}K`Hv+
ahrP$&ocg9QZ*KYqksmJv=I`tFQ3U{EUc~7D
literal 0
HcmV?d00001
diff --git a/secrets/pocket-id-maxmind-license-key.age b/secrets/pocket-id-maxmind-license-key.age
new file mode 100644
index 0000000000000000000000000000000000000000..ee9855887226121efbc8cf50fd33246ac877c6f7
GIT binary patch
literal 542
zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCU7v@CZ@byP^sh)hg&
zj|?xZG_`O_F3ry=3ePSyNeU=6N%Jf5Ei5p|3{J1`b2ClL3FLAMO!x6h3eq+RGD>uD
z&dN_q^zwIcF(?T)3^p?>Hp)*a_A<(kbPOvC%}2K_rPMqpr(D4zE2PLZ+t<(1DL=?S
zJHR}muq4MNBqJ)h%FH`2BF)pwF}cV$!oc6ju$;>v#L_9;qbwshRJ+Q@GQy;=*eNeF
z$uHBrD$GMW$gMCa)Z3)oC?&|hDiqze$jB;3^FW2vu);u7ui&U)mt>dnWJ{0yBEy{G
z?8>~L+{Ey-h%nzk-?Y%&tTbaMLq{$HeQyi>jEZtYXA>{er1D~y;<94TP?tbQ-vIsO
z4D+O7r;;ddN2h@FyhwE0jDx}p0|FIHwR2OvLVbEAzQL9UTLce4`9={R~S})5^3f%Jaj`D$CunLMlSj((`hX!ZIQ)
z4J}@1|d^4T3^Rt~jz5QJ+
pOgy>%Y?7Vu`N!i6Z|b*h?P-6O9c|NiE5jmAjb)?6l$%xzj{v(uwQv9c
literal 0
HcmV?d00001
diff --git a/secrets/regent.nix b/secrets/regent.nix
new file mode 100644
index 0000000..a7112ca
--- /dev/null
+++ b/secrets/regent.nix
@@ -0,0 +1,27 @@
+let
+ regent = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ0pU82lV9dSjkgYbdh9utZ5CDM2dPN70S5fBqN1m3Pb regent@orobas.local";
+ users = [ regent ];
+
+ valefar = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPu8CVFsnUxhvABEqv4+EBBOL8tva5HJFoV3hElAlD0";
+ buer = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMVhjwDcO8eleSoR8a37ZGGPvkHEgV+c8SYcy07SayPB";
+ focalor = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA518oTmTp5VG60/dBrLu7rlV1hh8muhMattoiGfmrei";
+ systems = [
+ valefar
+ buer
+ focalor
+ ];
+in
+{
+ "pocket-id-encryption-key.age".publicKeys = users ++ systems;
+ "pocket-id-maxmind-license-key.age".publicKeys = users ++ systems;
+
+ "vaultwarden-oidc.env.age".publicKeys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPu8CVFsnUxhvABEqv4+EBBOL8tva5HJFoV3hElAlD0 root@valefar"
+ ];
+ "pia-wireguard-auth.env.age".publicKeys = [
+ regent
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ7Y9Je7H3gC72cgdEH4wifUDsmhKMeU5Z4oL1s1WcSE niri@nekomimi.pet"
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCM1KHYX8icUv9XxV0QDgsaXE05nA8gaZ0O3OwP/vH0olpeXc13FxoFKvNVYo1aWCo8xjv9G01JrWNhBRFf76LyOJhL1cm3Psorcx0V7sdbRy9gWnPRR5tA58wKr51yvz/4I+KsrO537cTAVZQky9J2wDh9jDpiNQJN80Kv9RdrFc3BVrEXUrsE3YtsNiSg4YoAPD4vhLupVohMPoh/w3dtdTQBu+YF+1rjMJ9Xf22DaVqz6l95T/Zui1smQnIZbjXRibJ8RR3Kla7K94nqVvNMVURiK71vXh+bJvFE2HyDDNsVteAv2DxpXQJpojxGQUXvR1LGQP9Lm5yhdtTqZMeuTDOKCCTy3yMGX4tX09ZyP5S0WwgTh2vE33C4+gUFa4wSeGZj7IH2t4ivgxK17fkGIeMcPpPuwkIWbinCp5AXPBqly5OBry6Qyg8SN1jb+d6pW3sn3slsikH53B4Qx5PP1ywCSuKnboOn9pQqZt7uvs75W2oyYxdmh/SNx//dcNX16YLYJ1KfaGpxerT6AjfiHSB8Vp8n6N7bVmizsqg0nkU+SmF8gh3UKidyg2RzL+zcJcxp+APiumM0rs3fXFNBuT8VncOPbiyjSw/NUt3EiuBGIap5mnJF+zW965YqZAo+Upivqltdah0E9WJrF4t7Z5+FyuLi3N5ovyROEEAlNw== root@valefar"
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPu8CVFsnUxhvABEqv4+EBBOL8tva5HJFoV3hElAlD0 root@valefar"
+ ];
+}
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index 4182daf..8dbf4dd 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -40,4 +40,4 @@ in
yusdacra
trimounts
];
-}
+} // import ./regent.nix
diff --git a/secrets/vaultwarden-oidc.env.age b/secrets/vaultwarden-oidc.env.age
new file mode 100644
index 0000000000000000000000000000000000000000..7227ebe8af05874643acedd7e87be953a2164149
GIT binary patch
literal 314
zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCSnDK*c@DOU(K^7jeN
zGI34y&nPf6$qlnGDKc@)j|eOC^UF;%Do-uZj||Q(&UYzH_vA7tN=>xPHYoQ<_A50A
zFYz;T%5_cmHOz1duP_fPPEQOe(RMQ_@k=$xcI494)m8Ad%n31Zeg0tBP<8EO2(y
zPW38CcMdi;3Mq>)OZW0GOwV;P%?wOWi{#q1|6RaxQR@QHKF(jxbx)NAzU?Y`aNA@0
zweM@BoE@0=G4lxBf1?m3X|UJEK;Y~Cm0tU_l|6mi-q$lux_{%td*6HB(-@pKS$=ff", builtin.find_files, { desc = "Find files" })
+ vim.keymap.set("n", "fg", builtin.live_grep, { desc = "Live grep" })
+ vim.keymap.set("n", "fb", builtin.buffers, { desc = "Buffers" })
+ vim.keymap.set("n", "fh", builtin.help_tags, { desc = "Help" })
+
+ local cmp = require("cmp")
+ local luasnip = require("luasnip")
+ cmp.setup({
+ snippet = { expand = function(args) luasnip.lsp_expand(args.body) end },
+ mapping = cmp.mapping.preset.insert({
+ [""] = cmp.mapping.complete(),
+ [""] = cmp.mapping.confirm({ select = true }),
+ [""] = cmp.mapping.select_next_item(),
+ [""] = cmp.mapping.select_prev_item(),
+ }),
+ sources = cmp.config.sources({ { name = "nvim_lsp" } }),
+ })
+
+ local capabilities = require("cmp_nvim_lsp").default_capabilities()
+ for _, server in ipairs({ "bashls", "lua_ls", "nil_ls", "pyright", "rust_analyzer", "ts_ls" }) do
+ vim.lsp.config(server, { capabilities = capabilities })
+ vim.lsp.enable(server)
+ end
+ vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "Go to definition" })
+ vim.keymap.set("n", "gr", vim.lsp.buf.references, { desc = "References" })
+ vim.keymap.set("n", "K", vim.lsp.buf.hover, { desc = "Hover documentation" })
+ vim.keymap.set("n", "rn", vim.lsp.buf.rename, { desc = "Rename" })
+ vim.keymap.set("n", "ca", vim.lsp.buf.code_action, { desc = "Code action" })
+ vim.keymap.set("n", "f", function() vim.lsp.buf.format({ async = true }) end, { desc = "Format" })
+ '';
+ };
+
+ home.pointerCursor = {
+ enable = true;
+ gtk.enable = true;
+ package = pkgs.phinger-cursors;
+ name = "Phinger-cursors-light";
+ size = 32;
+ };
+
+ gtk = {
+ enable = true;
+ font = {
+ name = "Comic Neue";
+ size = 11;
+ };
+ };
+
+ xdg.configFile."niri/config.kdl".text = ''
+ input {
+ keyboard {
+ xkb {
+ layout "us"
+ }
+ }
+ touchpad {
+ tap
+ natural-scroll
+ }
+ mouse {
+ accel-speed -0.25
+ accel-profile "flat"
+ }
+ focus-follows-mouse max-scroll-amount="0%"
+ }
+
+ window-rule {
+ geometry-corner-radius 20
+
+ clip-to-geometry true
+ }
+
+ window-rule {
+ match app-id=r#"(?i)steam_app|aoe2|wine"#
+ geometry-corner-radius 0
+ clip-to-geometry false
+ }
+
+ window-rule {
+ match app-id="dev.noctalia.Noctalia"
+ open-floating true
+ default-column-width { fixed 1080; }
+ default-window-height { fixed 920; }
+ }
+
+ debug {
+ render-drm-device "/dev/dri/renderD129"
+ ignore-drm-device "/dev/dri/renderD128"
+ honor-xdg-activation-with-invalid-serial
+ }
+
+ layer-rule {
+ match namespace="^noctalia-backdrop"
+ place-within-backdrop true
+ }
+
+ layer-rule {
+ match namespace="^noctalia-wallpaper"
+ place-within-backdrop true
+ }
+
+ overview {
+ workspace-shadow {
+ off
+ }
+ }
+
+ window-rule {
+ exclude app-id="com\\.mitchellh\\.ghostty"
+ background-effect {
+ blur true
+ xray false
+ }
+ }
+
+ layer-rule {
+ match namespace="^noctalia-(bar-[^\"]+|notification|dock|panel|attached-panel|osd)$"
+ background-effect {
+ xray false
+ }
+ }
+
+ blur {
+ passes 2
+ offset 3.0
+ noise 0.03
+ saturation 1.0
+ }
+
+ layout {
+ background-color "transparent"
+ gaps 12
+ center-focused-column "never"
+ default-column-width {
+ proportion 0.5
+ }
+ focus-ring {
+ width 2
+ active-color "#89b4fa"
+ inactive-color "#45475a"
+ }
+ border {
+ off
+ }
+ }
+
+ prefer-no-csd
+ screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+ spawn-at-startup "noctalia"
+ spawn-at-startup "xwayland-satellite"
+
+ // Match by make/model/serial: connector names shift across GPU/driver
+ // updates (HDMI-A-3 to HDMI-A-1, DP-4 to DP-2) and silently orphan the blocks.
+ output "Microstep MAG 341C OLED 0x01010101" {
+ mode "3440x1440@174.962"
+ position x=0 y=0
+ }
+
+ output "ASUSTek COMPUTER INC ASUS PA279CV R9LMTF061509" {
+ mode "3840x2160@59.997"
+ scale 1.5
+ position x=3440 y=0
+ }
+
+ binds {
+ Mod+Return { spawn "ghostty"; }
+ Mod+Space { spawn-sh "noctalia msg panel-toggle launcher"; }
+ Mod+D { spawn-sh "noctalia msg panel-toggle launcher"; }
+ Mod+S { spawn-sh "noctalia msg panel-toggle control-center"; }
+ Mod+Comma { spawn-sh "noctalia msg settings-toggle"; }
+ Mod+Alt+L { spawn "noctalia" "ipc" "call" "lockScreen" "lock"; }
+ Mod+Shift+Escape { quit; }
+ Mod+Q { close-window; }
+ Mod+H { focus-column-left; }
+ Mod+J { focus-window-down; }
+ Mod+K { focus-window-up; }
+ Mod+L { focus-column-right; }
+ Mod+Shift+Q { move-column-left; }
+ Mod+Shift+J { move-window-down; }
+ Mod+Shift+K { move-window-up; }
+ Mod+Shift+E { move-column-right; }
+ Mod+Left { focus-column-left; }
+ Mod+Down { focus-window-down; }
+ Mod+Up { focus-window-up; }
+ Mod+Right { focus-column-right; }
+ Mod+Shift+Left { move-column-left; }
+ Mod+Shift+Down { move-window-down; }
+ Mod+Shift+Up { move-window-up; }
+ Mod+Shift+Right { move-column-right; }
+ Mod+1 { focus-workspace 1; }
+ Mod+2 { focus-workspace 2; }
+ Mod+3 { focus-workspace 3; }
+ Mod+4 { focus-workspace 4; }
+ Mod+5 { focus-workspace 5; }
+ Mod+Shift+X { maximize-column; }
+ Mod+Shift+F { fullscreen-window; }
+ Mod+C { center-column; }
+ Mod+V { toggle-window-floating; }
+ Print { screenshot; }
+ XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "noctalia msg volume-up"; }
+ XF86AudioLowerVolume allow-when-locked=true { spawn-sh "noctalia msg volume-down"; }
+ XF86AudioMute allow-when-locked=true { spawn-sh "noctalia msg volume-mute"; }
+ XF86MonBrightnessUp { spawn-sh "noctalia msg brightness-up"; }
+ XF86MonBrightnessDown { spawn-sh "noctalia msg brightness-down"; }
+ }
+ '';
+}