Setup a New Machine: Pre-Deployment Checklist
Duration: 15–30 minutes (all on the admin host, target machine not needed yet)
This phase prepares everything needed to deploy a new NixOS machine. All steps happen on the admin host (your development machine with Nix installed). The target machine can remain powered off.
Overview: The Three-Phase Process
- Pre-Deployment (this document): Prepare configuration and deployment artifacts on admin host
- Deployment: Boot target machine to live environment and run
nixos-anywhere - Post-Deployment: Minimal verification and optional additional setup
Terminology
- Admin host: The machine you are using to orchestrate the setup (your laptop, desktop, or CI system with Nix installed)
- Target host: The new NixOS machine being deployed (may have no OS yet)
- Live environment: NixOS minimal ISO running in RAM on the target machine during deployment
Prerequisites
- Nix package manager installed on admin host (required to use this repository)
- Repository cloned and up to date
- You are in the devshell:
nix develop(ordirenv allowif using direnv) - SSH key pair on admin host for connecting to the repository (for git operations)
- For cloud VPS targets: IP address or hostname already known from provider
- For physical targets: Network connectivity and way to boot the NixOS minimal image
Step 1: Create Target's NixOS Configuration
Choose a new machine hostname (e.g., hypervisor, gaming, staging-web) and create its configuration directory.
To make the following steps easier to follow, I recommend setting the HOSTNAME variable in your shell so you don't have to change it in all commands.
export HOSTNAME=your_hostname
1.1 Copy the Directory Structure
mkdir -p nix/hosts/${HOSTNAME}/users nix/hosts/${HOSTNAME}/data
Replace ${HOSTNAME} with your chosen name throughout these steps.
1.2 Create configuration.nix
Copy from a reference machine and customize:
cp nix/hosts/blackfog/configuration.nix nix/hosts/${HOSTNAME}/configuration.nix
Then edit to update:
- Hostname: Change networking.hostName = "blackfog" to your target hostname
- System name: Change system.name = "blackfog" to your target hostname
- Imports: Remove or adjust imports based on target machine purpose (e.g., remove gnome module if headless server)
- Disko config: If using disk encryption (LUKS), ensure disk IDs match target hardware
- Tailscale and other services: Enable/disable as appropriate
1.3 Create User Configuration
For desktop/workstation machines (e.g., gaming, blackfog):
cp nix/hosts/blackfog/users/snyssen.nix nix/hosts/${HOSTNAME}/users/snyssen.nix
For server machines (e.g., hypervisor, ingress):
mkdir -p nix/hosts/${HOSTNAME}/users
touch nix/hosts/${HOSTNAME}/users/.gitkeep
# No user config needed; root will be used via Tailscale or SSH
1.4 Create Placeholder hardware-configuration.nix
cp nix/hosts/blackfog/hardware-configuration.nix nix/hosts/${HOSTNAME}/hardware-configuration.nix
Note: This is a placeholder. The actual hardware configuration will be generated by nixos-anywhere during deployment and will overwrite this file.
1.5 Create secrets.yaml
Create a secrets file for target-specific secrets (e.g., Tailscale auth keys):
touch nix/hosts/${HOSTNAME}/data/secrets.yaml
Edit it with initial placeholder content (will be encrypted later):
# nix/hosts/${HOSTNAME}/data/secrets.yaml
tailscale:
authKey: "PLACEHOLDER_WILL_BE_REPLACED"
Step 2: Generate SSH Keypairs for Target
The target machine needs SSH keypairs for: - Git operations (cloning, pushing commits) - Commit signing - Connecting to other machines via SSH - Deriving age keys for SOPS secret decryption
We generate these on the admin host and stage them for deployment, then delete them from the admin host immediately after successful deployment.
2.1 Determine Key Type and Location
For server/headless machines (root-based):
- Key placement: /etc/ssh/ssh_host_ed25519_key (and .pub)
- Owner: root
For desktop/workstation machines (user-based):
- Key placement: /home/snyssen/.ssh/id_ed25519 (and .pub)
- Owner: snyssen user
2.2 Generate SSH Keypair
Create a temporary directory to stage deployment files:
mkdir -p /tmp/${HOSTNAME}-deploy/etc/ssh
Or for desktop:
mkdir -p /tmp/${HOSTNAME}-deploy/home/snyssen/.ssh
Generate the keypair with no passphrase:
ssh-keygen -t ed25519 -f /tmp/${HOSTNAME}-deploy/etc/ssh/ssh_host_ed25519_key -N "" -C "root@${HOSTNAME}"
Or for desktop:
ssh-keygen -t ed25519 -f /tmp/${HOSTNAME}-deploy/home/snyssen/.ssh/id_ed25519 -N "" -C "snyssen@${HOSTNAME}"
Verify the keypair was created:
ls -la /tmp/${HOSTNAME}-deploy/etc/ssh/
# or
ls -la /tmp/${HOSTNAME}-deploy/home/snyssen/.ssh/
Step 3: Derive and Authorize Age Keys
The age key (used for SOPS secret decryption) is derived from the SSH private key. At deployment time, sops-nix on the target will automatically derive the age key from the SSH key you staged. During pre-deployment, we only need the age public key to authorize the target in .sops.yaml.
3.1 Derive the Age Public Key
We'll generate a temporary age private key from the SSH key, extract the public key, then delete the temporary file (it's not needed anywhere—the age key is derived from the SSH key at runtime).
Set your hostname variable:
HOSTNAME=hypervisor # or your target hostname
TEMP_AGE_KEY="/tmp/${HOSTNAME}-age-privkey-temp"
Generate the temporary age private key from your staged SSH key:
just sops-gen-privkey /tmp/${HOSTNAME}-deploy/etc/ssh/ssh_host_ed25519_key $TEMP_AGE_KEY
Or for desktop machines:
just sops-gen-privkey /tmp/${HOSTNAME}-deploy/home/snyssen/.ssh/id_ed25519 $TEMP_AGE_KEY
Extract the age public key:
just sops-get-pubkey $TEMP_AGE_KEY
This prints the age public key in format: age1xxxxxxxxxxxxxx...
Delete the temporary age private key (no longer needed):
rm "$TEMP_AGE_KEY"
Why we delete it: The age private key doesn't need to be stored. At runtime, the target machine will derive the age key from the SSH private key you staged, so it can decrypt its own secrets. The temporary age key was only needed to extract the public key.
Copy the printed public key (age1xxxxxxxxxxxxxx...); you'll need it in the next step.
3.2 Update .sops.yaml
Edit .sops.yaml in the repository root and add the new machine's age key to the appropriate creation_rules section:
keys: &all_keys
- &snyssen_gaming age1qxzfz6w99ptdyen3mwp3wr93yd8690m20s8p4daqn0qqczujhghsua5x28
- &snyssen_sninful age1n9kh0lcwpcth7ex4n9lc2h5enl45pmmy2wqzhznwulgq9r9elf4qp3vr2k
- &snyssen_purplehaze age1299cj79spmwd93hjz80v5s743a0vr6cjjkm8wggmc4z0x54e69fs9a0hqs
- &snyssen_blackfog age104pa77q73yt8hht4kv05gpk7hyayefy697lfe63dl0ua6dsej54s3mmrgj
- &root_ingress age17gnf6sp839a0wlhd998vn0wv5rrcwle34pky8mfn5d8dymm99vtsm9xygx
- &root_hypervisor age1psnzzfwejkyx4aduux42lt6a6kghcg3sex5qwuqcuucmdx3hddwsex0w6h
- &root_gaming age1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # NEW
creation_rules:
# ... existing rules ...
- path_regex: nix/hosts/gaming/data/[^/]+\.(yaml|json|env|ini)$
key_groups:
- pgp:
age:
- *root_gaming # NEW
Define the key anchor at the top with other keys, then reference it in the appropriate creation_rules entry for your target machine.
3.3 Re-encrypt Secrets Files
Now update the secret files to include the new machine's age key:
just sops-update-keys nix/hosts/${HOSTNAME}/data/secrets.yaml
If the target needs access to global secrets:
just sops-update-keys nix/data/secrets.yaml
3.4 Add Secrets Values
Edit the target's secrets file and add actual secret values:
just sops-update nix/hosts/${HOSTNAME}/data/secrets.yaml
Your editor will open with decrypted content. Add values like Tailscale auth keys:
tailscale:
authKey: "tskey-auth-Cxxxxxx..."
Save and exit; SOPS will re-encrypt the file automatically.
Step 4: Prepare Deployment Credentials
4.1 Generate Temporary Root Password
Generate a temporary password that will be used only during the live environment. You'll enter this password once when nixos-anywhere connects via SSH:
read -s > /tmp/${HOSTNAME}-root-password.txt
Important: This password exists only in the live environment and is discarded after reboot. You won't need it again.
4.2 Prepare LUKS Encryption (if applicable)
Most LUKS configurations use a dual-unlock approach:
- Primary: Binary keyfile on USB (convenient, requires USB present during installation)
- Secondary: Password/passphrase (backup, works without USB if needed later)
4.2.1 Create Binary Keyfile on USB (Before Deployment)
If your disko configuration uses a USB keyfile (check for keyFile and usbKeysIds in the config):
- Create the keyfile on your USB device beforehand using the steps in Full Disk Encryption - Create The Keyfile
- Name it something identifiable (e.g.,
hypervisororhypervisor.key) -
Remember the USB device's UUID (you'll need it in Deployment Part A)
-
Have the USB key ready for the deployment. You'll physically insert it into the target machine during the live environment setup (see Deployment Part A, step A.7).
4.2.2 Prepare Backup Password
Generate a strong backup password for cases where the USB key is unavailable or lost:
read -s > /tmp/${HOSTNAME}-luks-password.txt
Note: This password is only for fallback. Primary unlock will use the USB keyfile.
Step 5: Validate Configuration
5.1 Build the Flake
Test that your NixOS configuration builds correctly:
nh os build -H ${HOSTNAME}
This will build the entire closure. If there are errors in your Nix code, they'll appear here before deployment.
5.2 Verify Secrets
Ensure secret files are properly encrypted:
cat nix/hosts/${HOSTNAME}/data/secrets.yaml | head -20
# Should show encrypted content with "ENC[AES256_GCM..." entries
Step 6: Summary and Next Steps
At this point you should have:
✅ Complete NixOS configuration for target
✅ SSH keypairs generated in /tmp/${HOSTNAME}-deploy/
✅ Age keys derived and authorized in .sops.yaml
✅ Secrets encrypted with target's age key
✅ Flake builds successfully
✅ Temporary credentials prepared (root password, LUKS password if needed)
Next step: Proceed to Setup New Machine - Deployment
Before you begin deployment, ensure you know: - Target IP address or hostname (from DHCP or cloud provider) - Target hardware (disk IDs if using disko) - Network connectivity (can admin host reach target over SSH during live environment?)