Configuration for my NixOS based systems and Home Manager
Nix 42%
Shell 31%
12%
JavaScript 7%
Lua 6%
Vim Script 2%
Makefile <1%

README.md

NixOS + Home Manager Configuration #

This repository contains my personal NixOS system configurations and Home Manager setup, managed via Nix flakes.

Overview #

This is a multi-host NixOS configuration using:

  • Nix Flakes for reproducible builds and dependency management
  • Home Manager for user environment configuration
  • agenix for secrets management (age-based encryption)
  • pre-commit-hooks for code quality

Hosts #

Hostname Purpose Platform
odin Main workstation with AMD GPU, Docker, Valheim game server x86_64-linux
misaki NAS/Server with ZFS, Plex, Jellyfin, Immich, Nginx reverse proxy x86_64-linux
shizuri Steam/gaming machine with LXQt desktop and XRDP x86_64-linux
touma-wsl WSL2 NixOS configuration x86_64-linux
aleister-noah macOS workstation (Home Manager only) aarch64-darwin

Repository Structure #

.
├── flake.nix              # Main flake configuration defining all hosts
├── flake.lock             # Lock file for reproducible builds
├── shell.nix              # Development shell configuration
├── boot.nix               # Bootloader configuration (shared)
├── common.nix             # Common system settings (timezone, locale, gc, etc.)
├── users.nix              # User definitions and SSH keys
├── services.nix           # Shared service configurations
├── default-home.nix       # Default Home Manager configuration
├── host-specific/         # Per-host configurations
│   ├── odin/
│   ├── misaki/
│   ├── shizuri/
│   └── touma-wsl.nix
├── overlays/              # Package overrides and package-producing overlays
├── secrets/               # Encrypted secrets (agenix)
├── config/                # User application configuration and scripts
└── README.md              # This file

Host Configuration Pattern #

Each host in host-specific/<hostname>/ follows a modular structure:

  • configuration.nix - Main entry point, imports other modules
  • hardware-configuration.nix - Auto-generated hardware config
  • boot.nix - Bootloader and kernel settings
  • networking.nix - Network configuration
  • packages.nix - System packages
  • services.nix - Service definitions
  • gui.nix - GUI/Desktop environment settings (where applicable)
  • users.nix - Host-specific users and groups

Adding a New Host #

  1. Create a new directory under host-specific/<hostname>/
  2. Copy the hardware configuration from the target machine:
    nixos-generate-config --show-hardware-config > host-specific/<hostname>/hardware-configuration.nix
    
  3. Create configuration.nix importing necessary modules
  4. Add the host to flake.nix in the nixosConfigurations output:
    nixosConfigurations.newhost = basicSystem {
      useUnstable = true;
      modules = [
        ./host-specific/newhost/configuration.nix
      ];
    };
    
  5. Build and switch:
    sudo nixos-rebuild switch --flake .#newhost
    

Secrets Management #

Secrets are encrypted using agenix with age encryption.

  • Secrets are stored in secrets/ with .age extension
  • Public keys for hosts/users are defined in secrets/secrets.nix
  • Each secret specifies which hosts/users can decrypt it
  • Deployed secrets are owned by specific users/groups with appropriate permissions

To add a new secret:

  1. Add the secret file to secrets/
  2. Define recipients in secrets/secrets.nix
  3. Encrypt: agenix -e secrets/my-secret.age
  4. Reference in your configuration via age.secrets.my-secret

Development #

This repo includes a development shell with formatting and linting tools:

nix develop

Pre-commit hooks are configured to run automatically on commit:

  • nixfmt-rfc-style - Nix code formatting
  • nil - Nix language server / linter
  • luacheck - Lua linting (for Neovim config)

Key Features #

Package Overrides #

  • neovim.nix - Pins Neovim to specific version
  • codex.nix - Pins Codex to specific version
  • lmstudio.nix - Local LLM GUI (AppImage wrapper)
  • inetutils.nix - Version override for inetutils

Home Manager #

The default-home.nix provides a comprehensive development environment including:

  • Neovim with extensive LSP and plugin configuration
  • Fish shell with custom configuration
  • Multiple language toolchains (Rust, Go, Clojure, Python, etc.)
  • Git configuration with delta/difftastic
  • Development tools (direnv, fzf, tmux, etc.)

Building #

NixOS System #

sudo nixos-rebuild switch --flake .#<hostname>

Home Manager (standalone) #

home-manager switch --flake .#noah
# or for macOS
home-manager switch --flake .#noah-aleister

Remote Builders #

shizuri, odin, and othinus are configured as trusted x86_64-linux Nix remote builders. Client hosts import modules/nix-remote-builders.nix through common.nix, so NixOS hosts in this flake will try to use each builder except for the current host itself.

Builder access uses the nixremote user declared in modules/nix-builder.nix. Client private keys live at /root/.ssh/nix-remote-builder; their public keys are declared in users.users.nixremote.openssh.authorizedKeys.keys.

Binary cache upload #

NixOS hosts also install a Nix post-build-hook from modules/nix-binary-cache.nix. After local builds, the hook copies $OUT_PATHS to ssh-ng://noah@misaki as the local noah user, using HOME=/home/noah and /home/noah/.ssh/config so SSH can use Noah's normal host aliases and default keypairs. Misaki skips this hook because its local paths are available from nix-serve through cache.ngp.computer.

NCPS serves cache.ngp.computer and reads older paths from Misaki's nix-serve. NGINX protects NCPS's /upload/ endpoint with secrets/nix-cache-push.age.

The hook warns instead of failing the build if the upload is unavailable. To disable it on a host:

ngp.binaryCacheCopy.enable = false;

Add client access #

On the client, generate the key and print its public half:

sudo install -d -m 700 /root/.ssh
sudo ssh-keygen -t ed25519 -N '' -C "nix-remote-builder@$(hostname)" -f /root/.ssh/nix-remote-builder
sudo cat /root/.ssh/nix-remote-builder.pub

Add the public key to modules/nix-builder.nix, then rebuild the builders once:

sudo nixos-rebuild switch --flake .#shizuri
sudo nixos-rebuild switch --flake .#odin
sudo nixos-rebuild switch --flake .#othinus

Builder host keys are declared alongside their addresses in modules/nix-remote-builders.nix and installed in the system SSH known-hosts file. When a builder's SSH host key changes, verify it and update its publicKey before rebuilding clients:

ssh-keyscan -t ed25519 <builder-address>

NixOS clients #

NixOS hosts in this flake already import the remote builder config through common.nix. After adding client access, rebuild the client:

sudo nixos-rebuild switch --flake .#<client-hostname>

Smoke test:

sudo ssh -i /root/.ssh/nix-remote-builder nixremote@shizuri nix-store --version
sudo ssh -i /root/.ssh/nix-remote-builder nixremote@odin nix-store --version
sudo ssh -i /root/.ssh/nix-remote-builder nixremote@othinus nix-store --version
sudo nix build nixpkgs#hello --max-jobs 0 -L

Standalone Home Manager clients #

Standalone Home Manager uses the host's Nix daemon. After adding client access, put the builder list in /etc/nix/nix.conf:

experimental-features = nix-command flakes
builders-use-substitutes = true
builders = ssh-ng://nixremote@shizuri x86_64-linux /root/.ssh/nix-remote-builder 24 4 nixos-test,benchmark,big-parallel,kvm - ; ssh-ng://nixremote@odin x86_64-linux /root/.ssh/nix-remote-builder 24 4 nixos-test,benchmark,big-parallel,kvm - ; ssh-ng://nixremote@othinus x86_64-linux /root/.ssh/nix-remote-builder 24 4 nixos-test,benchmark,big-parallel,kvm -

Restart the daemon and run Home Manager:

sudo systemctl restart nix-daemon
home-manager switch --flake .#noah

Linux builders cannot build normal Darwin outputs; homeConfigurations.noah-aleister needs Darwin builders.

Set up a new build host #

Import modules/nix-builder.nix from the new host configuration:

{
  imports = [
    ../../modules/nix-builder.nix
  ];
}

Add a matching entry, including its verified SSH publicKey, to the builders list in modules/nix-remote-builders.nix.

Rebuild the new builder first, then rebuild each client that should use it:

sudo nixos-rebuild switch --flake .#new-builder
sudo nixos-rebuild switch --flake .#<client-hostname>

Automated cache warming #

Misaki runs nix-impatient, which updates all updateable flake inputs daily and builds packages.x86_64-linux.nix-impatient using the remote builder pool. The aggregate package contains every Linux NixOS system closure in this flake and the standalone Linux Home Manager activation package. Build results return to Misaki's Nix store, where cache.ngp.computer serves and signs them.

Each run clears /tmp/nix-impatient-failure.log. A failed run recreates it with the local attempt time and the complete check log.

nix-impatient commits candidates as nix-impatient <misaki@ngp.computer>, runs the flake checks, builds the aggregate closure, and fast-forwards master in /srv/src/nixos.git only after everything succeeds. The commit body groups installed package version changes, additions, and removals by affected system, omitting pure size-only rebuilds and limiting the summary to 20 entries by default. The complete grouped changelog is written to the service journal. The bare repository's post-receive hook then mirrors the commit to its remotes. A concurrent update to master causes publication to fail instead of overwriting the newer commit.

The latest successful aggregate remains rooted. Previous successful roots are retained for 14 days. Configure the schedule, input selection, retention, and Git identity through services.nix-impatient in Misaki's configuration.

Trigger a complete update and build manually, then follow its logs:

sudo systemctl start --wait --verbose --no-pager nix-impatient.service

The equivalent mkfile target is mk impatient. It streams logs and exits when the run finishes. Use mk impatient-log to follow a run started elsewhere.

Build the current checkout without updating or publishing its lock file:

mk warm
# Equivalent to:
nix build .#nix-impatient --max-jobs 0 -L

Maintenance #

Regular maintenance tasks:

  • Update flakes: nix flake update
  • Garbage collect: nix-collect-garbage -d (also automatic via common.nix)
  • Check configuration: nix flake check

License #

This configuration is personal and not licensed for redistribution.