Table of contents
- Chapter 3: Host Configurations
- The Hosts
- All Hosts Share One Foundation
- Electra (Framework 16)
- Base (battery) tier
- igpu specialisation
- dgpu specialisation
- Hibernation and encrypted swap
- Hardware files
- Host-specific Ollama providers
- Lena (Lenovo 2-in-1)
- Vega (Home Server)
- Lyra (GPU Compute and Media Server)
- The Specialisation Detection Mechanism
- Design Pattern: Where Does Configuration Go?
- Adding a New Host
Chapter 3: Host Configurations
This chapter examines each host in detail: what it is, what it imports, and the design choices behind it.
The Hosts
| Host | Machine | Channel | Purpose |
|---|---|---|---|
electra |
Framework 16 (AMD AI 300) | unstable-small | Primary machine — desktop, gaming, AI |
lena |
Lenovo Ideapad 5 2-in-1 (Gen 9) | unstable | Family laptop |
vega |
Home server (headless) | unstable | Storage-backed services, AI agents, Attic cache |
lyra |
Intel/NVIDIA server (headless) | unstable | GPU compute, media stacks, Ollama |
All Hosts Share One Foundation
All hosts import modules/common/base.nix, which pulls in:
# modules/common/base.nix imports:
./nix-settings.nix # Flakes, caches, parallelism, allowUnfree
./locale.nix # en_GB, Europe/London, UK keyboard
./default-config.nix # nixosConfig.* portability options
./sops-host.nix # sops-nix host key setup
../maintenance/garbage-collection.nix # Daily GC, 7-day age limit
../maintenance/btrfs-maintenance.nix # Monthly btrfs scrub
../services/beszel-agent.nix # System monitoring agent
../services/backrest.nix # Automated backups (Backrest/restic)
inputs.disko.nixosModules.disko # Declarative disk partitioning support
Plus pcscd for YubiKey support, NetworkManager with captive portal detection disabled, the shared Hermes agent module, and shared service-failure monitoring. Electra overrides the common systemd-boot default with Lanzaboote for Secure Boot.
Desktop hosts (electra, lena) also import modules/desktop/environment.nix:
# modules/desktop/environment.nix imports:
../common/system.nix # Base system packages
./fonts.nix
../hardware/printing.nix
../hardware/removable-storage.nix
# Plus: programs.firefox.enable, AMD GPU utilities
Server hosts (vega and lyra) instead import modules/server/base.nix:
# modules/server/base.nix imports:
../common/system.nix # Base system packages
# Plus: OpenSSH (no passwords, no root), mosh, network tools
Electra (Framework 16)
Electra has three boot tiers. See Chapter 4 for a full explanation of the specialisation mechanism.
Base (battery) tier
The base configuration is the battery-optimised minimal tier, used when booting without a specific specialisation selected.
# hosts/electra/default.nix (base tier)
imports = [
# Laptop profile — aggregates common/base, desktop/environment, kde-plasma,
# apps, bluetooth, laptop hardware, wifi, wireguard, ai-desktop, maker,
# auto-update, and users/nimmo
../../modules/profiles/laptop.nix
# Hardware
./hardware-configuration.nix
../../modules/hardware/expansion-card-mount.nix
# Power management
../../modules/power/auto-power-profile.nix
../../modules/power/battery-efficiency.nix # Reversible EPP tuning (battery tier)
# Electra-specific services
../../modules/services/btrbk-home.nix # Hourly /home snapshots
../../modules/profiles/ollama.nix # Ollama on AMD iGPU (port 11434)
../../modules/profiles/jobseeking.nix
# Host-specific hardware
./framework-16.nix # Framework-specific hardware configuration
./disko.nix # Declarative encrypted disk layout
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
../../modules/virtualization/docker-amd.nix
];
networking.hostName = "electra";
boot.initrd.systemd.enable = true; # Required for TPM2 unlock
# Persistent encrypted swap for hibernation/resume
swapDevices = [{ device = "/dev/mapper/cryptswap"; priority = 0; }];
boot.resumeDevice = "/dev/mapper/cryptswap";
# Endurance mode: automatic target is power-saver unless manually overridden
laptop.enduranceMode.enable = true;
power.autoPowerProfile.strategy = "endurance";
environment.etc."nixos-specialisation".text = "battery";
system.nixos.label = "battery";
# ROCm compatibility workaround for AMD RDNA3 780M iGPU
environment.variables.HSA_OVERRIDE_GFX_VERSION = "11.0.0";
# Nouveau blocked in case NVIDIA expansion bay is physically present
boot.blacklistedKernelModules = [ "nouveau" ];
# Auto-update: pull latest flake.lock from git and rebuild
services.nixos-auto-update = {
enable = true;
pullOnly = true;
};
The nixos-hardware module for Framework 16 AMD AI 300 series handles: AMD CPU microcode, amd-pstate, hardware.graphics, amdgpu initrd, framework-laptop-kmod, fprintd, IIO sensors, QMK, fstrim, power-profiles-daemon, fwupd, and kernel parameters.
igpu specialisation
The igpu tier is the full-featured daily-use configuration. It uses the dynamic power strategy and adds the gaming stack:
specialisation.igpu.configuration = {
environment.etc."nixos-specialisation".text = lib.mkForce "igpu";
system.nixos.label = lib.mkForce "igpu";
power.autoPowerProfile.strategy = lib.mkForce "dynamic";
laptop.enduranceMode.enable = lib.mkForce false;
power.batteryEfficiency.enable = lib.mkForce false;
imports = sharedSpecialisationImports ++ [
../../modules/profiles/maker.nix
];
};
The igpu tier inherits all base capabilities (Plasma, AI tools, Ollama, Docker AMD, maker tools) and adds:
- Dynamic power profile (switches between power-saver/balanced/performance based on AC state)
- Full gaming stack: Steam, GameMode, Gamescope, Proton-GE, Discord, MangoHud, Moonlight, Remote Play, and LAN game transfers
dgpu specialisation
The dgpu tier adds NVIDIA drivers, NVIDIA Docker support, and activates the dGPU Ollama backend:
specialisation.dgpu.configuration = {
environment.etc."nixos-specialisation".text = lib.mkForce "dgpu";
system.nixos.label = lib.mkForce "dgpu";
imports = sharedSpecialisationImports ++ [
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series-nvidia
../../modules/hardware/nvidia-gpu.nix # NVIDIA drivers, Prime offload
../../modules/virtualization/docker-nvidia.nix # Docker with NVIDIA CDI
../../modules/virtualization/docker-amd.nix # AMD 780M iGPU access (also present)
];
nixosConfig.gpu.nvidia.enable = true;
power.autoPowerProfile.strategy = lib.mkForce "dynamic";
laptop.enduranceMode.enable = lib.mkForce false;
power.batteryEfficiency.enable = lib.mkForce false;
};
Both the AMD 780M iGPU and the NVIDIA dGPU are physically present in dgpu mode, so HSA_OVERRIDE_GFX_VERSION (set in the base) applies to the AMD GPU in both modes. The sharedSpecialisationImports let binding avoids repeating the common igpu/dgpu imports (currently just gaming.nix).
Hibernation and encrypted swap
Electra's Disko layout creates a persistent encrypted swap mapping at /dev/mapper/cryptswap for hibernation. Zram remains higher priority for normal memory pressure, while disk swap is available as the resume target. The initrd unlocks cryptswap before resume, and boot.resumeDevice applies across the base, igpu, and dgpu tiers.
Logind is configured for suspend-then-hibernate on lid close, hibernate on power button, and hibernate at 5% critical battery.
Hardware files
framework-16.nix — imported by the base, configures Framework-specific hardware, AMD iGPU behavior, fingerprint support, zram, and related kernel settings.
modules/hardware/nvidia-gpu.nix — enabled only by the dgpu specialisation, adds the NVIDIA proprietary driver, Prime offload configuration, and NVIDIA kernel modules.
Host-specific Ollama providers
Electra declares host-specific Ollama endpoints for opencode via the profiles.aiDesktop.opencodeExtraProviders option, injecting local iGPU (port 11434) and dGPU (port 11435) providers into the opencode config. The inactive instance simply won't respond in the current boot tier.
Lena (Lenovo 2-in-1)
Lena uses the default unstable nixpkgs input. Its system.stateVersion remains 25.11, which records the version used at first install and should not be treated as the active package channel.
# hosts/lena/default.nix
imports = [
../../modules/common/base.nix
../../modules/desktop/environment.nix
./hardware-configuration.nix
./disko.nix
../../modules/hardware/laptop.nix
inputs.nixos-hardware.nixosModules.lenovo-ideapad-16ahp9
../../modules/boot/silent-boot.nix
../../modules/desktop/kde-plasma.nix
../../modules/power/auto-power-profile.nix
../../modules/hardware/bluetooth.nix
../../modules/networking/wifi-networks.nix
../../modules/networking/wireguard-vpn.nix
../../modules/profiles/ai-desktop.nix
../../modules/profiles/maker.nix
../../modules/services/nixos-auto-update.nix
../../modules/desktop/apps.nix
../../users/nimmo.nix
../../users/claire.nix # Second user (not on any other host)
];
networking.hostName = "lena";
hardware.sensor.iio.enable = true; # Tablet mode: screen rotation, etc.
services.fprintd.enable = true; # Fingerprint reader
services.smartd.enable = true; # S.M.A.R.T. disk monitoring
nixosConfig.wireguard = {
enable = true;
ipv4Address = "10.0.0.2/24"; # WireGuard IP (different from electra)
};
services.displayManager.sddm = {
enable = true;
autoNumlock = true;
};
services.nixos-auto-update = {
enable = true;
pullOnly = true;
};
Notable differences from electra:
- Default unstable channel
disko.nixfor declarative disk partitioninglenovo-ideapad-16ahp9nixos-hardware module for Lenovo-specific optimizations- Tablet mode support (
hardware.sensor.iio.enable) - Two users: nimmo and claire
- SDDM display manager (electra uses plasma-login-manager)
- No specialisations, no Docker, no Ollama (no discrete GPU)
ai-desktopprofile (same as electra — CLI tools + opencode, but no local Ollama providers configured)- WireGuard VPN client enabled (same
wireguard-vpn.nixmodule as electra, different IP:10.0.0.2/24) - Auto-update in pull-only mode
Vega (Home Server)
Vega is a headless NixOS server on the unstable channel. It runs AI agent tooling, Docker, Attic binary cache hosting, Paseo, automated backups, and system monitoring.
# hosts/vega/default.nix
imports = [
../../modules/common/base.nix
../../modules/server/base.nix # SSH, mosh, network tools
./hardware-configuration.nix
./disko.nix
../../modules/profiles/ai-desktop.nix # AI agents + headless opencode/Paseo support
../../modules/virtualization/docker.nix
../../modules/services/atticd.nix
../../modules/services/nixos-auto-update.nix
../../users/nimmo.nix
];
networking.hostName = "vega";
# Persistent SSD storage (survives OS wipe, managed outside disko)
fileSystems."/mnt/storage" = {
device = "/dev/disk/by-uuid/...";
fsType = "btrfs";
options = [ "compress=zstd:3" "noatime" "subvol=@storage" ];
};
fileSystems."/mnt/partial" = { ... }; # nodatacow for large volatile files
# NFS/CIFS support for tdarr NAS volumes
boot.supportedFilesystems = [ "nfs" "cifs" ];
profiles.aiDesktop.headless = true;
nixosConfig.forgejoMcp.enable = true;
services.paseo = {
enable = true;
user = user;
group = "users";
dataDir = "/srv/services/paseo";
listenAddress = "127.0.0.1";
openFirewall = false;
};
services.nixos-auto-update = {
enable = true;
pullOnly = true;
};
Notable features:
modules/server/base.nix— OpenSSH (no passwords, no root), mosh, network toolsmodules/profiles/ai-desktop.nixwithheadless = true— AI agents plus headless opencode/Paseo support without GUI launchers- Docker (basic, no GPU access needed)
- Attic cache server at
https://attic.nimmog.uk/nixos - Paseo daemon bound to loopback and registered with the self-hosted TLS relay
- Self-hosted Paseo relay and web UI containers, reached through Pangolin/newt
- Native NixOS newt and SillyTavern services
- Hermes agent with Matrix E2EE integration
- Forgejo issues/wiki MCP service proxied only onto vega's Nebula address at
port
8780, with its bot token supplied from SOPS - Backrest exposed through the authenticated reverse-proxy path
- Nebula mesh node and CoreDNS resolver for
.meshnames - Two storage mounts on a separate persistent SSD (
/mnt/storage,/mnt/partial) - Service data under
/srv/servicesand Docker stack ownership under/srv/stacks - NFS/CIFS support for accessing NAS volumes
- Auto-update in pull-only mode
isServer = true— home-manager skips all desktop-only packages- No display manager, no Plasma, no GUI tools
Lyra (GPU Compute and Media Server)
Lyra is a headless NixOS server housing an Intel i5-1340P and an NVIDIA RTX 4070 eGPU (Thunderbolt-connected). It is the GPU compute and media host, running Ollama on the NVIDIA GPU, NVIDIA Docker for CUDA workloads, and a collection of Docker-based media and AI stacks under /srv/stacks.
# hosts/lyra/default.nix
imports = [
../../modules/common/base.nix
../../modules/server/base.nix
./hardware-configuration.nix
./disko.nix
../../modules/virtualization/docker-nvidia.nix
../../modules/profiles/ollama.nix
../../modules/services/nixos-auto-update.nix
../../users/nimmo.nix
];
networking.hostName = "lyra";
services.xserver.videoDrivers = [ "nvidia" ];
hardware.nvidia = {
modesetting.enable = true;
open = false;
package = config.boot.kernelPackages.nvidiaPackages.stable;
};
nixosConfig.gpu.nvidia.enable = true;
profiles.ollama = {
enable = true;
nvidia.host = "0.0.0.0"; # Ollama on dGPU, LAN-accessible
};
boot.supportedFilesystems = [ "nfs" "cifs" ];
services.nixos-auto-update = {
enable = true;
pullOnly = true;
};
Notable features:
modules/server/base.nix— OpenSSH (no passwords, no root), mosh, network tools- NVIDIA RTX 4070 eGPU via
nixosConfig.gpu.nvidia.enable = true modules/profiles/ollama.nixwith NVIDIA backend on port 11434docker-nvidia.nix— Docker with NVIDIA CDI for CUDA stacks (Immich ML, TTS, etc.)- Paseo daemon bound to loopback and connected through the same relay as vega
- Nebula mesh node with
.meshDNS resolution - Daily Docker stack report synchronized to Trilium
- Dedicated
/srv/modelsbtrfs subvolume for Ollama model storage - NFS/CIFS kernel support for NAS-backed Docker volumes
- Stack and services directories owned by the primary user under
/srv/stacksand/srv/services - Auto-update in pull-only mode
isServer = true— home-manager skips all desktop-only packages- No display manager, no Plasma, no GUI tools
The Specialisation Detection Mechanism
Electra writes the active tier name to /etc/nixos-specialisation at build time:
# Base: environment.etc."nixos-specialisation".text = "battery";
# igpu: environment.etc."nixos-specialisation".text = lib.mkForce "igpu";
# dgpu: environment.etc."nixos-specialisation".text = lib.mkForce "dgpu";
This file is read by nixos-rebuild-auto (a shell function in home/nimmo.nix) and by the nixos-auto-update service so they can reactivate the correct specialisation after a switch:
nixos-rebuild-auto() {
local CURRENT_SPEC=$(cat /etc/nixos-specialisation 2>/dev/null || echo "none")
sudo nixos-rebuild switch --flake /home/nimmo/nixos-config#$(hostname)
# nixos-rebuild switch always activates the base tier
# Re-apply the specialisation if one was active
if [ "$CURRENT_SPEC" != "none" ] && [ "$CURRENT_SPEC" != "battery" ] && \
[ -d "/run/current-system/specialisation/$CURRENT_SPEC" ]; then
sudo /run/current-system/specialisation/$CURRENT_SPEC/bin/switch-to-configuration switch
fi
}
lib.mkForce is needed on the specialisation values because environment.etc merges entries by key at equal priority — without mkForce, the base value and the specialisation value would conflict.
Design Pattern: Where Does Configuration Go?
| Question | Where it goes |
|---|---|
| Should every host have this? | modules/common/base.nix (or a module it imports) |
| Should every desktop host have this? | modules/desktop/environment.nix (or a module it imports) |
| Should every server host have this? | modules/server/base.nix |
| Should electra have this (all three tiers)? | hosts/electra/default.nix (outside specialisation blocks) |
| Should igpu and dgpu tiers have this (not base)? | Inside specialisation.igpu.configuration and specialisation.dgpu.configuration |
| Should only the dgpu tier have this? | Inside specialisation.dgpu.configuration only |
| Is it a self-contained feature that hosts opt into? | modules/<category>/ |
| Is it specific to one machine's hardware? | hosts/<hostname>/hardware*.nix |
| Should every user on every host have this software? | modules/common/system.nix |
| Should nimmo have this on every host? | home/nimmo.nix (via home-manager) |
| Should nimmo have this on desktop hosts only? | home/nimmo.nix behind if !isServer then [...] else [] |
Adding a New Host
1. Generate the hardware configuration
On the new machine:
nixos-generate-config --show-hardware-config > hardware-configuration.nix
2. Create the host directory and default.nix
# hosts/newhostname/default.nix
{ config, pkgs, inputs, ... }:
{
imports = [
../../modules/common/base.nix
# ../../modules/desktop/environment.nix # Add for desktop machines
# ../../modules/server/base.nix # Add for server/headless machines
./hardware-configuration.nix
../../modules/boot/silent-boot.nix
# ... add modules as needed ...
../../users/nimmo.nix
];
networking.hostName = "newhostname";
system.stateVersion = "25.11"; # Set to your NixOS version at first install; never change
}
3. Add it to flake.nix
newhostname = makeNixosSystem {
configName = "newhostname";
isServer = false; # or true for headless servers
# pkgs defaults to nixpkgs (unstable); override with nixpkgs-unstable-small for faster CI cache hits
};
4. Stage, check, deploy
git add hosts/newhostname/
nix flake check
sudo nixos-rebuild switch --flake .#newhostname