Adding a New Host#

This guide walks through adding a new NixOS host from scratch — from creating the config files to a working, post-install system with secrets access.

Prerequisites#

Before starting, make sure you have:

  • The devshell active (nix develop or direnv): gives you sops, gum, just, etc.
  • Your personal age key at ~/.config/sops/age/keys.txt (run just sops-init if missing)
  • Your SSH public key committed under modules/users/<you>/keys/
  • Write access to secrets.yaml (verify with just preflight)

Quick environment check:

just preflight

Step 1 — Scaffold the host directory#

Use the scaffold recipe to create the host directory and see the hosts/default.nix snippet you’ll need to add:

just new-host <name>                         # x86_64 NixOS (default)
just new-host <name> aarch64 nixos           # aarch64 NixOS (e.g. RPi)
just new-host <name> aarch64 darwin          # Apple Silicon Mac

This creates hosts/<arch>-<class>/<name>/default.nix with a minimal template and prints the entry to paste into hosts/default.nix.

If you prefer to do it manually, create the directory:

hosts/
└── x86_64-nixos/
    └── <name>/
        └── default.nix

Step 2 — Write the host config#

Edit hosts/<arch>-<class>/<name>/default.nix. A minimal NixOS host looks like:

{ inputs, lib, config, pkgs, ... }: {
  imports = [
    # Hardware quirks (optional — pick from nixos-hardware if applicable)
    # inputs.hardware.nixosModules.common-cpu-intel

    # Disk layout (required — define a disko config or import one)
    # ./disko.nix

    # Users on this host
    ../../../modules/users/tsunami
  ];

  networking.hostName = "<name>";

  # Disk / filesystem config here (see hosts/x86_64-nixos/mokou/ for a full example)

  tsunaminoai = {
    tailscale.enable = true;
    borg = {
      enable = true;
      repo = "<borgwarehouse-repo-id>";   # see Step 4
    };
    nix.builds.enable = true;            # remove if this host isn't a builder
  };

  system.stateVersion = "25.11";
}

See hosts/x86_64-nixos/mokou/default.nix for a full desktop example, or hosts/x86_64-nixos/ereshkigal/ for a server example.


Step 3 — Register in hosts/default.nix#

Add an entry inside the hosts = { ... } attrset in hosts/default.nix. The just new-host command prints the snippet for you; paste it in alphabetical order (the file uses # keep-sorted markers):

<name> = {
  path = ./<arch>-<class>/<name>;
  arch = "<arch>";        # x86_64 (default) or aarch64
  class = "<class>";      # nixos (default), darwin, rpi, wsl, catalina
  system = "<arch>-linux";
  tags = [ ];             # see Tags section below
};

Available tags and what they activate:

Tag Effect
desktop KDE Plasma, Wayland, Pipewire, GUI apps
server Headless hardening, server-oriented defaults
laptop Battery management, lid/suspend handling
builder Registers host as a Nix remote builder for other hosts
cache Hosts a binary cache; other hosts automatically use it
media-acquisition *arr stack, Tdarr, media pipeline
rpi Raspberry Pi 4 cross-compilation support

Tags are resolved in hosts/default.nix via discoveredBuilderHosts / discoveredCacheHosts special args — adding "builder" or "cache" automatically wires the host into the build and caching mesh.


Step 4 — Verify the flake evaluates#

nix flake show 2>&1 | grep <name>

Fix any evaluation errors before proceeding.


Step 5 — Generate and register a Borg backup key (if using Borg)#

If the host will use Borg backups (tsunaminoai.borg.enable = true):

just borg-generate-secrets <name>

This generates an ed25519 keypair and passphrase, writes them to secrets.yaml, and prints the public key. Register the public key in your BorgWarehouse instance to get the repo ID, then put that ID in the host config (tsunaminoai.borg.repo).


Step 6 — First boot: install the system#

Option A — Interactive installer ISO (local machine)#

  1. Build and write the installer ISO:
nix build .#nixosConfigurations.bootable-iso.config.system.build.isoImage
# Flash result/iso/*.iso to a USB drive
  1. Boot the target machine from the USB. The flake is included in the ISO at /etc/nixos-flake. Run the interactive installer:
install.sh

It will prompt for host selection, confirm disk destruction, run disko, and call nixos-install.

Option B — nixos-anywhere (remote, over SSH)#

From a machine in the devshell:

nix run github:nix-community/nixos-anywhere -- \
  --flake .#<name> \
  root@<target-ip>

Requires the target to have an SSH server running (minimal NixOS live ISO or existing Linux install with root SSH access).


Step 7 — Register the host’s SOPS age key#

After the first boot, the host has an SSH host key at /etc/ssh/ssh_host_ed25519_key. sops-nix derives the host’s age key from this.

On the new host, convert the host key to age format:

nix-shell -p ssh-to-age --run \
  'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
# → age1...

Back in the flake repo, add the age key to .sops.yaml:

# In the hosts: section:
- &<name> age1<output-from-above>

# In the creation_rules → secrets.yaml key_groups → age: section:
- *<name>

Then update the encrypted file so it’s re-keyed for the new host:

sops updatekeys secrets.yaml
git add .sops.yaml secrets.yaml
git commit -m "feat: add sops age key for <name>"

Save the host key in the repo for deploy-rs:

just get-host-key <name>
git add modules/nixos/security/pubkeys/<name>.pub
git commit -m "chore: save host key for <name>"

Step 8 — Second nixos-install (with secrets)#

Push the commits from Step 7, then on the target machine re-run the install so sops-nix can decrypt secrets during activation:

nixos-install --flake /etc/nixos-flake#<name>
reboot

Or deploy remotely after the system is up:

deploy .#<name>

Step 9 — Post-install checklist#

  • tailscale up --auth-key <key> — join the Tailscale mesh
  • Verify sops secrets decrypted: ls /run/secrets/
  • Verify Borg backup connectivity: borgmatic check
  • Verify deploy-rs: deploy .#<name> --dry-activate
  • Add host to tsunaminoai.deploy.monitoredHosts in the deploy node (e.g. mokou) if you want it in the Gatus dashboard

Adding a collaborator user#

To give a new person access to the flake:

  1. Create modules/users/<username>/ (copy modules/users/bcraton/ as a minimal template)
  2. Add their SSH public key to modules/users/<username>/keys/
  3. Generate their personal age key on their machine (just sops-init) and add it to .sops.yaml under the users: section and to the secrets.yaml creation rules
  4. Run sops updatekeys secrets.yaml and push
  5. Import ../../../modules/users/<username> in any host default.nix where they need access