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.
This commit is contained in:
2026-06-04 16:51:30 -03:00
parent 403cf2fde5
commit 2a911b057b
11 changed files with 585 additions and 44 deletions
+149
View File
@@ -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 <host> # 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 "$@"
'';
}
);
};
};
};
}
+6 -1
View File
@@ -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 = {
@@ -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 = {
+3
View File
@@ -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 = {
+33
View File
@@ -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;
}
+16 -4
View File
@@ -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
'')
+4 -20
View File
@@ -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;
};
+49
View File
@@ -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"
];
}
];
}
];
};
};
}
+14
View File
@@ -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;
};
};
};
}