Getting started
This guide walks through a complete first deployment of Keystone, from initializing a project to booting a fully configured NixOS system with encrypted storage and Secure Boot. By the end, a target machine will be running a Keystone-managed operating system accessible via SSH.
Prerequisites
Before beginning, ensure the following are available:
- A development machine with Nix installed (with flakes enabled)
- A target machine with UEFI firmware, a TPM2 chip (recommended), and network connectivity
- An SSH key pair on the development machine (generate one with
ssh-keygen -t ed25519if needed) - A USB drive for the installer ISO (at least 2 GB)
Step 1: Initialize a project from the flake template
The Keystone flake template provides a complete starting point with all required inputs and a documented configuration file. Run the following on the development machine:
mkdir my-infrastructure && cd my-infrastructure
git init
nix flake init -t github:ncrmro/keystoneThis creates four files:
| File | Purpose |
| ------------------- | ---------------------------------------------------------------------- |
| flake.nix | Flake inputs (nixpkgs, Keystone, home-manager) and machine definitions |
| configuration.nix | System configuration with keystone.os.* options and TODO markers |
| hardware.nix | Hardware-specific settings (populated after first boot) |
| README.md | Quick reference for the project |
Step 2: Configure the system
Open configuration.nix and search for TODO: markers to find all values that must be set before deployment:
grep -n "TODO:" configuration.nixThe required changes are:
Hostname
networking.hostName = "myserver"; # Replace with desired hostnameHost ID
ZFS requires a unique 8-character hexadecimal host ID. Generate one:
head -c 4 /dev/urandom | od -A none -t x4 | tr -d ' 'Set the result in the configuration:
networking.hostId = "a1b2c3d4";Storage devices
Identify the target disk using stable /dev/disk/by-id/ paths. On the target machine (or one with identical hardware), run:
ls -la /dev/disk/by-id/Set the disk path in the storage configuration:
keystone.os.storage = {
type = "zfs"; # "zfs" (recommended) or "ext4"
devices = [ "/dev/disk/by-id/nvme-Samsung_SSD_980_PRO_2TB_S6B0NL0W127373V" ];
swap.size = "16G";
};For multi-disk configurations, add additional devices and set a mode:
keystone.os.storage = {
type = "zfs";
devices = [
"/dev/disk/by-id/nvme-disk1"
"/dev/disk/by-id/nvme-disk2"
];
mode = "mirror"; # RAID1 mirroring
swap.size = "16G";
};The available multi-disk modes are single, mirror, stripe, raidz1, raidz2, and raidz3. For guidance on choosing between ZFS and ext4, see the storage backends documentation.
User account
Configure at least one user with SSH access and sudo privileges:
keystone.os.users.admin = {
fullName = "Your Name";
email = "you@example.com";
extraGroups = [ "wheel" ];
authorizedKeys = [ "ssh-ed25519 AAAAC3... you@laptop" ];
initialPassword = "changeme"; # Change on first login
terminal.enable = true;
};For production systems, use a hashed password instead of initialPassword:
mkpasswd -m sha-512hashedPassword = "$6$rounds=...";For details on user configuration options including ZFS quotas, desktop enablement, and additional user examples, see the user management documentation.
Desktop (optional)
To deploy a workstation with a graphical environment, uncomment the desktop module in flake.nix:
keystone.nixosModules.desktop # Uncomment this lineThen enable the desktop for the user in configuration.nix:
keystone.os.users.admin.desktop = {
enable = true;
hyprland.modifierKey = "SUPER";
};Security features
Secure Boot and TPM are enabled by default in the template. To disable them for hardware that does not support these features:
keystone.os.secureBoot.enable = false;
keystone.os.tpm.enable = false;Step 3: Build the installer ISO
The installer ISO provides a minimal NixOS environment with SSH access, allowing nixos-anywhere to connect and deploy the configuration remotely.
From the Keystone repository (not the project directory), build the ISO with the development machine's SSH public key:
git clone git@github.com:ncrmro/keystone.git
cd keystone
./bin/build-iso --ssh-key ~/.ssh/id_ed25519.pubThe --ssh-key option accepts either a file path or a key string directly:
# From a file
./bin/build-iso --ssh-key ~/.ssh/id_ed25519.pub
# As a string
./bin/build-iso --ssh-key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG... user@host"An ISO without embedded SSH keys can also be built, though it requires console access to the target machine to retrieve credentials:
./bin/build-isoWrite the ISO to USB
# Identify the USB device
lsblk
# Write the ISO (replace /dev/sdX with the correct device)
sudo dd if=result/iso/*.iso of=/dev/sdX bs=4M status=progress
syncStep 4: Boot the target machine
- Insert the USB drive into the target machine.
- Boot from the USB drive (this may require changing the boot order in the UEFI firmware settings).
- Wait for the installer to finish booting. It automatically configures DHCP networking and starts an SSH server.
- Retrieve the target machine's IP address. If console access is available:
ip addr show- Verify SSH connectivity from the development machine:
ssh root@<installer-ip>Step 5: Deploy with nixos-anywhere
Return to the project directory on the development machine and run nixos-anywhere to install the configuration on the target:
cd my-infrastructure
nixos-anywhere --flake .#my-machine root@<installer-ip>Replace my-machine with the name used in the nixosConfigurations block of flake.nix (the template default is my-machine).
The installation process performs the following steps:
- Disko partitions and formats the target disk according to the
keystone.os.storageconfiguration (UEFI ESP, swap, LUKS-encrypted root with ZFS or ext4). - nixos-anywhere installs the NixOS system closure onto the formatted disk.
- The target machine reboots into the installed system.
This process typically takes 5 to 15 minutes depending on network speed and hardware.
Step 6: First-boot verification
After the target machine reboots, connect via SSH using the user account configured in step 2:
ssh admin@<target-ip>Verify that the system is operational:
# Check ZFS pools and datasets
zpool list
zfs list
# Check LUKS encryption status
lsblk -f
# Check Keystone systemd services
systemctl status keystone-*
# Check Secure Boot status (if enabled)
bootctl statusStep 7: Post-installation security enrollment
Secure Boot key enrollment
If Secure Boot is enabled, enroll custom keys after the first successful boot:
sudo sbctl create-keys
sudo sbctl enroll-keys --microsoft
sudo nixos-rebuild switch --flake .#my-machineFor details on Secure Boot key management and rotation, see the Secure Boot documentation.
TPM enrollment
After Secure Boot keys are enrolled, bind the LUKS encryption key to the TPM so that the disk unlocks automatically on trusted boots:
sudo keystone-enroll-tpmThis binds the LUKS key to the PCR values specified in the configuration (by default, PCRs 1 and 7, which measure firmware configuration and Secure Boot state). For details on TPM enrollment, re-enrollment after firmware updates, and recovery procedures, see the disk encryption documentation.
Step 8: Generate hardware configuration
On the target machine, generate a hardware configuration file that captures the specific hardware (PCI devices, kernel modules, firmware):
nixos-generate-config --show-hardware-config > hardware.nixCopy this file back to the project directory on the development machine and commit it. Future rebuilds will include hardware-specific kernel modules and firmware.
Updating the system
After the initial deployment, apply configuration changes by editing configuration.nix on the development machine and rebuilding:
# Update flake inputs to latest versions
nix flake update
# Rebuild remotely
nixos-rebuild switch --flake .#my-machine --target-host admin@<target-ip>
# Or rebuild locally on the target machine
sudo nixos-rebuild switch --flake .#my-machineAdding more machines
To manage multiple machines from the same project, duplicate the machine block in flake.nix and create per-machine configuration directories:
machines/
server/
configuration.nix
hardware.nix
laptop/
configuration.nix
hardware.nixReference these files in the corresponding nixosConfigurations entry in flake.nix.
Troubleshooting
"hostId is required for ZFS"
Generate a host ID and set networking.hostId in configuration.nix:
head -c 4 /dev/urandom | od -A none -t x4 | tr -d ' 'Disk not found during boot
Ensure that storage device paths use /dev/disk/by-id/ identifiers, not unstable names like /dev/sda or /dev/nvme0n1. Unstable device names can change between boots.
SSH connection refused on the installer
Verify that the ISO was built with the correct SSH public key. Check that the SSH service is running on the installer:
systemctl status sshdSecure Boot verification failed
This occurs when Secure Boot keys have not been enrolled after installation. Follow the Secure Boot key enrollment steps in the post-installation section above.
TPM enrollment failed
- Verify that Secure Boot keys are enrolled first (TPM enrollment depends on a stable boot chain).
- Confirm that the TPM2 chip is enabled in the UEFI firmware settings.
- Check TPM device availability:
ls /dev/tpm*
See also
- Architecture -- Module system overview and deployment patterns
- Storage backends -- Choosing between ext4 and ZFS
- Disk encryption -- LUKS2 encryption and TPM auto-unlock details
- User management -- User configuration options and examples
- Testing -- VM-based testing workflows for validating configurations