No description
  • Nix 67.2%
  • Just 18.6%
  • Shell 13.6%
  • Dockerfile 0.6%
Find a file
Nimmo 163361286c
All checks were successful
Build and Push Attic Cache / build (push) Successful in 1m33s
feat: Tune cache and host update cadence
2026-06-03 09:14:24 +01:00
.claude/commands
.forgejo/workflows
docker/nix-cache
home
hosts
justfiles
manual
modules
potential
scripts
secrets
users
.gitignore
.sops.yaml
CLAUDE.md
flake.lock
flake.nix
Justfile
README.md

NixOS Configuration

Multi-host NixOS configuration with modular structure.

Commands

Use just to manage your NixOS configuration. Run just or just --list to see all available recipes.

Common recipes:

  • just check — Validate flake syntax
  • just deploy — Deploy current configuration (auto-detects specialisation)
  • just deploy igpu — Deploy and activate a specific specialisation
  • just switch-spec igpu — Switch specialisation without rebuilding
  • just plasma-capture — Capture current KDE Plasma settings to home/plasma.nix
  • just diff-last-update — Show package changes from the last update
  • just generations — List system generations (before rollback)
  • just gc — Run garbage collection
  • just firmware-update — Update firmware via LVFS

Note: validation from a Windows workstation is currently a known workflow gap unless nix, WSL, or the container fallback is available locally. A supported Windows-friendly flake check path still needs to be standardised.

Timer management:

  • just restic-snapshots — List restic backups
  • just restic-unlock — Remove stale restic locks
  • just restic-logs — Show restic backup logs
  • just restic-last-run — Show last restic backup run details
  • just restic-run-now — Trigger restic backup immediately
  • just btrfs-snapshots — List local and second-NVMe btrfs snapshots
  • just btrfs-restore SNAPSHOT PATH — Restore one path from btrfs into /tmp
  • just restic-ls — Browse files in a restic snapshot
  • just restic-restore-path — Restore one path from restic into /tmp
  • just restic-restore — Restore latest snapshot (disaster recovery)
  • just nixos-gc-last-run — Show last GC run details
  • just nixos-gc-run-now — Trigger GC immediately
  • just auto-update-last-run — Show last auto-update run details
  • just auto-update-run-now — Trigger auto-update immediately

Directory Structure

nixos-config/
├── flake.nix                      # Flake definition (inputs & host configs)
├── flake.lock                     # Locked dependency versions
├── .sops.yaml                     # SOPS age key configuration for secrets
│
├── home/
│   ├── nimmo.nix                  # Home-manager config for nimmo (all hosts)
│   └── plasma.nix                 # Declarative KDE Plasma settings (via plasma-manager)
│
├── hosts/
│   ├── common/                    # Shared across ALL hosts
│   │   ├── default.nix            # Bootloader, networking, imports shared modules
│   │   ├── nix-settings.nix       # Flakes, build parallelism, caches
│   │   └── locale.nix             # Timezone, locale, keyboard layout
│   │
│   ├── electra/                   # Framework 16 laptop
│   │   ├── default.nix            # Unified config: base=battery, specialisations=igpu,dgpu
│   │   ├── hardware.nix           # Host-specific hardware imports (base)
│   │   ├── hardware-dgpu.nix      # NVIDIA/AMD Prime offload (dgpu specialisation)
│   │   └── framework-16.nix       # AMD GPU, fingerprint, PipeWire, zram, Framework tools
│   │
│   ├── lena/                      # Lenovo Ideapad 5 2-in-1 (Gen 9)
│   │   ├── default.nix            # Tablet mode, fingerprint, touchscreen
│   │   ├── disko.nix              # Btrfs partitioning layout
│   │   └── hardware-configuration.nix  # Auto-generated hardware config
│   │
│
├── modules/                       # Reusable feature modules
│   ├── common/
│   │   ├── base.nix               # Shared host baseline
│   │   ├── default-config.nix     # Portability options (user, email, paths)
│   │   ├── locale.nix             # UK locale, timezone, keyboard
│   │   ├── nix-settings.nix       # Nix daemon settings and binary caches
│   │   ├── system.nix             # Universal CLI/admin packages
│   │   └── sops-host.nix          # sops-nix key source (host SSH key)
│   ├── boot/
│   │   └── silent-boot.nix        # Plymouth splash, quiet kernel parameters
│   ├── desktop/
│   │   ├── environment.nix        # Desktop environment support modules/packages
│   │   ├── kde-plasma.nix         # KDE Plasma 6 desktop environment
│   │   ├── apps.nix               # Desktop applications (Kate, LibreOffice, VLC, etc.)
│   │   ├── fonts.nix              # Font packages and fontconfig defaults (desktop only)
│   │   └── goose.nix              # Goose AI agent — GUI + TUI, pre-built binaries (igpu/dgpu)
│   ├── hardware/
│   │   ├── bluetooth.nix          # Bluetooth with experimental features
│   │   ├── laptop.nix             # Laptop utilities and optional endurance mode
│   │   ├── printing.nix           # CUPS with Brother printer drivers, Avahi discovery
│   │   ├── removable-storage.nix  # udisks2, NTFS support, polkit mount rules
│   │   └── expansion-card-mount.nix  # 1TB expansion card automount (electra only)
│   ├── maintenance/
│   │   ├── garbage-collection.nix # Daily cleanup (3-day age limit)
│   │   └── btrfs-maintenance.nix  # Monthly btrfs scrub (all hosts)
│   ├── networking/
│   │   └── wifi-networks.nix      # WiFi network definitions (home + other known networks)
│   ├── power/
│   │   └── auto-power-profile.nix # Automatic power profile switching
│   ├── profiles/
│   │   ├── gui.nix                # Common graphical KDE Plasma environment
│   │   ├── laptop.nix             # GUI profile + laptop hardware/Wi-Fi/Bluetooth
│   │   ├── gaming.nix             # Steam, Gamemode, Discord, MangoHUD
│   │   ├── ai-agents.nix          # Claude Code, Codex CLI, nixd
│   │   ├── ai-desktop.nix         # AI desktop tools and OpenCode configuration
│   │   ├── ollama.nix             # Composable AMD/NVIDIA Ollama loadouts
│   │   └── maker.nix              # Cura, OpenSCAD (3D printing/CAD)
│   ├── server/
│   │   └── base.nix               # Server baseline: SSH and admin/network tools
│   ├── services/
│   │   ├── nixos-auto-update.nix  # Daily auto-update with rollback
│   │   ├── btrbk-home.nix         # Hourly btrfs snapshots of /home (electra)
│   │   ├── ollama-common.nix      # Shared ollama user/group definition
│   │   └── restic-backup.nix      # Automated backups every 6 hours (sops-nix secrets)
│   └── virtualization/
│       ├── docker.nix             # Base Docker with Btrfs
│       ├── docker-amd.nix         # Docker with AMD GPU (ROCm) support
│       └── docker-nvidia.nix      # Docker with NVIDIA Container Toolkit
│
├── packages/
│   └── system.nix                 # System-wide packages (all users, all hosts)
│
├── users/
│   ├── nimmo.nix                  # Admin user account definition
│   └── claire.nix                 # Standard user (lena only, no sudo)
│
├── secrets/
│   ├── secrets.yaml               # Encrypted secrets (sops-nix, age-encrypted)
│   └── README.md                  # Secrets management documentation
│
├── scripts/
│   └── check-flake.sh             # Container-based flake validation (podman fallback)
│
└── manual/                        # Operational notes and recovery docs
    ├── disaster-recovery.md       # Restore a machine after wipe/failure
    └── security-review.md         # Security posture report and follow-up actions

How It All Fits Together

Understanding the import chain is key to knowing where to make changes. Each host pulls in only what it needs:

flake.nix
  └── hosts/electra/default.nix             (or lena)
        ├── modules/common/base.nix          (shared across ALL hosts)
        │     ├── modules/common/nix-settings.nix
        │     ├── modules/common/locale.nix
        │     ├── modules/common/default-config.nix
        │     ├── modules/common/sops-host.nix
        │     ├── modules/maintenance/garbage-collection.nix
        │     └── modules/maintenance/btrfs-maintenance.nix
        ├── modules/profiles/laptop.nix
        │     ├── modules/profiles/gui.nix
        │     │     ├── modules/common/base.nix
        │     │     ├── modules/desktop/environment.nix
        │     │     ├── modules/desktop/kde-plasma.nix
        │     │     ├── modules/desktop/apps.nix
        │     │     ├── modules/networking/wireguard-vpn.nix
        │     │     ├── modules/profiles/ai-desktop.nix
        │     │     │     └── modules/profiles/ai-agents.nix
        │     │     ├── modules/profiles/maker.nix
        │     │     ├── modules/services/nixos-auto-update.nix
        │     │     └── users/nimmo.nix
        │     ├── modules/hardware/laptop.nix
        │     ├── modules/hardware/bluetooth.nix
        │     └── modules/networking/wifi-networks.nix
        ├── hardware-configuration.nix
        ├── hardware.nix                     (imports framework-16.nix)
        ├── framework-16.nix                 (AMD GPU, fingerprint, PipeWire, zram)
        ├── modules/hardware/expansion-card-mount.nix
        ├── modules/services/restic-backup.nix
        ├── modules/services/btrbk-home.nix
        ├── modules/profiles/ollama.nix
        ├── modules/virtualization/docker-amd.nix
        ├── specialisation.igpu.configuration:
        │     ├── modules/power/auto-power-profile.nix
        │     ├── modules/profiles/gaming.nix
        │     └── modules/profiles/maker.nix
        └── specialisation.dgpu.configuration:
              ├── hardware-dgpu.nix          (NVIDIA/AMD Prime offload)
              ├── modules/virtualization/docker-nvidia.nix
              ├── modules/virtualization/docker-amd.nix
              ├── modules/power/auto-power-profile.nix
              └── modules/profiles/gaming.nix

This means: if you want to change something for all hosts, edit modules/common/. If you want to change desktop-environment support, edit modules/desktop/environment.nix. If you want to change something for electra only, edit hosts/electra/default.nix. If you want to change something for the dgpu specialisation only, edit hosts/electra/default.nix under specialisation.dgpu.configuration.

Home-Manager Integration

Home-manager manages user-level configuration declaratively across all hosts. It's integrated directly into the NixOS configuration (not standalone).

What's Managed by Home-Manager

User Packages (via capability profiles and home-manager):

  • AI tools (Claude Code, Codex CLI, nixd) — via modules/profiles/ai-agents.nix
  • Gaming packages (Discord, MangoHUD) — via modules/profiles/gaming.nix on electra
  • 3D printing/CAD (Cura, OpenSCAD) — via modules/profiles/maker.nix
  • Desktop apps (Element, Trilium, etc.) — via modules/desktop/apps.nix
  • Conditional packages (only on unstable channel): opencode-desktop, whosthere

Shell Environment (programs.bash):

  • Bash-it framework with rjorgenson theme
  • Custom nixos-rebuild-auto function (rebuilds and reactivates current specialisation)
  • History search with up/down arrows
  • Shell initialization

Development Tools:

  • Git configuration (unified email across hosts)
  • Direnv with nix-direnv integration

System Monitoring:

  • btop with ROCm/libdrm wrapper for GPU monitoring
  • Shows gpu0 and gpu1 slots; btop silently omits any GPU it cannot detect

Firefox (programs.firefox):

  • Declarative default profile with privacy/security hardening
  • Extensions from NUR: Bitwarden, Consent-O-Matic
  • Full telemetry/tracking/study opt-out, HTTPS-only mode, Pocket disabled

Desktop Optimization:

  • Autostart applications with delays (Nextcloud delayed 15s)
  • Disabled applications (Discover update notifier)
  • Faster login performance

Configuration Files

  • home/nimmo.nix - Main home-manager configuration
  • ~/.bash_it - Symlinked from Nix store (managed by bash-it flake input)
  • Firefox extensions - Declarative via NUR (inputs.nur); manually-managed extensions: Floccus, Karakeep

KDE Plasma Configuration (plasma-manager)

KDE Plasma settings are declaratively managed via home/plasma.nix using the plasma-manager flake input. This file is generated by capturing the current Plasma state:

just plasma-capture

This captures themes, keybindings, window behaviour, panel configuration, and other Plasma settings into a Nix file that is applied on rebuild. To make Plasma changes:

  1. Adjust settings via the KDE System Settings GUI
  2. Run just plasma-capture to capture the changes
  3. Review the diff, rebuild, commit

What's NOT Managed by Home-Manager

  • Application-specific configurations (managed via GUI)
  • Runtime user data and state

Updating User Configuration

Edit home/nimmo.nix and rebuild:

# Edit configuration
vim home/nimmo.nix

# Apply changes
nixos-rebuild-auto

Changes to home-manager configuration take effect immediately on rebuild - no reboot required.

Common Tasks

Check Configuration Syntax

Validate the flake without building. Note: New untracked files must be staged with git add first, as Nix flakes only see files tracked by git.

git add <new-files>  # If you created new files
nix flake check

Test Configuration

Build and activate without adding to boot menu (reverts on reboot):

sudo nixos-rebuild test --flake .#electra

Deploy Configuration

Build, activate, and add to boot menu:

# Auto-detect hostname and reactivate current specialisation (recommended)
nixos-rebuild-auto

# Or specify explicitly
sudo nixos-rebuild switch --flake .#electra
sudo nixos-rebuild switch --flake .#lena

The nixos-rebuild-auto command runs nixos-rebuild switch using the current hostname as the flake target, then reactivates the current specialisation (if any). All three boot entries (base, igpu, dgpu) are always built together in a single rebuild. It is defined as a bash function in home/nimmo.nix.

Switch Mode (Electra Specialisations)

Select the desired boot entry from the systemd-boot menu at startup. The bootloader uses @saved so it remembers your last selection across reboots.

  • Base entry (electra-battery): AMD iGPU, vanilla kernel, locked power-saver, no gaming/maker profiles — default mode
  • Specialisation entry (electra-igpu): AMD iGPU only, stock kernel, dynamic power profile, gaming/maker profiles
  • Specialisation entry (electra-dgpu): NVIDIA + AMD hybrid, Zen kernel — use when the NVIDIA expansion bay is installed

Update All Packages

Forgejo Actions updates flake inputs and fills the Attic cache at 03:00 and 15:00 UTC. NixOS hosts check for pull-only updates every 2 hours with a randomised delay so they consume tested lock updates rather than producing them locally. Laptops below 20% battery while discharging warn that an update is available and defer until plugged in. To trigger a host pull-only update manually:

just auto-update-run-now       # Normal run (respects 6h freshness window)
sudo nixos-auto-update --force       # Force update regardless of lock age

To see what changed after an update:

just diff-last-update

Adding Packages

There are three places to add packages, depending on scope:

System Packages (available to all users, all hosts)

Edit modules/common/system.nix. These are available system-wide via environment.systemPackages:

environment.systemPackages = with pkgs; [
  # Add your package here
  neovim
];

User Packages (nimmo, all hosts)

Edit home/nimmo.nix. These are managed by home-manager and available to nimmo on every host:

home.packages = with pkgs; [
  # Add your package here
  spotify
];

Capability Profiles

modules/profiles/ bundles related system and user-level config into a single import:

Profile What it enables Enable option
gui.nix Common base + graphical KDE Plasma environment Import to enable
laptop.nix GUI profile + laptop hardware, Bluetooth, Wi-Fi Import to enable
gaming.nix Steam, Gamemode, Discord, MangoHUD Import to enable
ai-agents.nix Claude Code, Codex CLI, nixd Import to enable
ai-desktop.nix AI agents + OpenCode desktop configuration Import to enable
maker.nix Cura, OpenSCAD (3D printing/CAD) Import to enable

Add a profile to a host by importing it in that host's default.nix:

imports = [
  # ...
  ../../modules/profiles/gaming.nix
];

For packages that don't fit an existing profile, add them directly to that host's default.nix (e.g. hosts/electra/default.nix or hosts/lena/default.nix).

Desktop Applications (all desktop hosts)

Edit modules/desktop/apps.nix. These are system-wide packages for desktop/laptop use (Kate, LibreOffice, VLC, Okular, Nextcloud, Bitwarden, etc.). Both electra and lena import this module.

Finding Package Names

Search for packages:

nix search nixpkgs firefox
nix search nixpkgs --json firefox | jq  # Detailed output

Or browse https://search.nixos.org/packages

Adding External Flakes

1. Add the input to flake.nix

inputs = {
  # ... existing inputs ...

  # New flake
  some-tool.url = "github:owner/repo";
  some-tool.inputs.nixpkgs.follows = "nixpkgs";  # Use our nixpkgs
};

2. Pass it through specialArgs

Already configured - all inputs are passed via specialArgs = { inherit inputs; }.

3. Use the package

In any module that has inputs in its arguments:

{ pkgs, inputs, ... }:

{
  environment.systemPackages = [
    inputs.some-tool.packages.${pkgs.stdenv.hostPlatform.system}.default
  ];
}

4. Update the lock file

nix flake update some-tool

Adding a New Host

1. Create host directory

mkdir -p hosts/newhostname

2. Generate hardware config on the new machine

nixos-generate-config --show-hardware-config > hosts/newhostname/hardware-configuration.nix

3. Create host default.nix

{ config, pkgs, inputs, ... }:

{
  imports = [
    ../../modules/common/base.nix
    ./hardware-configuration.nix
    # Add modules as needed
    ../../modules/boot/silent-boot.nix
    ../../modules/desktop/environment.nix
    ../../modules/desktop/kde-plasma.nix
    ../../modules/desktop/apps.nix
    ../../users/nimmo.nix
  ];

  networking.hostName = "newhostname";

  system.stateVersion = "25.11";  # Set to your NixOS version
}

4. Add to flake.nix

nixosConfigurations = {
  electra = nixpkgs.lib.nixosSystem { ... };

  newhostname = nixpkgs.lib.nixosSystem {
    specialArgs = { inherit inputs; };
    modules = [ ./hosts/newhostname ];
  };
};

5. Deploy

# First deployment (explicit)
sudo nixos-rebuild switch --flake .#newhostname

# Subsequent deployments can use
nixos-rebuild-auto

Provisioning with nixos-anywhere

For a fresh machine, use nixos-anywhere to install NixOS and activate the configuration in one step. nixos-anywhere copies a provisioning age key onto the target before installation so sops-nix can decrypt secrets during first activation — before the SSH host key exists.

One-time setup (do this once, before provisioning any machine):

just setup-provision-key

This generates an age keypair, prints the private key once, registers the public key in .sops.yaml, and re-encrypts secrets. Save the private key to your password manager — it will not be shown again.

Per-machine provisioning steps:

  1. Scaffold the host and write its configuration (steps 14 in Adding a New Host above), including a disko.nix for disk layout — nixos-anywhere requires disko to partition the disk.

  2. Validate and commit the configuration before provisioning:

    just check
    git add -p && git commit -m "feat: Add HOSTNAME host configuration"
    git push
    
  3. Boot the target machine into a NixOS installer or rescue environment with SSH access, then provision:

    just provision HOSTNAME TARGET   # e.g. just provision nova 192.168.1.50
    

    When prompted, paste the provisioning age private key from your password manager. nixos-anywhere will partition the disk, install NixOS, and reboot the machine.

  4. After the machine reboots, register its SSH host key so it can decrypt secrets directly without the provisioning key:

    just add-secret-remote HOSTNAME TARGET
    
  5. Optionally remove the provisioning key from the machine (it lives under /root so is not user-accessible, but removing it reduces key material on disk):

    ssh root@TARGET 'rm /root/.config/sops/age/keys.txt'
    

Adding New Modules

Create a new file in modules/category/:

# modules/services/tailscale.nix
{ config, pkgs, ... }:

{
  services.tailscale.enable = true;
  environment.systemPackages = [ pkgs.tailscale ];
}

Then import it in the appropriate host config:

  • modules/common/base.nix - for all hosts
  • hosts/electra/default.nix - for electra (both modes)
  • hosts/electra/default.nix (under specialisation.dgpu.configuration) or hosts/lena/default.nix - for a single mode/host
imports = [
  # ...
  ../../modules/services/tailscale.nix
];

Rollback

Boot into previous generation

Select from boot menu at startup (systemd-boot shows all available generations).

Rollback from command line

# List generations
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

# Switch to specific generation
sudo nix-env --switch-generation 42 --profile /nix/var/nix/profiles/system
sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch

Garbage Collection

Automatic daily cleanup is configured in modules/maintenance/garbage-collection.nix:

  • Deletes generations older than 3 days
  • Runs daily with a randomized 1-hour delay to avoid contention

Additionally, nix-settings.nix configures automatic store GC to maintain 5-10GB free disk space.

Manual cleanup:

# Remove old generations (keeps last 7 days)
sudo nix-collect-garbage --delete-older-than 7d

# Remove ALL old generations (keep only current)
sudo nix-collect-garbage -d

Automated System Services

NixOS Auto-Update (All Hosts)

Defined in modules/services/nixos-auto-update.nix. Hosts run in pull-only mode every 2 hours with a 15-minute randomised delay. Forgejo Actions is the flake.lock producer and pushes accepted input updates after the Attic cache build succeeds.

Pull-only mode (all hosts):

  1. Skips if the repository has uncommitted changes (work in progress detected)
  2. Pulls latest config from remote
  3. On laptops below 20% battery while discharging, warns that an update is available and defers until a later check
  4. Validates the configuration with a dry-run build
  5. Builds the new configuration
  6. Switches to the new configuration and reactivates the current specialisation if applicable
  7. Desktop notifications at key points and on success/failure; warns separately if a reboot is needed for kernel changes

The flake.lock is updated either manually or automatically by the Forgejo Attic cache workflow. Channel inputs are accepted independently:

  • nixpkgs updates are accepted when lena and vega build successfully.
  • nixpkgs-unstable-small updates are accepted when electra builds successfully.

The service also supports full mode (not currently used) which applies the same per-channel validation, commits the accepted lock file changes with a summary of input changes, and pushes before switching.

Monitor the service:

journalctl -u nixos-auto-update.service -f

Trigger or inspect manually:

just auto-update-run-now   # Trigger immediately (normal run)
just auto-update-last-run  # Show last run status
sudo nixos-auto-update --force   # Force flake update regardless of lock age
just diff-last-update            # Show package changes from the last update

Restic Backup (Electra and Lena)

Defined in modules/services/restic-backup.nix. Automated backups of /home/nimmo:

  • Runs when connecting to home WiFi or WireGuard, debounced to at most once every 4 hours
  • Has a daily fallback timer with a 15-minute randomized delay
  • Credentials managed via sops-nix (encrypted in secrets/secrets.yaml, decrypted to /run/secrets/ at runtime)
  • Excludes configured via /etc/restic/excludes.txt (created empty; edit to customise)

Monitor backups:

journalctl -u restic-backup.service -f

Restore Process

Electra has three recovery tiers for /home data:

  1. Main-drive btrfs snapshots at /home/.btrbk-snapshots
  2. Replicated btrfs snapshots on the second NVMe at /run/media/snapshots
  3. Network restic backups of /home/nimmo

Use the closest good copy first. For accidental deletion or a bad edit, start with btrfs snapshots because they are local and fast:

just btrfs-snapshots
just btrfs-restore home.20260429T1218 /home/nimmo/Documents/example.txt

The restore command copies into /tmp/restore-<snapshot>/... and refuses to overwrite existing files. Inspect the restored file, then copy it back manually when you are sure it is the version you want.

If both local drives are unavailable, or the needed version is only in the network backup, browse and restore from restic:

just restic-snapshots
just restic-ls latest /home/nimmo/Documents
just restic-restore-path latest /home/nimmo/Documents/example.txt

Restic restores under /tmp/restic-restore-<snapshot>/home/nimmo/... by default, again without touching the live home directory.

Disaster Recovery (Restic Restore)

After a catastrophic failure, once a fresh NixOS install has been provisioned and the configuration deployed:

just restic-restore

This reads credentials from /run/secrets/ (populated by sops-nix at boot), shows the latest snapshot for confirmation, then restores all backed-up files to their original paths with restic restore latest --target / --overwrite always.

Prerequisites before running:

  1. NixOS installed and nixos-rebuild switch completed (sops-nix must have activated)
  2. Network access to the restic repository

Silent Boot (All hosts)

Defined in modules/boot/silent-boot.nix. Plymouth splash screen with quiet kernel parameters for a clean boot experience.

Auto Power Profile (Electra igpu/dgpu and Lena)

Defined in modules/power/auto-power-profile.nix. Triggered by udev on AC plug/unplug events and switches profiles:

  • Charging/Full + >25% battery: performance mode
  • Charging/Full + <=25% battery: balanced mode
  • Discharging + >50% battery: balanced mode
  • Discharging + <=50% battery: power-saver mode

Monitor with:

journalctl -u auto-power-profile.service -f

Current Hosts

Host Machine Channel Key Differences
electra Framework 16 unstable-small Base: endurance mode (vanilla kernel, locked power-saver, iGPU runtime PM, maker). Specialisation igpu: dynamic power profile, gaming/maker. Specialisation dgpu: Zen kernel, NVIDIA/AMD hybrid, dual Ollama, Docker
lena Lenovo Ideapad 5 2-in-1 unstable Tablet mode, touchscreen, Disko partitioning, auto-update (pull-only), no Ollama/gaming

Shared Across All Hosts (via modules/common/base.nix)

  • Systemd-boot, NetworkManager
  • Garbage collection (daily, 3-day age limit)
  • btrfs scrub (monthly, all btrfs mounts)
  • Portability options (modules/common/default-config.nix)
  • sops-nix host key configuration
  • UK locale (en_GB.UTF-8, Europe/London)

Desktop environment support (electra, lena — via modules/desktop/environment.nix):

  • Fonts (Noto, Fira Code, JetBrains Mono Nerd Font)
  • Printing (CUPS with Brother drivers, Avahi network discovery)
  • Removable storage (udisks2, NTFS support, polkit mount rules)
  • KDE Plasma 6 (login manager is host-specific: plasma-login-manager on electra, SDDM on lena)
  • Silent boot (Plymouth)
  • Auto power profile switching (udev-triggered)
  • Bluetooth
  • Desktop apps (modules/desktop/apps.nix)
  • Universal system packages (modules/common/system.nix)

Electra (All Modes)

  • Framework 16 hardware module (AMD GTT 32GB, fingerprint, PipeWire, zram, ROCm)
  • Expansion card automount (1TB NTFS at /run/media/nimmo/Expansion1TB)
  • AI desktop profile (modules/profiles/ai-desktop.nix) plus Ollama service: OpenCode, AI user tools, Ollama + Open WebUI
  • Maker profile (modules/profiles/maker.nix): Cura, OpenSCAD
  • NixOS auto-update (daily)
  • ROCm HSA override for AMD 780M iGPU (HSA_OVERRIDE_GFX_VERSION=11.0.0)
  • Docker with AMD 780M iGPU access

Electra Base (battery)

  • Linux vanilla kernel (mainline, no latency tuning)
  • Laptop endurance mode enabled through modules/hardware/laptop.nix (laptop.enduranceMode.enable = true)
  • iGPU runtime power management (amdgpu.runpm=1)
  • Gaming profile disabled

Electra igpu Specialisation Only

  • Dynamic power profile switching (modules/power/auto-power-profile.nix)
  • Gaming profile (modules/profiles/gaming.nix): Steam, Gamemode, Discord, MangoHUD
  • Maker profile (modules/profiles/maker.nix): Cura, OpenSCAD

Electra dgpu Specialisation Only

  • Linux Zen kernel
  • Dynamic power profile switching (modules/power/auto-power-profile.nix)
  • Gaming profile (modules/profiles/gaming.nix): Steam, Gamemode, Discord, MangoHUD
  • NVIDIA/AMD Prime offload drivers
  • NVIDIA Ollama instance (port 11435, CUDA acceleration)
  • Docker with NVIDIA Container Toolkit
  • nvtop for GPU monitoring

Lena-Specific

  • Tablet mode via iio-sensor-proxy (automatic screen rotation)
  • Fingerprint reader via fprintd
  • Btrfs with Disko-managed partitioning (subvolumes: @, @home, @nix, @log)
  • Both nimmo (admin) and claire (standard user) accounts
  • AI desktop profile (modules/profiles/ai-desktop.nix): Claude Code, Codex CLI, OpenCode
  • Maker profile (modules/profiles/maker.nix): Cura, OpenSCAD
  • NixOS 25.11 stable channel

User Accounts

nimmo (All hosts)

  • Type: Administrator account
  • Groups: wheel (sudo), networkmanager, video, audio, render, docker (all electra modes)
  • Shell: bash with bash-it framework (rjorgenson theme)
  • Configuration: Managed by home-manager in home/nimmo.nix
  • Packages: Via capability profiles (modules/profiles/); AI tools, gaming, and maker packages are per-host based on which profiles are imported
  • Features:
    • Git configuration unified across all hosts (nimmo@nimmog.uk)
    • Direnv with nix-direnv for automatic environment loading
    • Custom nixos-rebuild-auto function (rebuilds and reactivates current specialisation)
    • Optimized autostart: Nextcloud delayed 15 seconds, Discover disabled for faster login

claire (Lena only)

  • Type: Standard user account
  • Groups: networkmanager, video, audio (no wheel/sudo)
  • Purpose: Guest/family account with restricted privileges
  • Packages: System packages only

Ollama with Open WebUI

Electra runs Ollama for local LLM inference with Open WebUI as the web interface, using native NixOS services.

Architecture

Base / both modes (modules/profiles/ollama.nix): AMD Ollama instance + Open WebUI (configured by profiles.ollama.amd)

  • AMD iGPU Instance (port 11434): For large models with 48GB shared RAM
    • Context window: 65,536 tokens
    • Vulkan acceleration
    • Keep-alive: 24 hours
  • Open WebUI (localhost:3000): Web interface for model interaction

dgpu specialisation (profiles.ollama.nvidia.enable = true): Adds a second NVIDIA instance

  • NVIDIA dGPU Instance (port 11435): For fast inference with 8GB VRAM
    • Context window: 8,192 tokens
    • CUDA acceleration
    • Optimized for quick chats and coding completion
  • Open WebUI is configured to connect to both instances

Both instances share a models directory at /run/media/models (btrfs subvolume), so models downloaded on one instance are available to the other.

Usage

Access the web interface at http://localhost:3000

Direct API access:

# AMD instance (electra, all tiers)
curl http://localhost:11434/api/generate -d '{"model":"llama3.2","prompt":"Hello"}'

# NVIDIA instance (electra-dgpu only)
curl http://localhost:11435/api/generate -d '{"model":"llama3.2","prompt":"Hello"}'

View logs:

journalctl -u ollama.service -f          # AMD instance
journalctl -u ollama-nvidia.service -f   # NVIDIA instance (electra-dgpu only)
journalctl -u open-webui.service -f      # Web interface

Hardware Acceleration

dgpu specialisation:

  • AMD instance uses Vulkan with GTT extended to 32GB (amdgpu.gtt_size=32768)
  • NVIDIA instance uses CUDA
  • Both instances run simultaneously, allowing you to choose the best hardware for each task

Base (iGPU mode):

  • Uses Vulkan with GTT extended to 32GB
  • HSA override for RDNA3 780M compatibility (HSA_OVERRIDE_GFX_VERSION=11.0.0)

Model Recommendations

For NVIDIA dGPU (8GB VRAM) - Fast inference:

  • Llama 3.2 3B (very fast, lightweight)
  • Qwen 2.5 Coder 7B (excellent for code)
  • Llama 3.1 8B (balanced performance)
  • Mistral 7B (general purpose)

For AMD iGPU with 48GB shared RAM - Large models:

  • Qwen 2.5 72B (high capability)
  • CodeLlama 70B (specialized for code)
  • QwQ 32B (reasoning focused)
  • Llama 3.1 70B (general purpose)
  • DeepSeek Coder 33B (advanced code generation)

Data Persistence

/run/media/models/        # Shared models directory (btrfs @models subvolume)
/var/lib/ollama/          # AMD instance state
/var/lib/ollama-nvidia/   # NVIDIA instance state (dgpu specialisation only)
/var/lib/open-webui/      # Open WebUI data (chat history, settings)

Flake Inputs

Input Purpose
nixpkgs Default NixOS unstable channel
nixpkgs-unstable-small NixOS unstable-small channel (electra)
nixpkgs-bitwarden Pinned nixpkgs for bitwarden-desktop (electron 39 workaround)
nixpkgs-stable NixOS 25.11 package import escape hatch
home-manager User environment management (unstable)
bash-it Bash framework for themes and plugins
claude-code-nix Claude Code AI assistant
codex-cli-nix Codex CLI (OpenAI)
nixos-hardware Hardware-optimized configs (Lenovo Ideapad, Framework)
disko Declarative disk partitioning
nur Nix User Repository (Firefox extensions)
sops-nix Encrypted secrets management (age-based)
plasma-manager Declarative KDE Plasma configuration via home-manager
framework-system Official Framework hardware tool (upstream flake)

Learning More

The manual/ directory contains a 13-chapter learning manual designed to take you from relying on AI for config changes to understanding and confidently modifying the configuration yourself. Start with manual/README.md for the chapter index. Practical chapters come first; theory chapters come later.

  • Need to do a specific task right now? Jump to Chapter 2 (Workflows) or Chapter 8 (Quick Reference).
  • Want to understand how this repo is organised? Start at Chapter 1 (Configuration Structure).
  • Something broke? Go to Chapter 7 (Troubleshooting).
  • New to Nix entirely? Start at Chapter 9 (Nix Language) and work through Part 2.