9 03 Host Configurations
Nimmo edited this page 2026-07-01 19:41:48 +01:00

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.nix for declarative disk partitioning
  • lenovo-ideapad-16ahp9 nixos-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-desktop profile (same as electra — CLI tools + opencode, but no local Ollama providers configured)
  • WireGuard VPN client enabled (same wireguard-vpn.nix module 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 tools
  • modules/profiles/ai-desktop.nix with headless = 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 .mesh names
  • Two storage mounts on a separate persistent SSD (/mnt/storage, /mnt/partial)
  • Service data under /srv/services and 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.nix with NVIDIA backend on port 11434
  • docker-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 .mesh DNS resolution
  • Daily Docker stack report synchronized to Trilium
  • Dedicated /srv/models btrfs 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/stacks and /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