How it works
Bring your config
Your NixOS flake imports the nixnas module and sets a handful of nixnas.* values. Everything you actually run — services, containers, orchestrators, shares — is your own declarative NixOS.
Write your image
A local Rust TUI builds the personalised image on your machine, signs it with your own Secure Boot keys, and flashes the stick. Your secrets never leave.
Boot from RAM
Encrypted, headless, running entirely in RAM. The stick unlocks via TPM2; your data waits for ONE passphrase over SSH (nixnas-unlock). A bad update? Boot the previous generation from the menu.
What nixnas does
Boot a Nix from USB, into RAM
From an 8 GB stick up. The root is a tmpfs (impermanence); only /nix and the ESP persist, and the closure is warmed into a compressed page cache — run-from-RAM without copytoram, self-update intact.
Secure Boot, your keys
lanzaboote signs every Unified Kernel Image with your own PK/KEK/db. The Microsoft CA is not enrolled — no unsigned kernel runs. Paired with a firmware password and measured boot, it's evil-maid hardened.
TPM2 store unlock
The store key is released only when the boot chain measures clean — optionally plus a PIN on every boot (strict default; auto-unlock is the unattended-NAS choice). A mandatory recovery keyslot is the fallback.
Headless remote unlock
No console? The box boots reachable with the data locked; nixnas-unlock over SSH opens everything. With the strict PIN, the initrd itself runs sshd — validated unlocking the box entirely over the network.
Tuned for speed
The CachyOS kernel (x86-64-v3 + ThinLTO), pulled pre-built from a binary cache — performance without a local compile. f2fs zstd:22 keeps the slow stick small.
Declarative & rollback-safe
The whole appliance is your git-versioned NixOS. Every past generation stays bootable as its own signed UKI; the bootloader menu is the guaranteed rollback, with boot-counting on top.
Self-update, never self-reboots
autoUpgrade stages the next generation and stops — it activates on your next PIN-unlocked reboot, so a headless box is never stuck at an unlock prompt.
Bring your own storage
Connect the storage you already have — any Linux filesystem and encryption. nixnas imports and unlocks it; it never creates, formats, or destroys it. The box boots even if a disk is missing.
Kind to the stick
Logs, /tmp, coredumps and swap live in RAM, so the USB takes ~no writes except updates — measured at 60 KiB for ~100 MiB of activity. Cheap sticks don't wear out.
Portable disks
Only the stick binds to the TPM. Your data carries a plain passphrase keyslot, so a disk pulled into any machine still opens — no specific box's TPM required.
One passphrase, all your data
nixnas-unlock opens your LUKS members serially — the first entry is cached in the kernel keyring and opens the rest, imports the pools, and starts everything gated on them. One entry per boot, for the whole set.
Thin by design
nixnas owns boot, crypto, the USB store, and kernel packaging — nothing else. Your services, containers, k3s, shares, VMs — whatever you run — are your own NixOS, declared alongside.
Architecture
USB stick (8 GB+, write-isolated)
ESP signed UKIs - Secure Boot (your keys) + rollback
store LUKS2 + f2fs-zstd - the nix store = your OS
|
| unlock once: TPM2 + PIN (headless, over SSH in the initrd)
v
root = tmpfs (RAM) - /nix warmed into compressed RAM
|
+---> your declarative config in git (services, containers, k3s - you bring)
|
+---> your Linux storage (any FS + encryption; imported, never formatted)
Impermanence
The root is a tmpfs; only /nix and the ESP survive a reboot. The OS runs from RAM — the slow stick is spared, runtime state lives on your storage.
You bring the rest
nixnas is the boot + crypto + store + kernel mechanism. Your workloads — k3s, containers, file shares, VMs — are plain NixOS in your own git, built into the same image.
Import-only
nixnas unlocks and mounts your existing storage. It never creates, formats, or destroys it — the only device it ever partitions is the USB stick.
Get started
Scaffold your host
nixnas is the public core; your real disks, keys and config live in your own flake that imports it:
nix flake init -t github:julian-corbet/nixnas#host
Fill in your nixnas.* values in host.nix — storage, SSH keys, the USB device — and add whatever else you run as normal NixOS.
Write & flash your image
The Rust TUI builds the personalised image on your trusted machine and flashes the stick:
cd tui && cargo run
# → builds .#image, prompts the passphrase,
# flashes via caligula
Requirements
A UEFI x86-64 box with TPM 2.0, an 8 GB+ USB stick, and your own storage (any Linux filesystem; LUKS members carry the chosen passphrase as a keyslot). Secure Boot needs your own keys enrolled + a firmware password.
Status. Built and validated end to end in a VM (QEMU / OVMF / swtpm): the CachyOS kernel, impermanence, the LUKS f2fs-zstd store, lanzaboote Secure Boot, TPM2 + PIN, headless initrd-SSH unlock, rollback and self-update all boot in one image. Hardware bring-up — Secure-Boot key enrolment on real UEFI, the boot-counting bless loop, the firmware password and the TPM2-NV anti-rollback counter — is the next step. See the architecture notes.