Table of Contents
- Chapter 3: Host Configurations
- The Hosts
- All Hosts Share One Foundation
- Electra (Framework 16)
- Base (battery) tier
- igpu specialisation
- dgpu specialisation
- Kernel selection summary
- Hardware files
- Host-specific Ollama providers
- Lena (Lenovo 2-in-1)
- Vega (Home 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 | Home inference server, backups, monitoring |
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, 3-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 bootloader (systemd-boot with @saved default to remember last selection), pcscd for YubiKey support, and NetworkManager with captive portal detection disabled.
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) 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 = [
../../modules/common/base.nix
../../modules/desktop/environment.nix
./hardware-configuration.nix
../../modules/hardware/laptop.nix # upower, powertop, endurance mode option
../../modules/boot/silent-boot.nix
../../modules/desktop/plasma.nix
../../modules/hardware/bluetooth.nix
../../modules/hardware/expansion-card-mount.nix
../../modules/power/auto-power-profile.nix
../../modules/services/btrbk-home.nix # Hourly /home snapshots
../../modules/services/nixos-auto-update.nix
../../modules/profiles/ollama.nix # Ollama on AMD iGPU (port 11434)
../../modules/networking/wifi-networks.nix
../../modules/networking/wireguard-vpn.nix
../../modules/profiles/ai-desktop.nix # AI tools + opencode + desktop launchers
../../modules/profiles/maker.nix
../../modules/desktop/apps.nix
../../users/nimmo.nix
./hardware.nix # AMD iGPU configuration
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
../../modules/virtualization/docker-amd.nix
];
networking.hostName = "electra";
boot.kernelPackages = pkgs.linuxPackages_7_0;
boot.initrd.systemd.enable = true; # Required for TPM2 unlock
# 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 gaming and Goose:
specialisation.igpu.configuration = {
environment.etc."nixos-specialisation".text = lib.mkForce "igpu";
system.nixos.label = lib.mkForce "igpu";
boot.kernelPackages = lib.mkForce pkgs.linuxPackages_7_0; # Linux 7.0 for AMD GPU improvements
power.autoPowerProfile.strategy = lib.mkForce "dynamic";
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 strategy on the same Linux 7.0 kernel series
- Dynamic power profile (switches between power-saver/balanced/performance based on AC state and battery thresholds)
- Full gaming stack
- Goose desktop app
dgpu specialisation
The dgpu tier adds NVIDIA drivers, a second Ollama instance (on the dGPU), and NVIDIA Docker support:
specialisation.dgpu.configuration = {
environment.etc."nixos-specialisation".text = lib.mkForce "dgpu";
system.nixos.label = lib.mkForce "dgpu";
boot.kernelPackages = lib.mkForce pkgs.linuxPackages_7_0;
imports = sharedSpecialisationImports ++ [
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series-nvidia
./hardware-dgpu.nix # NVIDIA drivers, Prime offload
../../modules/virtualization/docker-nvidia.nix # NVIDIA dGPU access
../../modules/virtualization/docker-amd.nix # AMD 780M iGPU access (also present)
];
profiles.ollama.nvidia.enable = true;
power.autoPowerProfile.strategy = lib.mkForce "dynamic";
environment.systemPackages = with pkgs; [ nvtopPackages.full ];
# framework-tool with nvidia feature enabled
nixpkgs.overlays = [
(final: prev: {
framework-tool = (inputs.framework-system.packages.${prev.stdenv.hostPlatform.system}.default).overrideAttrs (old: {
buildFeatures = (old.buildFeatures or []) ++ [ "nvidia" ];
});
})
];
};
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 pattern in the actual code avoids repeating the common igpu/dgpu imports.
Kernel selection summary
| Tier | Kernel | Why |
|---|---|---|
| Base (battery) | linuxPackages_7_0 |
Current Electra kernel series with endurance power strategy |
| igpu | linuxPackages_7_0 |
Better AMD GPU driver support |
| dgpu | linuxPackages_7_0 |
NVIDIA/AMD hybrid using the same current Electra kernel series |
Hardware files
hardware.nix — imported by the base, configures AMD iGPU specifics (GTT memory extension, ROCm packages, PipeWire, zram swap, fingerprint).
hardware-dgpu.nix — imported 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/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 tools, Docker, 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-agents.nix # Claude Code, Codex CLI, MCP plugins
../../modules/virtualization/docker.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" ];
services.nixos-auto-update = {
enable = true;
pullOnly = true;
};
Notable features:
modules/server/base.nix— OpenSSH (no passwords, no root), mosh, network toolsmodules/profiles/ai-agents.nix— Claude Code + Codex CLI + nixd LSP + Claude Code MCP plugin (nixos), without desktop launchers- Docker (basic, no GPU access needed)
- Two storage mounts on a separate persistent SSD (
/mnt/storage,/mnt/partial) - 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
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 {
pkgs = nixpkgs;
hmFlake = home-manager;
configName = "newhostname";
isServer = false; # or true for headless servers
};
4. Stage, check, deploy
git add hosts/newhostname/
nix flake check
sudo nixos-rebuild switch --flake .#newhostname