Installation

Emanix installs from its own live ISO. You build the ISO once on a machine you already have, boot the target from it, and run one command.

Two scripts do the work:

  • fresh-emanix-install ships with the distribution and is baked into the ISO. It runs on the booted target and does the install.
  • bin/emanix-iso is the one script your consuming flake supplies — the repo that declares your hosts. It builds the ISO, and optionally writes it to a USB stick.

Everything below assumes the host is already declared in your flake. If it is not, start with Adding a Host and come back.

1. 1. Build the installer ISO

On an existing machine, from your flake repo:

bin/emanix-iso                 # build; prints the ISO path
bin/emanix-iso --no-keys       # build with NO host keys (rescue / debug ISO)
bin/emanix-iso /dev/sdX        # build and write to a device (destructive)

The ISO stages your whole flake at /etc/emanix/flake and, unless --no-keys, every staged host key at /etc/emanix/keys.

An ISO built with keys IS your identity. It carries the private host keys that decrypt your secrets. Keep the .iso file and the stick exactly as carefully as you keep the keys themselves. Build with --no-keys for anything you intend to hand to someone else or leave in a drawer.

1.1. Why a keys-carrying build is impure

The private halves are gitignored, so staging them means reading the working tree — which pure evaluation forbids. bin/emanix-iso passes --impure for you, and the private keys are copied into /nix/store (world-readable) on the way into the image. Delete those store paths once the ISO is written; a reference script can do this for you, but then it must match every staged name (see the join-key note below).

A build whose keys directory holds only .pub halves fails at build time, on purpose. The alternative is an ISO that looks key-carrying, boots, and then dies at the installer's preflight on the target's console.

1.2. Staging a join key for your mesh VPN

The ISO can also carry one more credential, a preauthkey, that lets the freshly installed machine join your tailnet without anyone typing a long key at its console.

How to stage it. Put one file named tailscale-preauthkey in the directory that emanix.installer.keysDir names, beside the host keys. If the file exists at build time, the ISO carries it at /etc/emanix/tailscale-authkey, and the login banner lists that path. There is no option for it; the file name is the convention. It is staged only when keysDir is set, so a keyless build (--no-keys) carries none.

Why it travels with the host keys. It is the one credential a fresh machine cannot get for itself. Your coordination server is unreachable until the join succeeds, and the machine has no key with which to ask another machine to mint one. It is the same kind of thing as a host key — a credential that lets a new machine identify itself — so it rides in the same place.

What kind of key to use. A reusable key with a long expiry. A short-lived key expires before you use the ISO, and then it fails at the moment of need and looks as if the staged key was ignored. How you mint the key depends on your coordination server; the distribution does not prescribe one.

What reads it. Nothing in the distribution. The installer (fresh-emanix-install) does not read the file and does not copy it onto the installed disk. The join itself is your flake's job, in the script you give emanix.firstboot.text (see Options) or in your own tooling. A script that runs after the first reboot finds the file only if your tooling put it on the installed system. The distribution ships no tailnet join of its own.

One build check treats the file with care: it does not count as a host key. A keys directory that holds only the preauthkey and public halves still fails the build, because the ISO would carry no identity.

Warning: the preauthkey is a credential, and building it into an ISO writes it to the Nix store. Anyone who can read the .iso file, or the store path, can join a machine to your network until the key expires. The build copies the key file into its own store path, whose name ends in tailscale-preauthkey, in addition to the copy inside the emanix-keys directory path. Delete both after the build, and keep the .iso as carefully as the key. A cleanup that matches only the emanix-keys name leaves the key file behind. Your build script is the place to do this; the distribution ships none.

2. 2. Before you go

The ISO bakes in the repo and the keys, so there is no USB staging to get right. What still has to be true:

  • Real values are in the encrypted secrets. Placeholders install a system that activates and then fails to do anything useful with them. See Secrets.
  • The host's public key is committed at keys/<host>_host_ed25519.pub, and the private half is present in the working tree so the ISO can stage it.
  • Secrets are rekeyed for this host, so the encrypted-to-this-machine copies exist.

The public 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 cannot decrypt anything and the first activation fails.

3. 3. Install, from the ISO

  1. Boot the stick. Its volume label is emanix.
  2. Network: ethernet comes up on its own. For WiFi, the ISO ships NetworkManager with the iwd backend, so nmtui, nmcli and iwctl all work:

    nmtui                                    # nothing to remember
    nmcli device wifi connect <ssid> --ask
    

    iwctl works here and not on the machine you end up with: the installed host does not get the iwd backend, so nmtui and nmcli are the two that carry over, and neither needs the interface name. See Network.

  3. Install:

    sudo fresh-emanix-install <host>
    

The host name selects a host you declared in the flake. Give one the flake does not know — or none at all — and the installer switches to interactive mode, asking for the hostname, user, disk, encryption, filesystem and graphics driver. Two options are available:

sudo fresh-emanix-install <host> --check-only     # preflight, then exit
sudo fresh-emanix-install <host> --disk /dev/X    # override disk auto-detection

--check-only needs no root, touches nothing, and prints the checklist below. Run it first.

3.1. The preflight

Check What it means
repo The flake was found. /etc/emanix/flake on the ISO, or $EMANIX_REPO
keys A private host key for <host> was found, and its fingerprint matches the key your secrets are encrypted to
UEFI The machine booted in UEFI mode (/sys/firmware/efi exists)
network cache.nixos.org is reachable
disk A target disk was identified
Secure Boot Reported, not required — see below

Anything missing aborts before the disk is touched.

The fingerprint check is the one worth calling out. It compares the staged private key against keys/<host>_host_ed25519.pub — the actual recipient your secrets are encrypted to — and fails closed on a mismatch. That check exists because the alternative is discovering the wrong key after the disk is gone.

Disk auto-detection picks the largest non-removable, writable device with no mounted partitions, which skips the stick you booted from. The installer also cross-checks that against the device your disk layout declares and refuses to continue if they disagree — a disagreement usually means a VM, or the wrong pick.

3.2. What it then does

The stages are numbered, so a long step is visibly a step and not a hang.

  • Asks you to type yes to wipe the target — once. disko's own second confirmation is suppressed, because you have already answered it and it would block any unattended install
  • Partitions and formats with disko (destroy,format,mount), prompting for a disk-encryption passphrase if the layout declares LUKS. This prints a wall of + ... shell tracing; that is normal output, not errors
  • Installs a host key to /mnt/etc/ssh/ — the staged one for a known host, a freshly generated one for a new machine, which has no secrets to decrypt
  • Runs nixos-install
  • Prompts for the primary user's password, retrying rather than leaving a passwordless account behind
  • Offers to reboot

Remove the stick on reboot.

3.3. Expect the install to compile Emacs

The nixos-install step is the long one, and it is long for a reason worth understanding rather than waiting through.

Most of what a desktop host needs for its Emacs and compositor comes straight from cache.nixos.org, but some of it does not and is compiled on the machine you are installing: ewm-core (Rust, via Smithay), the Emacs itself on the base the host runs (the pwayl/Skia 31.1 fork under EWM, pgtk 31.1 otherwise — see Components), and the Emacs Lisp packages built against it. The exact counts change whenever the base or the package list does, so none is quoted here.

The Lisp is uncacheable for a structural reason. Every Emacs package is built against the host's own Emacs base, so even entirely ordinary packages — xref, project, eldoc — exist in the public cache only as builds against a different Emacs. Change the Emacs and all of them rebuild.

The output is small, so this costs CPU time rather than bandwidth and it scales with how many cores the machine has. Nothing is wrong while it runs.

A binary cache would remove the step entirely, and that is the intended fix. Until it exists, budget for it.

4. Installing a host the flake has never heard of

Everything above assumes the host is already declared. It does not have to be. Run the installer with no host name and it switches to interactive mode, which invents the host as it goes:

sudo fresh-emanix-install

It decides by asking the flake, not by guessing: if the name you give matches a nixosConfigurations attribute it takes the pre-staged path unchanged, and anything else — including no name at all — is treated as a new machine.

4.1. What it asks

Six questions, in this order:

Question Notes
Hostname Validated as RFC 1123 — lowercase letters, digits, hyphens
Username Your account on the new machine, same character rules
LUKS Whole-disk encryption, on by default
Filesystem btrfs (with subvolumes) or ext4
Swap A size like 8G, or 0 for none
Graphics amd, intel or none; lspci supplies the default

Network is not a question — it checks, and only involves you if there is no route, in which case it offers to open nmtui.

The graphics answer matters more than it looks. EWM launches from tty1 and loses the DRM-master race unless the driver is in the initrd, so a wrong answer here is a black screen on a machine whose owner has no other console. It is asked rather than assumed for exactly that reason.

4.2. What it generates

A complete flake in /etc/nixos, whose only machine-specific file is host.nix:

{
  hostName   = "testbox";
  device     = "/dev/vda";
  luks       = false;
  filesystem = "btrfs";
  swapSize   = "2G";
  gpu        = null;              # "amd" or "intel" on a graphical host
  hardwareModule = null;          # a nixos-hardware module, if you know yours
}

Everything else in that flake reads from those values. The disk layout comes from emanix.lib.mkDisk, so the disk parameters above are the whole description of the machine's disk; gpu is the only other thing a graphical host must set.

Before anything is erased the installer prints your answers back — hostname, username, encryption, filesystem, swap, graphics — immediately above the wipe confirmation. The preflight proves the machine is ready; that block is how you check the installer understood you.

4.3. After the reboot

You land in Emacs. It is the desktop here, not an application inside one.

Key Does
s-<return> A terminal
s-d Launch an application
s-1 … s-9 Switch frame slots
C-c ? Reopen the welcome buffer

A welcome buffer opens on the first start with those keys and a pointer back here. Dismiss it with q for now, or n to never see it again.

Do not run emanix-firstboot on a machine installed this way. That command is the consuming flake's seam — it runs whatever your flake put in emanix.firstboot.text, and a generated host has set nothing, so it prints a message about configuring it and exits. It is for a fleet, not for a machine the installer just invented.

4.4. Making the config yours

The installer leaves a working flake in /etc/nixos. That is enough to run and not enough to live in: root-owned, no git, nowhere to put a secret.

emanix-init

This copies it to ~/flake, initialises git, adds a keys/ directory whose .gitignore commits the public halves and never the private ones, records this machine's own public host key as a future secrets recipient, and runs nix flake check to prove the result still evaluates. It refuses rather than overwrites, and leaves /etc/nixos untouched, so a failed adoption costs nothing.

The welcome buffer offers this as [i] when it finds no config repo.

Rebuild from there afterwards:

sudo nixos-rebuild switch --flake ~/flake

5. ⚠ 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 — a far more confusing failure than an install-time error, which is why the installer warns rather than assumes.

6. Installing over SSH

The ISO runs sshd. The nixos user has an empty password, so nothing can log in until you set one:

  • add live.nixos.passwd=<pw> to the kernel command line at boot, or
  • run sudo passwd nixos at the target's console

then ssh nixos@<ip> and drive the install from your own machine. The ISO's host keys are generated fresh at every boot — it runs on tmpfs, so they are ephemeral by construction.

7. First boot

  1. Disk passphrase, if the layout has LUKS
  2. The compositor starts (autologin is opt-in per host; without it you get a login prompt and EWM starts on your first tty1 login)
  3. Run the one-time setup:

    emanix-firstboot
    

This runs whatever your flake put in emanix.firstboot.text. Emanix supplies the command and shellchecks the body at build time — so a broken first-boot script fails your build rather than failing at the one moment nobody is watching a screen they can debug from. 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, and confirms secrets decrypted. If you set nothing, emanix-firstboot prints a message saying so 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.

8. Ongoing

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

See Build and Deploy.

9. Manual fallback

If the installer fails partway, everything it automates is an ordinary command. Note that disko reads the layout from the flake — there is no separate config file to write:

# Partition, format and mount, from the declarative layout
sudo disko --mode destroy,format,mount --flake /etc/emanix/flake#<host>

# Install the host key so agenix can decrypt on first boot
sudo install -d -m 755 /mnt/etc/ssh
sudo install -m 600 /etc/emanix/keys/<host>_host_ed25519     /mnt/etc/ssh/ssh_host_ed25519_key
sudo install -m 644 /etc/emanix/keys/<host>_host_ed25519.pub /mnt/etc/ssh/ssh_host_ed25519_key.pub

# Install, then set the user's password
sudo nixos-install --flake /etc/emanix/flake#<host> --no-root-password
sudo nixos-enter --root /mnt -c 'passwd <username>'

sudo reboot

10. 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 (it comes from your own flake's nixos-wsl input, not from Emanix); 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.