- Nix 94.4%
- Lua 2.8%
- Shell 2.4%
- Just 0.4%
| home | ||
| hosts | ||
| private@bd7b945bdb | ||
| .envrc | ||
| .gitignore | ||
| .gitmodules | ||
| .sops.yaml | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
NixOS Configuration
Modular NixOS + Home Manager configuration with impermanence, sops-nix, and disko.
Current hosts
| Host | Arch | Role | Hardware |
|---|---|---|---|
| hyades | x86_64 | Desktop | Ryzen 7 3700X · RX 6800 XT · 32GB |
| naiads | x86_64 | Laptop | Framework 13 (Ryzen AI 7 350 · 32GB) |
Structure
The configuration lives in the user-writeable /persist/nixos-directory.
.
├── flake.nix # Flake definition with all hosts
├── justfile # Task runner commands
├── private/ # Private modules (git submodule)
├── hosts
│ ├── modules
│ │ ├── core # Core system configuration (incl. shared disko for tmpfs root & home)
│ │ └── desktop # Desktop-specific system configuration
│ └── HOSTNAME # Host-specific system configuration (incl. disk disko, hardware)
└── home
├── modules
│ ├── core # Core home configuration
│ └── desktop # Desktop-specific home configuration
└── HOSTNAME # Host-specific home configuration
Disk management is split into two layers: a shared disko module in hosts/modules/core declares the tmpfs mounts for / and /home (identical across hosts), while each host's disko.nix defines the physical disk layout (ESP, swap, LUKS, btrfs subvolumes).
Flake inputs
| Input | Purpose |
|---|---|
| nixpkgs (unstable) | Package set |
| nixos-hardware | Hardware-specific quirks |
| disko | Declarative disk partitioning |
| home-manager | User environment management |
| impermanence | Ephemeral root with opt-in persistence |
| sops-nix | Secret management |
| stylix | System-wide theming |
| nix-index-database | Pre-built nix-index database |
| niri | Niri Wayland compositor |
| walker | Application launcher |
nixos-private (path:./private) |
Private modules (local submodule) |
Private modules
The private/ directory is a git submodule pointing to a private Forgejo repository. It is declared as a flake input via path:./private so existing ${inputs.nixos-private}/... imports work unchanged. Since the input resolves locally, no SSH access to the Git server is needed during build — only when cloning or updating the submodule.
Because nix flakes don't include submodule contents by default, the justfile passes the flake URI as git+file:<path>?submodules=1 to ensure the private/ directory is included when copying the source to the nix store.
This also simplifies disaster recovery: both repos can be downloaded as tarballs from the Forgejo web UI without needing SSH credentials, and the flake resolves path:./private from the extracted files.
Dev shell
A devShell is provided with common tooling (just, nvd, nil, nixfmt, sops, ssh-to-age). Enter it with nix develop or automatically via direnv.
Usage
Commands are run via just and use nh under the hood. The hostname is detected automatically.
just switch # Rebuild and switch to new configuration
just test # Build and activate without adding a boot entry
just boot # Build and add boot entry without activating
just update # Update flake inputs and rebuild
just bump-private # Update the private submodule to latest commit
just rollback # Roll back to the previous generation
just clean # Garbage collect (keep 7 days / 5 generations)
just gens # List generations
just verify-store # Verify the Nix store
Adding a new host
The recommended approach is to prepare the configuration and secrets on an existing host (where the devShell and sops keys are already available), commit and push, then install on the target machine.
1. Gather target hardware info
Boot the target machine with a NixOS installer ISO to collect the information needed for the configuration:
# Disk ID for disko.nix
ls /dev/disk/by-id/
# Hardware config
nixos-generate-config --show-hardware-config
2. Prepare the configuration (on an existing host)
- Create
hosts/<hostname>/directory - Add
disko.nixwith the host-specific disk layout (ESP, swap, LUKS, btrfs subvolumes). The tmpfs mounts for/and/homeare handled by the shared disko module and do not need to be declared per host. - Add
boot.nixandhardware.nix(from the output gathered above, or use the appropriate nixos-hardware module if available) - Add
default.niximporting the host-specific system configuration and the necessary system modules - Create
home/<hostname>/default.niximporting the host-specific home configuration and the necessary home modules - Add the host to
flake.nix:
nixosConfigurations = {
# ...existing hosts...
newhost = mkHost { system = "x86_64-linux"; hostname = "newhost"; };
};
3. Set up secrets (on an existing host)
This must be done before installation, as the build depends on sops-encrypted secrets.
- Generate a host-specific system ssh-key via Bitwarden (this will be used for sops decryption on the new host)
- Enter the dev shell (includes
sopsandssh-to-age):nix develop - Derive the public age-key from the host key's public half:
ssh-to-age < ssh_host_ed25519_key.pub - Append
.sops.yamlwith the host-specific public age-key and creation rule:keys: # Admin key # ... # Host keys - &<hostname> age1... creation_rules: - path_regex: hosts/<hostname>/secrets\.yaml$ key_groups: - age: - *admin - *<hostname> - Create
hosts/<hostname>/secrets.yamland add the necessary secrets:sops hosts/<hostname>/secrets.yaml - Generate the user ssh-key and add the private key as
ssh_private_key:ssh-keygen -t ed25519 -f /tmp/ssh_ed25519_<hostname> -N "" - Add the public key (
/tmp/ssh_ed25519_<hostname>.pub) tohosts/<hostname>/default.nixashost.sshPublicKey - Commit and push
4. Install (on the target machine)
- Boot the NixOS installer ISO and connect to the network
- Get the repo onto the installer. If SSH access is available:
If no SSH access is available (disaster recovery), download both repos as tarballs from the Forgejo web UI and extract them, placing the private repo insidegit clone --recursive <repo-url>private/. - Run disko to partition, encrypt, and mount:
This will create the LUKS volume (prompting for a passphrase) and mount everything undersudo nix run github:nix-community/disko -- --mode disko hosts/<hostname>/disko.nix/mnt. - Place the sops host key (from Bitwarden) in the persist volume:
Impermanence will bind-mountsudo mkdir -p /mnt/persist/etc/ssh # Write the private and public key from Bitwarden to: # /mnt/persist/etc/ssh/ssh_host_ed25519_key # /mnt/persist/etc/ssh/ssh_host_ed25519_key.pub/persist/etc/sshto/etc/sshat boot. - Place the sops
AGE-SECRET-KEYin the persist volume for the user:sudo mkdir -p /mnt/persist/home/<user>/.config/sops/age # Write the AGE-SECRET-KEY to: # /mnt/persist/home/<user>/.config/sops/age/keys.txt - Install:
sudo nixos-install --flake 'path:.#<hostname>' - Reboot into the new system
Persistence
Since the root filesystem is ephemeral (tmpfs), anything that should survive reboots must be explicitly persisted. All persistent state lives under /persist on a btrfs subvolume, and impermanence bind-mounts the declared paths back to their expected locations at boot (e.g. /persist/etc/ssh → /etc/ssh).
Persisted directories are defined in four locations:
| Scope | File | Option |
|---|---|---|
| System core | hosts/modules/core/persistence.nix |
environment.persistence |
| System desktop | hosts/modules/desktop/persistence.nix |
environment.persistence |
| Home core | home/modules/core/persistence.nix |
home.persistence |
| Home desktop | home/modules/desktop/persistence.nix |
home.persistence |
Add new directories as needed when installing new applications.