Installation

emanix installs on bare metal using disko + nixos-install, from a NixOS minimal ISO on a multi-boot USB. Two scripts do the work; you type one command each, plus passphrases.

1. Prerequisites

  • A USB with a NixOS minimal ISO
  • The flake repo cloned onto the USB
  • The new host's SSH host key beside the repo on the USB
  • Real values inserted into the encrypted secrets

The host key is the part people skip, and it is the part that matters: secrets are decrypted by the host's own key, so a machine that boots without its key installed cannot decrypt anything and the first activation fails.

2. 1. Prepare the USB

On an existing machine, once, before you go:

# Insert real credentials into the encrypted secret
cd ~/dotfiles
nix run github:ryantm/agenix -- -e secrets/<name>.age -i ~/.ssh/id_ed25519
git add secrets/<name>.age && git commit -m "secret: real values" && git push

# Stage repo + host key on the USB
V=/run/media/$USER/<usb>              # adjust to your mount
HOST=<host>                           # the host name defined in the flake
git clone ~/dotfiles "$V/dotfiles"
mkdir -p "$V/$HOST-keys"
cp ~/.ssh/${HOST}_host_ed25519{,.pub} "$V/$HOST-keys/"

3. 2. Install, from the live ISO

  1. Boot the USB → nixos-*-minimal-x86_64.iso
  2. Network: ethernet is automatic; otherwise iwctlstation wlan0 connect <ssid>
  3. Run the installer:
sudo bash /run/media/*/<usb>/dotfiles/installer/fresh-emanix-install

The installer:

  • Preflights — UEFI mode, network, a Secure Boot warning, and a host key fingerprint match
  • Asks you to type yes to wipe the target disk
  • Prompts for the disk-encryption passphrase
  • Injects the host key
  • Runs nixos-install
  • Prompts for the primary user's password
  • Offers to reboot

Remove the USB on reboot.

The fingerprint preflight is worth calling out: it fails before wiping anything if the key on the USB is not the key the secrets were encrypted for. That check exists because the alternative is discovering the mismatch after the disk is gone.

4. ⚠ Firmware: disable Secure Boot

Disable Secure Boot in the machine's firmware setup (on a ThinkPad: F1 at the logo → Security → Secure Boot → Disabled) and confirm UEFI mode.

The bootloader is unsigned. With Secure Boot enabled the system installs cleanly and then will not boot — which is a far more confusing failure than an install-time error, hence the preflight warning.

5. 3. First boot

  1. Disk passphrase → autologin → the compositor
  2. Run the one-time setup:
emanix-firstboot

This runs whatever your flake put in emanix.firstboot.text. Emanix supplies the command and shellchecks it at build time; it does not supply the steps, because joining a network and pairing file sync are decisions about your infrastructure, not the distribution's.

A typical script joins a mesh VPN, prints the file-sync device id to pair with a hub, clones the flake, and confirms secrets decrypted. If you set nothing, emanix-firstboot prints a message and exits.

If yours pairs file sync, remember that pairing is two-sided: the hub must also accept the new device and share the folders. Printing the id is half the handshake, not all of it.

6. 4. Ongoing

cd ~/dotfiles && sudo nixos-rebuild switch --flake .#<host>

See Build and Deploy.

7. Manual fallback

If the installer fails partway, the steps it automates are ordinary:

# Partition + format, from the declarative disk layout
sudo nix --experimental-features "nix-command flakes" \
  run github:nix-community/disko -- --mode disko /tmp/disko-config.nix

# Mount
sudo mkdir -p /mnt
sudo mount /dev/<root-partition> /mnt
sudo mkdir -p /mnt/boot
sudo mount /dev/<efi-partition> /mnt/boot

# Copy the flake and install
sudo cp -r /run/media/*/<usb>/dotfiles /mnt/dotfiles
sudo nixos-install --flake /mnt/dotfiles/#<host>

sudo reboot

8. WSL hosts

A wsl host is not installed this way at all — there is no disk layout, no bootloader and no host hardware. The WSL module supplies boot and mounts; you import a distribution image and apply the flake.

One platform quirk is worth knowing: the system must not set its own hostname at activation, because that breaks the WSL user-session bootstrap. The host composer sets the hostname as a default precisely so a WSL host can force it empty.