- Nix 67.2%
- Just 18.6%
- Shell 13.6%
- Dockerfile 0.6%
|
|
||
|---|---|---|
| .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 syntaxjust deploy— Deploy current configuration (auto-detects specialisation)just deploy igpu— Deploy and activate a specific specialisationjust switch-spec igpu— Switch specialisation without rebuildingjust plasma-capture— Capture current KDE Plasma settings to home/plasma.nixjust diff-last-update— Show package changes from the last updatejust generations— List system generations (before rollback)just gc— Run garbage collectionjust 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 backupsjust restic-unlock— Remove stale restic locksjust restic-logs— Show restic backup logsjust restic-last-run— Show last restic backup run detailsjust restic-run-now— Trigger restic backup immediatelyjust btrfs-snapshots— List local and second-NVMe btrfs snapshotsjust btrfs-restore SNAPSHOT PATH— Restore one path from btrfs into/tmpjust restic-ls— Browse files in a restic snapshotjust restic-restore-path— Restore one path from restic into/tmpjust restic-restore— Restore latest snapshot (disaster recovery)just nixos-gc-last-run— Show last GC run detailsjust nixos-gc-run-now— Trigger GC immediatelyjust auto-update-last-run— Show last auto-update run detailsjust 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.nixon 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-autofunction (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:
- Adjust settings via the KDE System Settings GUI
- Run
just plasma-captureto capture the changes - 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:
-
Scaffold the host and write its configuration (steps 1–4 in Adding a New Host above), including a
disko.nixfor disk layout — nixos-anywhere requires disko to partition the disk. -
Validate and commit the configuration before provisioning:
just check git add -p && git commit -m "feat: Add HOSTNAME host configuration" git push -
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.50When prompted, paste the provisioning age private key from your password manager. nixos-anywhere will partition the disk, install NixOS, and reboot the machine.
-
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 -
Optionally remove the provisioning key from the machine (it lives under
/rootso 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 hostshosts/electra/default.nix- for electra (both modes)hosts/electra/default.nix(underspecialisation.dgpu.configuration) orhosts/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):
- Skips if the repository has uncommitted changes (work in progress detected)
- Pulls latest config from remote
- On laptops below 20% battery while discharging, warns that an update is available and defers until a later check
- Validates the configuration with a dry-run build
- Builds the new configuration
- Switches to the new configuration and reactivates the current specialisation if applicable
- 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:
nixpkgsupdates are accepted whenlenaandvegabuild successfully.nixpkgs-unstable-smallupdates are accepted whenelectrabuilds 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:
- Main-drive btrfs snapshots at
/home/.btrbk-snapshots - Replicated btrfs snapshots on the second NVMe at
/run/media/snapshots - 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:
- NixOS installed and
nixos-rebuild switchcompleted (sops-nix must have activated) - 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-autofunction (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.