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 moduleshardware-configuration.nix- Auto-generated hardware configboot.nix- Bootloader and kernel settingsnetworking.nix- Network configurationpackages.nix- System packagesservices.nix- Service definitionsgui.nix- GUI/Desktop environment settings (where applicable)users.nix- Host-specific users and groups
Adding a New Host #
- Create a new directory under
host-specific/<hostname>/ - Copy the hardware configuration from the target machine:
nixos-generate-config --show-hardware-config > host-specific/<hostname>/hardware-configuration.nix - Create
configuration.niximporting necessary modules - Add the host to
flake.nixin thenixosConfigurationsoutput:nixosConfigurations.newhost = basicSystem { useUnstable = true; modules = [ ./host-specific/newhost/configuration.nix ]; }; - 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.ageextension - 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:
- Add the secret file to
secrets/ - Define recipients in
secrets/secrets.nix - Encrypt:
agenix -e secrets/my-secret.age - 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 formattingnil- Nix language server / linterluacheck- Lua linting (for Neovim config)
Key Features #
Package Overrides #
neovim.nix- Pins Neovim to specific versioncodex.nix- Pins Codex to specific versionlmstudio.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 viacommon.nix) - Check configuration:
nix flake check
License #
This configuration is personal and not licensed for redistribution.