Archived
sops-nix (in the nixos flake) derives each host's age decryption key from its own /etc/ssh/ssh_host_ed25519_key at activation time, which runs before systemd would otherwise generate that key on first boot (sshd-keygen is a plain systemd service gated behind multi-user.target; activation scripts run earlier). Without pre-seeding, secrets -- including the login password -- fail to decrypt on a fresh install's very first boot. - scripts/prepare-host-key.sh: run on the admin workstation before an install, generates the host's ed25519 keypair and prints the exact steps to register its derived age key in nixos/.sops.yaml and re-encrypt the affected secrets/*.yaml files. - common.nix's auto-install.sh: after disko mounts /mnt and before nixos-install, installs a pre-seeded key from /root/host-keys/ into /mnt/etc/ssh/ if present, otherwise warns and asks for confirmation before continuing without one. - installer.nix now imports common.nix (previously only proxmox-lxc.nix did), so the ISO/netboot path used for EFI VM installs gets the same auto-install.sh and pre-seed check, not just the LXC path. - Also fixes a pre-existing stray backtick in the disko invocation that broke auto-install.sh's bash syntax entirely, independent of this change (found while rendering the script to verify the new logic). README.md documents the new pre-flight workflow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
377 lines
8.5 KiB
Markdown
377 lines
8.5 KiB
Markdown
# Nix Auto Installer
|
|
|
|
This repository builds a custom NixOS installer environment that can automatically
|
|
install any host exposed by a separate NixOS flake.
|
|
|
|
The installer provides a small NixOS netboot/install environment with SSH access,
|
|
Git support, and an interactive installation script. When the `nixos` user logs
|
|
in, it runs `/etc/auto-install.sh`, discovers available hosts from the target
|
|
flake, allows the operator to choose a system profile, applies the matching
|
|
Disko storage configuration, installs NixOS, and reboots.
|
|
|
|
The installer is designed for a mixed environment where different machines may
|
|
have different storage layouts, including Proxmox VMs, Linode instances, and
|
|
physical hardware. Disk layout is no longer hardcoded in the installer; it is
|
|
defined by each host's NixOS configuration using Disko.
|
|
|
|
## Repository Layout
|
|
|
|
* `flake.nix`
|
|
|
|
* Defines the installer build outputs.
|
|
* Builds an `install-iso` image using `nixos-generators`.
|
|
* Builds netboot kernel, initrd, and iPXE scripts.
|
|
|
|
* `installer.nix`
|
|
|
|
* Defines the installer environment.
|
|
* Enables SSH access.
|
|
* Configures Git access to the target flake repository.
|
|
* Creates the `nixos` and `root` users.
|
|
* Provides the generated `/etc/auto-install.sh` installation script.
|
|
|
|
The installed system configuration lives in a separate NixOS flake. Each target
|
|
host is exposed through:
|
|
|
|
```nix
|
|
nixosConfigurations.<hostname>
|
|
```
|
|
|
|
and may optionally include a Disko storage configuration.
|
|
|
|
## Requirements
|
|
|
|
The build machine requires:
|
|
|
|
* Nix installed with flakes enabled.
|
|
* Network access to fetch flake inputs.
|
|
* Ability to build NixOS images.
|
|
|
|
The target machine requires:
|
|
|
|
* Network connectivity during installation.
|
|
* A disk defined by its Disko configuration.
|
|
* SSH access or local console access.
|
|
|
|
## Build The Installer ISO
|
|
|
|
From the repository root:
|
|
|
|
```sh
|
|
nix build
|
|
```
|
|
|
|
The generated installer ISO will be available through the `result` symlink.
|
|
|
|
The ISO is generated using:
|
|
|
|
```nix
|
|
nixos-generators.nixosGenerate {
|
|
format = "install-iso";
|
|
}
|
|
```
|
|
|
|
## Build Netboot Components
|
|
|
|
The flake also exposes netboot components:
|
|
|
|
```sh
|
|
nix build .#netboot-ipxe
|
|
nix build .#netboot-initrd
|
|
nix build .#netboot-kernel
|
|
```
|
|
|
|
These can be used to PXE boot the installer environment.
|
|
|
|
## Use The Installer
|
|
|
|
1. Boot the generated ISO or netboot environment on the target machine.
|
|
|
|
2. Log in as the `nixos` user.
|
|
|
|
3. The login shell launches:
|
|
|
|
```sh
|
|
/etc/auto-install.sh
|
|
```
|
|
|
|
4. The installer queries the target flake:
|
|
|
|
```text
|
|
git+https://gitea.lan.ddnsgeek.com/beatzaplenty/nixos.git
|
|
```
|
|
|
|
5. Available installation profiles are discovered from:
|
|
|
|
```nix
|
|
nixosConfigurations
|
|
```
|
|
|
|
6. Select the host profile to install.
|
|
|
|
7. Confirm the installation.
|
|
|
|
8. The installer applies the selected host's Disko storage layout.
|
|
|
|
9. NixOS is installed using the selected flake configuration.
|
|
|
|
10. The system reboots into the installed OS.
|
|
|
|
## Storage Management
|
|
|
|
Disk partitioning is handled by Disko.
|
|
|
|
The installer no longer contains hardcoded commands such as:
|
|
|
|
```sh
|
|
parted
|
|
mkfs.ext4
|
|
mkswap
|
|
mount
|
|
```
|
|
|
|
Instead, each NixOS host defines its own storage layout.
|
|
|
|
Example:
|
|
|
|
```nix
|
|
{
|
|
disko.devices = {
|
|
disk.main = {
|
|
type = "disk";
|
|
device = "/dev/sda";
|
|
|
|
content = {
|
|
type = "gpt";
|
|
|
|
partitions = {
|
|
root = {
|
|
size = "-8G";
|
|
|
|
content = {
|
|
type = "filesystem";
|
|
format = "ext4";
|
|
mountpoint = "/";
|
|
};
|
|
};
|
|
|
|
swap = {
|
|
size = "100%";
|
|
|
|
content = {
|
|
type = "swap";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|
|
```
|
|
|
|
This allows different hosts to define different layouts:
|
|
|
|
* Proxmox VMs
|
|
* Linode instances
|
|
* Physical servers
|
|
* ZFS systems
|
|
* Future hardware-specific layouts
|
|
|
|
A host without a Disko configuration will not be automatically partitioned.
|
|
|
|
## Installer Process
|
|
|
|
The generated `/etc/auto-install.sh` performs the following steps:
|
|
|
|
1. Fetch available hosts from the configured NixOS flake.
|
|
|
|
2. Present available `nixosConfigurations` as a menu.
|
|
|
|
3. Confirm the selected installation target.
|
|
|
|
4. Check whether the selected host has a Disko configuration.
|
|
|
|
5. Run:
|
|
|
|
```sh
|
|
disko --mode destroy,format,mount --flake <flake>#<host>
|
|
```
|
|
|
|
6. Prepare `/mnt` using the Disko-generated mount configuration.
|
|
|
|
7. Check for a pre-seeded SSH host key at `/root/host-keys/<host>_ssh_host_ed25519_key`
|
|
and install it to `/mnt/etc/ssh/` if present (see "Pre-Seeding SSH Host
|
|
Keys for sops-nix" below) — prompts for confirmation before continuing
|
|
without one.
|
|
|
|
8. Install NixOS:
|
|
|
|
```sh
|
|
nixos-install \
|
|
--flake <flake>#<host> \
|
|
--no-root-password
|
|
```
|
|
|
|
9. Remove temporary installation files.
|
|
|
|
10. Reboot.
|
|
|
|
## Pre-Seeding SSH Host Keys for sops-nix
|
|
|
|
The `nixos` flake manages secrets with sops-nix, using an age key derived
|
|
from each host's own `/etc/ssh/ssh_host_ed25519_key`. That key is normally
|
|
generated by a systemd service (`sshd-keygen`) the first time a host boots
|
|
— but sops-nix decrypts secrets (including the login password) earlier
|
|
than that, during system *activation*, which runs before systemd starts
|
|
pursuing the target that `sshd-keygen` is gated behind. On a genuinely
|
|
fresh install, this means secrets fail to decrypt on the very first boot
|
|
unless the host key already exists beforehand.
|
|
|
|
To avoid this, generate the key ahead of time and register it with the
|
|
`nixos` flake's sops-nix setup **before** installing:
|
|
|
|
```sh
|
|
./scripts/prepare-host-key.sh <hostname> [path-to-nixos-repo]
|
|
```
|
|
|
|
This generates `host-keys/<hostname>_ssh_host_ed25519_key(.pub)` locally
|
|
and prints the exact steps to add its derived age key to `nixos/.sops.yaml`,
|
|
re-encrypt the relevant `secrets/*.yaml` files with `sops updatekeys`, and
|
|
commit + push the `nixos` repo.
|
|
|
|
Once that's done and the target machine is booted into the installer,
|
|
copy the generated key onto it before running (or continuing)
|
|
`/etc/auto-install.sh`:
|
|
|
|
```sh
|
|
scp host-keys/<hostname>_ssh_host_ed25519_key{,.pub} root@<target-ip>:/root/host-keys/
|
|
```
|
|
|
|
`auto-install.sh` checks for this file automatically and installs it to
|
|
the target's `/mnt/etc/ssh/` before running `nixos-install`. If it's
|
|
missing, the script warns and asks for confirmation before continuing —
|
|
useful for hosts that don't consume any sops-nix secrets, but skipping it
|
|
for a host that does will lock secrets out of decrypting on first boot.
|
|
|
|
`host-keys/` is gitignored — never commit private key material.
|
|
|
|
## Configuration Notes
|
|
|
|
The installer currently assumes:
|
|
|
|
* Target flake:
|
|
|
|
```text
|
|
git+https://gitea.lan.ddnsgeek.com/beatzaplenty/nixos.git
|
|
```
|
|
|
|
* Host definitions are under:
|
|
|
|
```nix
|
|
nixosConfigurations
|
|
```
|
|
|
|
* The installer has network access before installation.
|
|
|
|
* SSH is enabled.
|
|
|
|
* Root SSH login is permitted.
|
|
|
|
* The timezone is:
|
|
|
|
```text
|
|
Australia/Brisbane
|
|
```
|
|
|
|
* The target host is responsible for defining its own storage layout.
|
|
|
|
Change these values in `installer.nix` if your environment differs.
|
|
|
|
## Security Notes
|
|
|
|
`installer.nix` currently contains:
|
|
|
|
* Git credentials
|
|
* Password hashes
|
|
* SSH public keys
|
|
|
|
Treat the repository and generated installer images as sensitive.
|
|
|
|
For production use, consider replacing embedded credentials with:
|
|
|
|
* Short-lived tokens
|
|
* SSH deploy keys
|
|
* External secrets management
|
|
* Runtime credential injection
|
|
|
|
## Safety Warnings
|
|
|
|
This installer is destructive.
|
|
|
|
The command:
|
|
|
|
```sh
|
|
disko --mode destroy,format,mount
|
|
```
|
|
|
|
will erase any disks defined by the selected host's Disko configuration.
|
|
|
|
Always verify:
|
|
|
|
* The selected host profile.
|
|
* The Disko device paths.
|
|
* The target machine.
|
|
|
|
Never boot this installer on a machine containing important data unless the
|
|
storage configuration has been reviewed.
|
|
|
|
## Troubleshooting
|
|
|
|
### No hosts appear in the menu
|
|
|
|
Check that the installer can reach the flake repository:
|
|
|
|
```sh
|
|
nix eval --json \
|
|
"git+https://gitea.lan.ddnsgeek.com/beatzaplenty/nixos.git#nixosConfigurations" \
|
|
--apply builtins.attrNames
|
|
```
|
|
|
|
### Selected host has no Disko configuration
|
|
|
|
The installer checks for:
|
|
|
|
```nix
|
|
config.disko.devices
|
|
```
|
|
|
|
Hosts without Disko storage definitions must either:
|
|
|
|
* Add a Disko module, or
|
|
* Be installed using another storage method.
|
|
|
|
### Disko evaluation fails
|
|
|
|
Check the selected host locally:
|
|
|
|
```sh
|
|
nix eval \
|
|
".#nixosConfigurations.<host>.config.disko.devices"
|
|
```
|
|
|
|
### Installation fails fetching the flake
|
|
|
|
Verify:
|
|
|
|
* Network connectivity.
|
|
* Git credentials.
|
|
* Flake input availability.
|
|
|
|
### Installer does not start automatically
|
|
|
|
Run manually:
|
|
|
|
```sh
|
|
sudo /etc/auto-install.sh
|
|
```
|