1
0
Fork 0
NixOS configuration
  • Nix 94.4%
  • Lua 2.8%
  • Shell 2.4%
  • Just 0.4%
Find a file
2026-04-08 16:13:59 +02:00
home
hosts
private@bd7b945bdb
.envrc
.gitignore
.gitmodules
.sops.yaml
flake.lock flake: update 2026-04-08 16:13:59 +02:00
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)

  1. Create hosts/<hostname>/ directory
  2. Add disko.nix with the host-specific disk layout (ESP, swap, LUKS, btrfs subvolumes). The tmpfs mounts for / and /home are handled by the shared disko module and do not need to be declared per host.
  3. Add boot.nix and hardware.nix (from the output gathered above, or use the appropriate nixos-hardware module if available)
  4. Add default.nix importing the host-specific system configuration and the necessary system modules
  5. Create home/<hostname>/default.nix importing the host-specific home configuration and the necessary home modules
  6. 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.

  1. Generate a host-specific system ssh-key via Bitwarden (this will be used for sops decryption on the new host)
  2. Enter the dev shell (includes sops and ssh-to-age):
    nix develop
    
  3. Derive the public age-key from the host key's public half:
    ssh-to-age < ssh_host_ed25519_key.pub
    
  4. Append .sops.yaml with 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>
    
  5. Create hosts/<hostname>/secrets.yaml and add the necessary secrets:
    sops hosts/<hostname>/secrets.yaml
    
  6. Generate the user ssh-key and add the private key as ssh_private_key:
    ssh-keygen -t ed25519 -f /tmp/ssh_ed25519_<hostname> -N ""
    
  7. Add the public key (/tmp/ssh_ed25519_<hostname>.pub) to hosts/<hostname>/default.nix as host.sshPublicKey
  8. Commit and push

4. Install (on the target machine)

  1. Boot the NixOS installer ISO and connect to the network
  2. Get the repo onto the installer. If SSH access is available:
    git clone --recursive <repo-url>
    
    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 inside private/.
  3. Run disko to partition, encrypt, and mount:
    sudo nix run github:nix-community/disko -- --mode disko hosts/<hostname>/disko.nix
    
    This will create the LUKS volume (prompting for a passphrase) and mount everything under /mnt.
  4. Place the sops host key (from Bitwarden) in the persist volume:
    sudo 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
    
    Impermanence will bind-mount /persist/etc/ssh to /etc/ssh at boot.
  5. Place the sops AGE-SECRET-KEY in 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
    
  6. Install:
    sudo nixos-install --flake 'path:.#<hostname>'
    
  7. 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.