From 2a911b057b3f8976c9bd1a83e94f6e3f2941738b Mon Sep 17 00:00:00 2001 From: OlivierChiasson Date: Thu, 4 Jun 2026 16:51:30 -0300 Subject: [PATCH] Add navi deployment module and integrate SSH inventory for remote management - Introduced a new `navi` module for managing deployments across multiple hosts. - Enhanced SSH inventory management to support public key application for authorized hosts. - Configured system deployment builder for seamless integration with Navi. - Updated various host configurations to enable deployment capabilities and streamline SSH access. --- flake.lock | 325 +++++++++++++++++++-- flake.nix | 2 + modules/deploy/navi.nix | 149 ++++++++++ modules/hosts/client-services.nix | 7 +- modules/hosts/nix-server/configuration.nix | 3 + modules/hosts/r5500/configuration.nix | 3 + modules/lib/navi-hive.nix | 33 +++ modules/lib/ssh-inventory.nix | 20 +- modules/ssh/nixos/default.nix | 24 +- modules/system/deploy/builder.nix | 49 ++++ modules/system/users/catalog-default.nix | 14 + 11 files changed, 585 insertions(+), 44 deletions(-) create mode 100644 modules/deploy/navi.nix create mode 100644 modules/lib/navi-hive.nix create mode 100644 modules/system/deploy/builder.nix diff --git a/flake.lock b/flake.lock index 499326c..10ab17e 100644 --- a/flake.lock +++ b/flake.lock @@ -16,6 +16,21 @@ "type": "github" } }, + "crane": { + "locked": { + "lastModified": 1770169865, + "narHash": "sha256-iPiy13xzDQ9GjpOez+NNIjh/qjl7i4RDf9dF2x5mF9I=", + "owner": "ipetkov", + "repo": "crane", + "rev": "8254ccf3b5b5131890ee073776f2e61c6d1e55d4", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "cursor": { "inputs": { "nixpkgs": [ @@ -23,11 +38,11 @@ ] }, "locked": { - "lastModified": 1779537840, - "narHash": "sha256-IS3aolEKgyL0VuMfd/QX2AHvur1YukCTa6eZdxQWe1A=", + "lastModified": 1780452866, + "narHash": "sha256-Fq5cR/qGYcGwOOZf6sShtM+kljwecgntgcKXnIBrQDE=", "ref": "refs/heads/main", - "rev": "8d9c19f98abf47aa4504efa8d2233730b4afed50", - "revCount": 109, + "rev": "344f4065e69455adbcdfb4db1cd8d07a73ae7140", + "revCount": 114, "type": "git", "url": "https://git.chiasson.cloud/Olivier/cursor-nixos-flake" }, @@ -52,6 +67,7 @@ "url": "https://git.chiasson.cloud/Olivier/DDRM" }, "original": { + "rev": "c3d014f96855e45b53f7391ce19729493887cb96", "type": "git", "url": "https://git.chiasson.cloud/Olivier/DDRM" } @@ -76,6 +92,29 @@ "type": "github" } }, + "disko": { + "inputs": { + "nixpkgs": [ + "navi", + "nixos-anywhere", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769524058, + "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=", + "owner": "nix-community", + "repo": "disko", + "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "master", + "repo": "disko", + "type": "github" + } + }, "dms": { "inputs": { "nixpkgs": [ @@ -149,6 +188,46 @@ "inputs": { "nixpkgs-lib": "nixpkgs-lib_2" }, + "locked": { + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": [ + "navi", + "nixos-anywhere", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1768135262, + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_4": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_3" + }, "locked": { "lastModified": 1778716662, "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", @@ -163,7 +242,7 @@ "type": "github" } }, - "flake-parts_3": { + "flake-parts_5": { "inputs": { "nixpkgs-lib": [ "nur", @@ -292,6 +371,50 @@ "type": "github" } }, + "navi": { + "inputs": { + "crane": "crane", + "flake-parts": "flake-parts_2", + "nix-github-actions": "nix-github-actions", + "nixos-anywhere": "nixos-anywhere", + "nixpkgs": "nixpkgs", + "stable": "stable" + }, + "locked": { + "lastModified": 1777283737, + "narHash": "sha256-sVwTOYwxzUUqAEr5kjOZooJ/v8H2hcuZ42f17MgCqX8=", + "owner": "cafkafk", + "repo": "navi", + "rev": "b060683e4ca2bd37a1c3bcb4e04abd2296190e9d", + "type": "github" + }, + "original": { + "owner": "cafkafk", + "repo": "navi", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "navi", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1729742964, + "narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "e04df33f62cdcf93d73e9a04142464753a16db67", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, "nix-monitor": { "locked": { "lastModified": 1771568669, @@ -307,10 +430,33 @@ "type": "github" } }, + "nix-vm-test": { + "inputs": { + "nixpkgs": [ + "navi", + "nixos-anywhere", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769079217, + "narHash": "sha256-R6qzhu+YJolxE2vUsPQWWwUKMbAG5nXX3pBtg8BNX38=", + "owner": "Enzime", + "repo": "nix-vm-test", + "rev": "58c15f78947b431d6c206e0966500c7e9139bd2f", + "type": "github" + }, + "original": { + "owner": "Enzime", + "ref": "pr-105-latest", + "repo": "nix-vm-test", + "type": "github" + } + }, "nixcord": { "inputs": { "flake-compat": "flake-compat", - "flake-parts": "flake-parts_2", + "flake-parts": "flake-parts_4", "nixpkgs": [ "nixpkgs" ], @@ -330,7 +476,62 @@ "type": "github" } }, + "nixos-anywhere": { + "inputs": { + "disko": "disko", + "flake-parts": "flake-parts_3", + "nix-vm-test": "nix-vm-test", + "nixos-images": "nixos-images", + "nixos-stable": "nixos-stable", + "nixpkgs": [ + "navi", + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1772874867, + "narHash": "sha256-d79nCSys2aaV6E1tL/H4xEeKJvLFwEeP6yj5q0glbuQ=", + "owner": "cafkafk", + "repo": "nixos-anywhere", + "rev": "b88c2ee9734ce4ec5aa63cf74c2ea58f6f9970ec", + "type": "github" + }, + "original": { + "owner": "cafkafk", + "ref": "kexec-ssh-args-bug", + "repo": "nixos-anywhere", + "type": "github" + } + }, "nixos-images": { + "inputs": { + "nixos-stable": [ + "navi", + "nixos-anywhere", + "nixos-stable" + ], + "nixos-unstable": [ + "navi", + "nixos-anywhere", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1766770015, + "narHash": "sha256-kUmVBU+uBUPl/v3biPiWrk680b8N9rRMhtY97wsxiJc=", + "owner": "nix-community", + "repo": "nixos-images", + "rev": "e4dba54ddb6b2ad9c6550e5baaed2fa27938a5d2", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-images", + "type": "github" + } + }, + "nixos-images_2": { "inputs": { "nixos-stable": [ "nixos-raspberrypi", @@ -360,7 +561,7 @@ "inputs": { "argononed": "argononed", "flake-compat": "flake-compat_2", - "nixos-images": "nixos-images", + "nixos-images": "nixos-images_2", "nixpkgs": [ "nixpkgs" ] @@ -380,17 +581,33 @@ "type": "github" } }, - "nixpkgs": { + "nixos-stable": { "locked": { - "lastModified": 1779357205, - "narHash": "sha256-cCO8aTqss5x9Ky8GWkpY0Hy5fyTZEbtifSUV8QjSzic=", - "owner": "nixos", + "lastModified": 1769598131, + "narHash": "sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "f83fc3c307e74bc5fd5adb7eb6b8b13ffd2a36e1", + "rev": "fa83fd837f3098e3e678e6cf017b2b36102c7211", "type": "github" }, "original": { - "owner": "nixos", + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1750134718, + "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c", + "type": "github" + }, + "original": { + "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" @@ -412,6 +629,21 @@ } }, "nixpkgs-lib_2": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_3": { "locked": { "lastModified": 1777168982, "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", @@ -443,6 +675,22 @@ } }, "nixpkgs_2": { + "locked": { + "lastModified": 1779357205, + "narHash": "sha256-cCO8aTqss5x9Ky8GWkpY0Hy5fyTZEbtifSUV8QjSzic=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "f83fc3c307e74bc5fd5adb7eb6b8b13ffd2a36e1", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1778869304, "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", @@ -460,7 +708,7 @@ }, "nur": { "inputs": { - "flake-parts": "flake-parts_3", + "flake-parts": "flake-parts_5", "nixpkgs": [ "nixpkgs" ] @@ -556,10 +804,11 @@ "home-manager": "home-manager", "import-tree": "import-tree", "mobile-nixos": "mobile-nixos", + "navi": "navi", "nix-monitor": "nix-monitor", "nixcord": "nixcord", "nixos-raspberrypi": "nixos-raspberrypi", - "nixpkgs": "nixpkgs", + "nixpkgs": "nixpkgs_2", "nur": "nur", "oom-hardware": "oom-hardware", "personal-website": "personal-website", @@ -614,6 +863,22 @@ "type": "github" } }, + "stable": { + "locked": { + "lastModified": 1750133334, + "narHash": "sha256-urV51uWH7fVnhIvsZIELIYalMYsyr2FCalvlRTzqWRw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "36ab78dab7da2e4e27911007033713bab534187b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, "swiftshare": { "inputs": { "flake-utils": "flake-utils_2", @@ -703,11 +968,11 @@ "t2linux-patches": { "flake": false, "locked": { - "lastModified": 1779369552, - "narHash": "sha256-vDcWjgjhYAQcXZH40QN17ZV9BS0zqZeme9APXBqjlHs=", + "lastModified": 1779914035, + "narHash": "sha256-VsHuI2CbQ8gFplW+51gUJvCqo1Ts10Ueks9aTtkAOiw=", "owner": "t2linux", "repo": "linux-t2-patches", - "rev": "716093d3244566cd708362661de269ab7e67ff0f", + "rev": "7ee7d19c38e5df31a386b2a0c35ca8f064003960", "type": "github" }, "original": { @@ -716,6 +981,28 @@ "type": "github" } }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "navi", + "nixos-anywhere", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769691507, + "narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "wallpapers": { "flake": false, "locked": { @@ -734,7 +1021,7 @@ }, "wrapper-modules": { "inputs": { - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_3" }, "locked": { "lastModified": 1779297405, diff --git a/flake.nix b/flake.nix index d0d8712..c396f2d 100644 --- a/flake.nix +++ b/flake.nix @@ -103,6 +103,8 @@ url = "git+https://git.chiasson.cloud/Olivier/DDRM?rev=c3d014f96855e45b53f7391ce19729493887cb96"; inputs.nixpkgs.follows = "nixpkgs"; }; + + navi.url = "github:cafkafk/navi"; }; outputs = inputs: diff --git a/modules/deploy/navi.nix b/modules/deploy/navi.nix new file mode 100644 index 0000000..d034e62 --- /dev/null +++ b/modules/deploy/navi.nix @@ -0,0 +1,149 @@ +{ + inputs, + self, + lib, + ... +}: +let + ssh = self.lib.sshInventory; + + # Remote deploy identity (see `nixosModules.systemDeployBuilder`). + defaultTargetUser = _: "builder"; + + hostSpecs = { + "14900k" = { + configuration = self.nixosModules."14900kConfiguration"; + system = "x86_64-linux"; + specialArgs = { + inherit self inputs; + host = "14900k"; + system = "x86_64-linux"; + }; + }; + ideapad = { + configuration = self.nixosModules.ideapadConfiguration; + system = "aarch64-linux"; + specialArgs = { + inherit self inputs; + host = "ideapad"; + system = "aarch64-linux"; + }; + }; + t2mbp = { + configuration = self.nixosModules.t2mbpConfiguration; + system = "x86_64-linux"; + specialArgs = { + inherit self inputs; + host = "t2mbp"; + system = "x86_64-linux"; + }; + }; + uConsole = { + modules = [ + inputs.nixos-raspberrypi.nixosModules.raspberry-pi-5.base + inputs.oom-hardware.nixosModules.uc.kernel + inputs.oom-hardware.nixosModules.uc.configtxt + inputs.oom-hardware.nixosModules.uc.base-cm5 + self.nixosModules.uConsoleConfiguration + ]; + system = "aarch64-linux"; + specialArgs = inputs // { + inherit self; + inputs = inputs; + host = "uConsole"; + system = "aarch64-linux"; + }; + }; + nix-server = { + configuration = self.nixosModules.nix-serverConfiguration; + system = "x86_64-linux"; + specialArgs = { + inherit self inputs; + host = "nix-server"; + system = "x86_64-linux"; + }; + }; + r5500 = { + configuration = self.nixosModules.r5500Configuration; + system = "x86_64-linux"; + specialArgs = { + inherit self inputs; + host = "r5500"; + system = "x86_64-linux"; + }; + }; + }; + + deployments = lib.mapAttrs ( + name: entry: + { + targetHost = entry.hostName; + targetUser = defaultTargetUser name; + tags = + [ name ] + ++ lib.optionals (name == "nix-server") [ "server" ] + ++ lib.optionals (lib.elem name [ + "ideapad" + "uConsole" + ]) [ "aarch64" ]; + } + // lib.optionalAttrs (name == "14900k") { + allowLocalDeployment = true; + } + // lib.optionalAttrs (name == "nix-server") { + targetPort = 22; + } + ) ssh.activeHosts; + + metaNixpkgs = import inputs.nixpkgs { + system = "x86_64-linux"; + }; +in +{ + flake.navi = self.lib.mkNaviHiveConfig { + inherit metaNixpkgs hostSpecs deployments; + }; + + flake.naviHive = inputs.navi.lib.makeHive self.outputs.navi; + + perSystem = + { + pkgs, + system, + ... + }: + lib.optionalAttrs (lib.elem system [ + "x86_64-linux" + "aarch64-linux" + ]) { + devShells.default = pkgs.mkShell { + packages = [ inputs.navi.packages.${system}.default ]; + shellHook = '' + echo "Navi fleet deploy (from repo root):" + echo " navi apply --on # build + switch one host" + echo " navi apply-local --node 14900k --sudo # switch this machine locally (needs root), --node if hostname differs" + echo " navi tui # interactive fleet dashboard" + ''; + }; + + apps = { + navi = { + type = "app"; + program = lib.getExe inputs.navi.packages.${system}.default; + }; + navi-tui = { + type = "app"; + program = + toString ( + pkgs.writeShellApplication { + name = "navi-tui"; + runtimeInputs = [ inputs.navi.packages.${system}.default ]; + text = '' + exec navi tui "$@" + ''; + } + ); + }; + }; + }; +} diff --git a/modules/hosts/client-services.nix b/modules/hosts/client-services.nix index d920ade..70e35a4 100644 --- a/modules/hosts/client-services.nix +++ b/modules/hosts/client-services.nix @@ -1,7 +1,12 @@ { self, ... }: { #TODO[epic=Moderate] Move this somewhere else, would prefer not relying on this module flake.nixosModules."client-services" = { ... }: { - imports = [ self.nixosModules.systemBluetooth ]; + imports = [ + self.nixosModules.systemBluetooth + self.nixosModules.systemDeployBuilder + ]; + + chiasson.system.deploy.builder.enable = true; # Lab-ish SSH defaults on clients — tighten for anything exposed. services.openssh = { diff --git a/modules/hosts/nix-server/configuration.nix b/modules/hosts/nix-server/configuration.nix index 951934e..6c4cc8b 100644 --- a/modules/hosts/nix-server/configuration.nix +++ b/modules/hosts/nix-server/configuration.nix @@ -10,6 +10,7 @@ { imports = [ inputs.ddrm.nixosModules.default + self.nixosModules.systemDeployBuilder self.nixosModules.nix-serverHardware inputs.sops-nix.nixosModules.sops self.nixosModules.system @@ -83,6 +84,8 @@ extraPackages = with pkgs; [ btop ]; }; + chiasson.system.deploy.builder.enable = true; + chiasson.users = { enabled = [ "server" ]; hostOverrides.server = { diff --git a/modules/hosts/r5500/configuration.nix b/modules/hosts/r5500/configuration.nix index fa5a162..7f4c93f 100644 --- a/modules/hosts/r5500/configuration.nix +++ b/modules/hosts/r5500/configuration.nix @@ -9,6 +9,7 @@ }: { imports = [ + self.nixosModules.systemDeployBuilder self.nixosModules.r5500Hardware inputs.sops-nix.nixosModules.sops self.nixosModules.system @@ -55,6 +56,8 @@ extraPackages = with pkgs; [ btop git ]; }; + chiasson.system.deploy.builder.enable = true; + chiasson.users = { enabled = [ "server" ]; hostOverrides.server = { diff --git a/modules/lib/navi-hive.nix b/modules/lib/navi-hive.nix new file mode 100644 index 0000000..7ee7c61 --- /dev/null +++ b/modules/lib/navi-hive.nix @@ -0,0 +1,33 @@ +# Build a raw Navi hive attrset from host specs + deployment targets. +# Call `inputs.navi.lib.makeHive` on the result to produce `flake.naviHive`. +{ lib, inputs, ... }: { + flake.lib.mkNaviHiveConfig = + { + metaNixpkgs, + hostSpecs, + deployments, + }: + let + deployNodes = lib.filterAttrs (name: _: deployments ? ${name}) hostSpecs; + in + { + meta = { + nixpkgs = metaNixpkgs; + nodeNixpkgs = lib.mapAttrs ( + name: spec: + import inputs.nixpkgs { + system = spec.system; + } + ) deployNodes; + nodeSpecialArgs = lib.mapAttrs (_: spec: spec.specialArgs) deployNodes; + allowApplyAll = false; + }; + } + // lib.mapAttrs ( + name: spec: + { + imports = spec.modules or [ spec.configuration ]; + deployment = deployments.${name}; + } + ) deployNodes; +} diff --git a/modules/lib/ssh-inventory.nix b/modules/lib/ssh-inventory.nix index 0b25737..de12fbb 100644 --- a/modules/lib/ssh-inventory.nix +++ b/modules/lib/ssh-inventory.nix @@ -60,10 +60,17 @@ (builtins.attrNames selectedHosts) ); - # Must come before inventory `Host` blocks and before `Host *`: LAN Gitea SSH is not a catalog PC, - # and `Host *` sets `IdentityAgent none` — without this, git@nix-server never sees rbw keys. + # Gitea git-over-SSH listens on port 222. System SSH (nix deploy, server@…) uses port 22 + # via the catalog `nix-server` Host block — never list nix-server or 192.168.2.238 here. giteaSshBlock = identityAgent: '' - Host git.chiasson.cloud gitea nix-server 192.168.2.238 + Host git.chiasson.cloud gitea + HostName 192.168.2.238 + Port 222 + User git + IdentityAgent ${identityAgent} + IdentitiesOnly no + + Match host nix-server,192.168.2.238 user git HostName 192.168.2.238 Port 222 User git @@ -84,11 +91,16 @@ entry = selectedHosts.${hostName}; hostPatterns = builtins.concatStringsSep " " (entry.aliases ++ [ entry.hostName ]); userLine = if user == null then "" else " User ${user}\n"; + portLine = + if hostName == "nix-server" then + " Port 22\n" + else + ""; in '' Host ${hostPatterns} HostName ${entry.hostName} -${userLine} IdentityFile ~/${mkIdentityFileName hostName} +${userLine}${portLine} IdentityFile ~/${mkIdentityFileName hostName} IdentityAgent ${identityAgent} IdentitiesOnly yes '') diff --git a/modules/ssh/nixos/default.nix b/modules/ssh/nixos/default.nix index 08ff828..34f4e07 100644 --- a/modules/ssh/nixos/default.nix +++ b/modules/ssh/nixos/default.nix @@ -7,26 +7,10 @@ let cfg = config.chiasson.ssh.inbound; inventory = self.lib.sshInventory; - resolveSelection = - selection: - if selection == "all" then - inventory.authorizedKeys - else - let - missing = builtins.filter (name: !(builtins.hasAttr name inventory.hosts)) selection; - in - if missing != [ ] then - throw "ssh.inbound: unknown host keys: ${builtins.concatStringsSep ", " missing}" - else - lib.unique ( - builtins.filter (key: key != null) ( - builtins.map (hostName: inventory.hosts.${hostName}.publicKey) selection - ) - ); in { options.chiasson.ssh.inbound = { - enable = lib.mkEnableOption "Apply `authorizedKeys` from the SSH inventory."; + enable = lib.mkEnableOption "Apply SSH inventory public keys to `authorized_keys`."; userAuthorizedHosts = lib.mkOption { type = lib.types.attrsOf (lib.types.either (lib.types.enum [ "all" ]) (lib.types.listOf lib.types.str)); default = { }; @@ -35,15 +19,15 @@ admin = [ "14900k" "t2mbp" ]; }; description = '' - Per user: `"all"` or a list of inventory host names whose keys land in `authorized_keys`. + Catalog users that receive the SSH inventory public keys in `authorized_keys`. ''; }; }; config = lib.mkIf cfg.enable { users.users = lib.mapAttrs - (_user: selection: { - openssh.authorizedKeys.keys = resolveSelection selection; + (_user: _selection: { + openssh.authorizedKeys.keys = inventory.authorizedKeys; }) cfg.userAuthorizedHosts; }; diff --git a/modules/system/deploy/builder.nix b/modules/system/deploy/builder.nix new file mode 100644 index 0000000..3ff1a13 --- /dev/null +++ b/modules/system/deploy/builder.nix @@ -0,0 +1,49 @@ +# Navi / remote-deploy identity: push closures + activate system profiles over SSH. +{ ... }: { + flake.nixosModules.systemDeployBuilder = + { config, lib, pkgs, ... }: + let + cfg = config.chiasson.system.deploy.builder; + in + { + options.chiasson.system.deploy.builder = { + enable = lib.mkEnableOption '' + Fleet deploy user for Navi (and similar tools). + + Creates the `builder` catalog user, trusts it with the Nix daemon for + `nix copy`, and grants passwordless sudo for non-interactive activation. + SSH inbound is limited to the deploy machine key (see catalog `builder.ssh`). + ''; + }; + + config = lib.mkIf cfg.enable { + chiasson.users.enabled = lib.mkAfter [ "builder" ]; + + users.users.builder = { + password = "!"; + # nix copy / navi push opens an SSH session; nologin breaks the store protocol. + shell = pkgs.bash; + }; + + nix.settings.trusted-users = lib.mkAfter [ "builder" ]; + + # Navi wraps remote steps in `sudo -H --` (nix-env, switch-to-configuration, + # provenance under /etc/navi, readlink, …). Scoped store-path rules are fragile; + # this account has no wheel; SSH/key-only in practice (password locked). + security.sudo.extraRules = [ + { + users = [ "builder" ]; + commands = [ + { + command = "ALL"; + options = [ + "NOPASSWD" + "SETENV" + ]; + } + ]; + } + ]; + }; + }; +} diff --git a/modules/system/users/catalog-default.nix b/modules/system/users/catalog-default.nix index 3ef3409..40ae68d 100644 --- a/modules/system/users/catalog-default.nix +++ b/modules/system/users/catalog-default.nix @@ -77,6 +77,20 @@ }; }; }; + + builder = { + isNormalUser = true; + description = "Navi fleet deploy (push + activate only)"; + extraGroups = [ ]; + createHome = false; + + homeManager = { + enable = false; + module = null; + }; + + ssh.inbound.enable = true; + }; }; }; }