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 developor direnv): gives yousops,gum,just, etc. - Your personal age key at
~/.config/sops/age/keys.txt(runjust sops-initif missing) - Your SSH public key committed under
modules/users/<you>/keys/ - Write access to
secrets.yaml(verify withjust 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)#
- Build and write the installer ISO:
nix build .#nixosConfigurations.bootable-iso.config.system.build.isoImage
# Flash result/iso/*.iso to a USB drive
- 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.monitoredHostsin 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:
- Create
modules/users/<username>/(copymodules/users/bcraton/as a minimal template) - Add their SSH public key to
modules/users/<username>/keys/ - Generate their personal age key on their machine (
just sops-init) and add it to.sops.yamlunder theusers:section and to thesecrets.yamlcreation rules - Run
sops updatekeys secrets.yamland push - Import
../../../modules/users/<username>in any hostdefault.nixwhere they need access