LXC hosts (device busy fix): modules/platforms/lxc.nix now imports nixpkgs' own virtualisation/proxmox-lxc.nix, giving every lxc-* host a real config.system.build.tarball output — a directly `pct restore`-able Proxmox container image. This is the actual bug fix behind the "cannot remove real root directory: device busy or in use" error: lxc-* targets were only reachable through nixos-install, which bind-mounts / onto /mnt for containers (no raw disk to partition) and then correctly refuses to modify the filesystem it's currently running on. auto-install.sh's menu now excludes lxc-* targets entirely (they deploy via nix build + pct restore instead, see docs/auto-installer.md) — and, on the same reasoning, also excludes `installer`/`proxmox-lxc`, which are the installer image's own flake targets, not deployable hosts. manageHostName = true keeps host.nix's declared hostnames (upstream's default would let Proxmox's ambient container config win instead); privileged = false matches how these containers are actually created. Eval warnings, now zero across all 19 nixosConfigurations + 4 packages: - Multiple password options (root/nixos in the installer): nixpkgs' own installer profile sets initialHashedPassword = "" for passwordless login, conflicting with our explicit hashedPassword. Force-nulled the upstream option rather than adopting passwordless login, since this image now also boots over LAN PXE with PasswordAuthentication enabled. - boot.zfs.forceImportRoot default value: set explicitly to false (matching the two places that already did) in modules/common/configuration.nix and modules/installer/common.nix, covering every host and the installer alike. - Deprecated pkgs.system in modules/build-types/gui.nix: switched to pkgs.stdenv.hostPlatform.system. All confirmed non-behavioral where it matters: unrelated hosts' drvPaths are byte-identical to their pre-existing baselines throughout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
8.8 KiB
Auto-installer
This flake builds a self-contained NixOS installer environment that can
install any host exposed by its own nixosConfigurations. It was migrated
from a formerly-separate nix-auto-installer repo — everything it did now
lives here.
The installer provides a small NixOS install environment (ISO/netboot/Proxmox
LXC) with SSH access, Git support, and an interactive installation script.
Logging in as any user (root or nixos) runs /etc/auto-install.sh,
discovers available hosts from this same flake, lets the operator choose a
target, applies that host's Disko storage configuration, installs NixOS, and
reboots.
This applies to every nixosConfigurations target except lxc-* hosts —
see "LXC hosts" immediately below for why those are different.
LXC hosts
lxc-* targets (lxc-minimal, lxc-nix-cache, lxc-server, lxc-docker,
lxc-gui, lxc-pxe-boot) are not installed via auto-install.sh — the
interactive menu deliberately excludes them. Don't try to select one there;
nixos-install would bind-mount / onto /mnt (LXC containers have no raw
disk to partition) and then refuse to touch the filesystem it's currently
running on — it's designed to protect exactly this case, so it just fails.
modules/platforms/lxc.nix imports nixpkgs' own
virtualisation/proxmox-lxc.nix module, which gives every lxc-* host a
config.system.build.tarball output — a complete, directly Proxmox-importable
container image, no install step at all:
nix build .#nixosConfigurations.lxc-minimal.config.system.build.tarball
Then, on the Proxmox host, pct restore (or the GUI's "Create CT" → upload
template flow) that tarball directly as a new container. First boot runs
boot.postBootCommands (registers the Nix store DB and system profile) —
there's no separate activation step to run yourself.
Host keys still need pre-seeding the same way as any other host (see "Host
keys" below) — the sops-nix activation-vs-first-boot race is identical
regardless of how the image reaches the machine. NIXOS_HOST_KEYS_DIR=... nix build ... --impure bakes the matching key into the tarball the same way
it does for the ISO/PXE installer images.
Layout
modules/installer/common.nix— shared by every installer target: SSH access, users, the generated/etc/auto-install.shscript, and theprograms.bash.loginShellInithook that runs it on login.modules/installer/iso.nix— ISO/netboot-specific: imports the stockinstallation-cd-minimal.nixmodule pluscommon.nix. Also used, paired withnetboot-minimal.nix, to build the PXE netboot variant (seedocs/pxe-boot.md).modules/installer/proxmox-lxc.nix— Proxmox LXC-specific: disables bootloader options (containers don't need one) pluscommon.nix.modules/installer/host-keys.nix— optionally bakes pre-generated SSH host keys into the image; see "Host keys" below.scripts/prepare-host-key.sh— admin-workstation pre-flight tool, see "Creating a New Machine" below.
Flake outputs:
nixosConfigurations.installer # ISO/netboot installer image
nixosConfigurations.proxmox-lxc # Proxmox LXC-based installer image
packages.x86_64-linux.iso # installer ISO/netboot image
packages.x86_64-linux.lxc # Proxmox LXC installer tarball
packages.x86_64-linux.pxe # netboot-ipxe + netboot-initrd + netboot-kernel, bundled
packages.x86_64-linux.all # all three of the above, bundled
nix build .#iso
nix build .#lxc
nix build .#pxe
nix build .#all
The pxe variant is also built automatically as part of the pxe-boot host
itself (modules/pxe-boot/stage-installer-artifacts.nix) and served over
iPXE — see docs/pxe-boot.md.
Host keys
sops-nix derives each host's decryption key from its own
/etc/ssh/ssh_host_ed25519_key, generated at activation time — before
systemd would otherwise generate one on first boot. Without pre-seeding this
key, secrets (including the root/nixos login password) fail to decrypt on a
genuinely fresh install.
Generated host keys live in host-keys/ at the repo root (ssh_host_ed25519_key
.pubpairs per hostname). This directory is gitignored on purpose — private key material must never be committed — which also means flakes can't see it through a normal relative path.modules/installer/host-keys.nixreads it throughbuiltins.getEnv, which Nix silently returns as an empty string under normal (non---impure) evaluation, so the module is a no-op — safe by default, including in CI — unless explicitly opted into:
NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build .#iso --impure
When built this way, every key currently in host-keys/ is baked into the
image at /etc/host-keys/<hostname>_ssh_host_ed25519_key(.pub), and
auto-install.sh automatically installs whichever one matches the flake
target selected at install time — no manual per-host scp step needed.
Trade-off, accepted deliberately for this LAN-only setup: baking keys in
means every key present in host-keys/ at build time becomes readable by
anyone who can reach the built image — including, for the PXE variant, anyone
who can reach the pxe-boot host's unauthenticated HTTP server. This is
considered acceptable here because pxe-boot sits behind LAN-only network
infrastructure, not the open internet. If that ever changes, reconsider this
default.
auto-install.sh still supports the older manual path as a fallback: if a
host's key isn't baked in (/etc/host-keys), it checks /root/host-keys
next, where you can scp a key in after boot, same as before this migration.
Storage
Disk partitioning is handled by Disko — the installer has no hardcoded
parted/mkfs/mkswap/mount commands. Each host defines its own layout
via disko.devices; a host without one is installed in place via bind-mount
(this is how every LXC container target works, since they have no raw block
device to partition).
Installer process
/etc/auto-install.sh:
- Queries
nixosConfigurationsfrom this flake over the network (git+https://<lanDomain>/beatzaplenty/nixos.git) — this happens at install time, not build time, so a generic installer image always sees whatever hosts are currently committed, without needing a rebuild. - Presents them as a menu; confirms the choice.
- Skips the
nix-cachesubstituter when installing anix-cachehost itself (consistent with that host's own runtime config). - Runs
disko --mode destroy,format,mountif the target has a Disko config, otherwise bind-mounts/onto/mnt. - Installs the target's SSH host key from
/etc/host-keysor/root/host-keys(see "Host keys" above). - Runs
nixos-install --flake <url>#<choice> --no-root-password. - Cleans up and reboots.
Creating a new machine
Do this instead of jumping straight to a plain install whenever the target
host consumes any sops-nix secret — as of this writing, that's every host
(modules/common/configuration.nix puts the root/nixos password hash and the
GitHub token behind sops-nix for all of them).
-
On your admin workstation, decide the new machine's flake target name (
<platform>-<buildtype>, e.g.proxmox-server) and generate + register its host key:./scripts/prepare-host-key.sh <hostname>This generates
host-keys/<hostname>_ssh_host_ed25519_key(.pub)and prints the.sops.yamlsnippet to add, which secrets files to re-encrypt it into, and the next steps below. -
Edit
.sops.yamlwith the printed snippet, then re-encrypt every secrets file you added the new host to:nix-shell -p sops --run 'sops updatekeys secrets/common.yaml' -
Commit and push. The flake build the installer uses has to see the new recipient before you install, or decryption fails on first boot regardless of the next step.
-
Build the installer image with keys baked in (or reuse an already-serving
pxe-boothost, which does this automatically once redeployed):NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build .#iso --impure -
Boot it on the target machine, log in, select the new host's flake target from the menu, confirm.
auto-install.shfinds the baked-in key, runs Disko +nixos-install, and reboots. -
Verify after reboot:
ssh <new-host> ls /run/secrets/If that's empty or login fails, the host's age key most likely wasn't in
.sops.yaml(or wasn't re-encrypted into the secrets file it needs) whennixos-installran — fix.sops.yaml/secrets/*.yaml, push, then re-runnixos-install --flake .#<hostname> --no-root-passwordfrom a rescue environment against the existing/mnt, or just redo the install.
Safety
This installer is destructive: disko --mode destroy,format,mount erases
any disk defined by the selected host's Disko configuration. Always verify
the selected host profile and target machine before confirming.