refactor: host profiles; give ruby, kana and aquamarine the TUI home (#512)

- Add the agent profile (modules/nixos/agent.nix: rootless Podman, linger,
  nix-ld) with home/linux/tui.nix as its home role; ruby, kana and
  aquamarine use it, so hosts no longer import from hosts/idols-ruby.
- Gate nushell's ~/.secrets source behind modules.shell.secrets.enable,
  off on agent hosts.
- Persist the TUI home state on aquamarine's tmpfs root.
- Document host profiles in hosts/README.md; add tests/agent-profile and
  drop the x86_64 colmena-parity test, which needs mysecrets.

Assisted-by: Claude Code (claude-opus-5-5)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-11-03 04:57:06 -06:00
1 parent eb6f6c34f0
commit 9a307b313d
31 files changed
+301 -147

No files matched your search

+4 -4
View File
@@ -47,10 +47,10 @@ Some tests iterate over every configuration, so a new host is covered automatica
them. Others name specific hosts — or a name prefix, like `k3s-api-endpoint`, which covers every
`k3s-test-1-*` configuration — and only cover a new host if you add it.
| Kind | Tests |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Every configuration | `hostname`, `kernel`, `nix-system-features`, `security-apparmor`, `security-container-groups`, `security-exporters`, `security-firewall`, `security-k3s-kubeconfig`, `security-k3s-pod-firewall`, `security-kernel`, `security-ssh-x11` |
| Named hosts only | `btrbk`, `cpu-power`, `home-manager`, `home-manager-xdg`, `computer-use-headless`, `k3s-api-endpoint`, `k3s-node-home-manager`, `idols-ai-gpu`, `idols-ai-net`, `idols-ai-voice-input`, `idols-aquamarine`, `security-postgres-auth`, `youko-metrics`, `ups-metrics`, `shoukei-logind`, `mihomo-dns`, `colmena-parity`, `microvm-guest-parity` |
| Kind | Tests |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Every configuration | `hostname`, `kernel`, `nix-system-features`, `security-apparmor`, `security-container-groups`, `security-exporters`, `security-firewall`, `security-k3s-kubeconfig`, `security-k3s-pod-firewall`, `security-kernel`, `security-ssh-x11` |
| Named hosts only | `btrbk`, `cpu-power`, `home-manager`, `home-manager-xdg`, `computer-use-headless`, `agent-profile`, `k3s-api-endpoint`, `k3s-node-home-manager`, `idols-ai-gpu`, `idols-ai-net`, `idols-ai-voice-input`, `idols-aquamarine`, `security-postgres-auth`, `youko-metrics`, `ups-metrics`, `shoukei-logind`, `mihomo-dns`, `colmena-parity`, `microvm-guest-parity` |
`hostname` expects each configuration's `networking.hostName` to equal its name. Niri desktop
configurations are the exception (`ai-niri` → `ai`, `shoukei-niri` → `shoukei`) and are listed in
@@ -3,4 +3,7 @@
inputs,
}:
# Colmena must deploy the system that nixosConfigurations evaluates and builds.
# Every node comes out of the same rawColmenaHive code in outputs/default.nix, so
# akane covers the x86_64 nodes too; those all read the private mysecrets input,
# which CI cannot fetch.
lib.genAttrs [ "akane" ] (name: inputs.self.colmenaHive.toplevel.${name}.drvPath)
@@ -22,8 +22,8 @@ let
modules = {
nixos-modules =
(map mylib.relativeToRoot [
# common
"modules/nixos/server/server.nix"
# profile: agent (hosts/README.md#host-profiles)
"modules/nixos/agent.nix"
# host specific
"hosts/idols-${name}"
])
@@ -32,6 +32,10 @@ let
# until its new host key is a recipient in nix-secrets. Enabling one is
# the last step in hosts/idols-aquamarine/README.md.
];
home-modules = map mylib.relativeToRoot [
# host specific
"home/hosts/linux/idols-${name}.nix"
];
};
systemArgs = modules // args;
+2 -3
View File
@@ -24,10 +24,9 @@ let
(map mylib.relativeToRoot [
# common
"secrets/nixos.nix"
"modules/nixos/server/server.nix"
# profile: agent (hosts/README.md#host-profiles)
"modules/nixos/agent.nix"
"modules/nixos/server/qemu-guest-hardware-configuration.nix"
# headless computer-use environment (sway + desktop automation)
"modules/nixos/desktop/computer-use.nix"
# host specific
"hosts/idols-${name}"
])
+2 -3
View File
@@ -24,10 +24,9 @@ let
(map mylib.relativeToRoot [
# common
"secrets/nixos.nix"
"modules/nixos/server/server.nix"
# profile: agent (hosts/README.md#host-profiles)
"modules/nixos/agent.nix"
"modules/nixos/server/qemu-guest-hardware-configuration.nix"
# headless computer-use environment (sway + desktop automation)
"modules/nixos/desktop/computer-use.nix"
# host specific
"hosts/idols-${name}"
])
@@ -0,0 +1,24 @@
{ ... }:
let
common = {
agentsFromHome = true;
devToolsFromHome = true;
nushellSecrets = false;
nixLd = true;
rootlessPodman = true;
docker = false;
linger = true;
agentsInSystem = false;
};
in
{
aquamarine = common // {
computerUse = false;
};
ruby = common // {
computerUse = true;
};
kana = common // {
computerUse = true;
};
}
@@ -0,0 +1,41 @@
# The `agent` profile (hosts/README.md#host-profiles): every agent host gets
# the full TUI home role, and the system side only carries what needs root.
{
myvars,
lib,
outputs,
}:
let
username = myvars.username;
hosts = [
"aquamarine"
"ruby"
"kana"
];
hasPackage = name: lib.any (package: (package.pname or "") == name);
in
lib.genAttrs hosts (
name:
let
system = outputs.nixosConfigurations.${name}.config;
home = system.home-manager.users.${username};
in
{
# home role: home/linux/tui.nix
agentsFromHome = hasPackage "codex" home.home.packages;
devToolsFromHome = hasPackage "kubectl" home.home.packages;
# ~/.secrets is synced to the desktops only; sourcing it would break nushell.
nushellSecrets = home.modules.shell.secrets.enable;
# system module: modules/nixos/agent.nix
nixLd = system.programs.nix-ld.enable;
rootlessPodman = system.virtualisation.podman.enable;
docker = system.virtualisation.docker.enable;
linger = system.users.users.${username}.linger;
# User tools stay out of the system profile.
agentsInSystem = hasPackage "codex" system.environment.systemPackages;
# host switch
computerUse = system.modules.desktop.computerUse.enable;
}
)
@@ -1,7 +0,0 @@
{
lib,
outputs,
}:
lib.genAttrs [ "kana" ] (
name: outputs.nixosConfigurations.${name}.config.system.build.toplevel.drvPath
)
@@ -1,6 +0,0 @@
{
lib,
inputs,
}:
# Colmena must deploy the system that nixosConfigurations evaluates and builds.
lib.genAttrs [ "kana" ] (name: inputs.self.colmenaHive.toplevel.${name}.drvPath)
@@ -21,7 +21,7 @@
raplPermissionsExposed = true;
collectorFlagsUnique = true;
inferenceUserCanRender = true;
noHomeManager = true;
homeManagerStatePersistent = true;
pythonInstalled = true;
staticAddressMatchesWiredInterface = true;
rootIsTmpfs = true;
@@ -92,8 +92,18 @@ in
== builtins.length (lib.unique cfg.services.prometheus.exporters.node.enabledCollectors);
inferenceUserCanRender = builtins.elem "render" cfg.users.users.${myvars.username}.extraGroups;
# Headless: no desktop session, no Home Manager.
noHomeManager = !cfg ? home-manager.users;
# Home Manager's own state has to survive the tmpfs root; the agent profile
# itself is covered by ../agent-profile.
homeManagerStatePersistent =
let
dirs = cfg.preservation.preserveAt."/persistent".users.${myvars.username}.directories;
in
builtins.all (path: builtins.any (d: d.directory == "/home/${myvars.username}/${path}") dirs) [
".local/state/home-manager"
".local/state/nix/profiles"
".codex"
".config/opencode"
];
pythonInstalled = builtins.any (
package: lib.hasPrefix "python3-" (package.name or "")
) cfg.environment.systemPackages;