Skip to content
IT related

Nixos Refactor documentation

Updated 6 min read

NixOS Flake Configuration - Refactoring Documentation

Overview

This documentation covers the comprehensive refactoring of your NixOS flake configuration from a duplicated, user-specific
setup to a modular, multi-user, multi-system architecture.


What Was Changed and Why

  1. Feature-Based Module System (modules/features/)

Before: Each host configuration contained 300-400 lines of duplicated code with hardcoded features.

After: Created 9 reusable feature modules:

  • gaming.nix - Steam, Gamemode
  • nvidia.nix - NVIDIA GPU support with container toolkit
  • virtualisation.nix - Docker, libvirt, QEMU
  • desktop.nix - Hyprland, GNOME, GDM configuration
  • printing.nix - CUPS with optional Epson drivers
  • bluetooth.nix - Bluetooth support
  • laptop.nix - Power management, fingerprint, YubiKey
  • ai.nix - Ollama with GPU acceleration
  • services.nix - User-specific systemd services

Why: Enable/disable features with simple flags instead of duplicating configuration. Each module is self-contained and
conditionally enabled.

Location: /home/ed/Flake/modules/features/


  1. Host Profiles (hosts/profiles/)

Before: Each host duplicated the same base configuration.

After: Created reusable profiles:

  • desktop.nix - Desktop workstation profile (gaming, nvidia, virtualisation, no suspend)
  • laptop.nix - Laptop profile (power management, printing, fingerprint auth)

Why: Define common configurations for device types, then customize per-host.

Location: /home/ed/Flake/hosts/profiles/


  1. Centralized Shared Configuration (hosts/common.nix)

Before: Empty/unused file, all config duplicated in each host.

After: Consolidated shared configuration:

  • All feature module imports
  • User management import
  • System packages
  • Networking base configuration
  • Audio (PipeWire)
  • Boot loader defaults
  • OpenSSH

Why: DRY (Don't Repeat Yourself) - configure once, apply everywhere.

Location: /home/ed/Flake/hosts/common.nix


  1. Simplified Host Configurations

Before:

  • fullmetal-desktop/configuration.nix: ~350 lines
  • fullmetal-lappy/configuration.nix: ~400 lines

After:

  • fullmetal-desktop/configuration.nix: ~65 lines
  • fullmetal-lappy/configuration.nix: ~67 lines

Why: Each host now only contains:

  • Hardware configuration import
  • Profile import
  • Feature flags (enable/disable)
  • Host-specific configuration (e.g., NAS mounts)

Structure:
{
imports = [
./hardware-configuration.nix
../common.nix
../profiles/desktop.nix # or laptop.nix
];

features = {
  nvidia.enable = true;
  gaming.enable = true;
  # ... other features
};

# Host-specific config only

}


  1. Declarative User Management (users/)

Before: Users hardcoded in each host configuration with duplicated code.

After: Centralized user definitions in users/users.nix:

userSettings = {
ed = {
username = "ed";
name = "FullMetal";
homeDirectory = "/home/ed";
hosts = [ "fullmetal-desktop" "fullmetal-lappy" ]; # Which hosts this user is on
hashedPasswordSecret = "users/fullmetal/hashedPassword";
groups = [ "wheel" "docker" "libvirtd" /* ... */ ];

  # Home-manager config
  enableHomeManager = true;
  homeConfig = ../home/home.nix;
  desktopEnvironment = "hyprland";

  # Feature flags
  enableSops = true;
  enableNvf = true;
  enableAdvancedPrograms = true;

  # Secrets
  secretsPath = "users/fullmetal";
  secretsList = [ "email" "githubpat" /* ... */ ];
};

sophia = {
  # Similar structure
  hosts = [ "fullmetal-desktop" ];  # Only on desktop
  desktopEnvironment = "gnome";
  enableAdvancedPrograms = false;  # Simplified setup
};

};

Why:

  • Define users once, assign to hosts declaratively
  • Automatic user creation, password management (SOPS), and home-manager setup
  • Per-user feature flags for different experience levels

Location: /home/ed/Flake/users/users.nix


  1. Universal Home-Manager Configuration (home/)

Before:

  • Separate home.nix for ed
  • Separate home-sophia.nix for sophia
  • Hard-coded paths (/home/ed/...)
  • User-specific parameters everywhere

After:

  • Single home/home.nix for all users
  • User-agnostic modules that read from config.userSettings
  • Dynamic paths using ${config.home.homeDirectory}
  • Conditional logic based on user feature flags

Structure:
home/
├── home.nix # Universal entry point
├── base/
│ ├── packages.nix # Conditional on enableAdvancedPrograms
│ ├── programs.nix # Bash, git, starship (conditional)
│ ├── sops.nix # Secrets (conditional on enableSops)
│ ├── nvf.nix # Neovim (conditional on enableNvf)
│ └── stylix.nix # Theming
├── desktops/
│ ├── default.nix # Auto-loader
│ ├── hyprland/ # Conditional on desktopEnvironment == "hyprland"
│ └── gnome/ # Conditional on desktopEnvironment == "gnome"
└── hosts/
├── default.nix # Auto-loader
├── fullmetal-desktop.nix
└── fullmetal-lappy.nix

Why:

  • Single source of truth
  • Easy to maintain
  • Users automatically get appropriate configuration based on their feature flags

  1. SOPS Integration

System-level (users/user-management.nix):

  • Uses SSH host key (/etc/ssh/ssh_host_ed25519_key)
  • Manages user password hashes
  • Automatically creates secrets for all users on host

Home-level (home/base/sops.nix):

  • User-specific secrets (API keys, tokens, SSH keys)
  • Conditional on enableSops flag
  • Dynamically generates secrets from secretsList

Why: Secure secret management without storing plaintext in git.


How to Add New Features

  1. Create a new feature module in modules/features/:

modules/features/myfeature.nix

{ lib, config, pkgs, ... }:

{
options.features.myfeature = {
enable = lib.mkEnableOption "my awesome feature";
# Add more options as needed
someOption = lib.mkOption {
type = lib.types.str;
default = "default-value";
description = "Some option for my feature";
};
};

config = lib.mkIf config.features.myfeature.enable {
  # Your configuration here
  environment.systemPackages = with pkgs; [
    # packages
  ];

  services.myservice = {
    enable = true;
    # ...
  };
};

}

  1. Import the module in hosts/common.nix:

imports = [
../modules/features/myfeature.nix
# ... other imports
];

  1. Enable it in host configs as needed:

hosts/fullmetal-desktop/configuration.nix

features.myfeature = {
enable = true;
someOption = "custom-value";
};


How to Add New Users

  1. Add user definition to users/users.nix:

userSettings = {
# ... existing users

newuser = {
  # Basic info
  username = "newuser";
  name = "New User";
  homeDirectory = "/home/newuser";
  gitHubUser = "newuser-github";  # optional
  groups = [ "wheel" ];  # Add appropriate groups

  # Host assignment - which systems should this user be on?
  hosts = [ "fullmetal-desktop" ];

  # Password secret path
  hashedPasswordSecret = "users/newuser/hashedPassword";

  # Home-manager setup
  enableHomeManager = true;
  homeConfig = ../home/home.nix;
  desktopEnvironment = "hyprland";  # or "gnome"

  # Feature flags (customize experience)
  enableSops = false;  # true if user needs secrets
  enableNvf = true;    # Enable custom Neovim
  enableAdvancedPrograms = true;  # Advanced bash config, tools

  # Secrets (if enableSops = true)
  secretsPath = "users/newuser";
  secretsList = [ "email" "githubpat" ];
};

};

  1. Add password to SOPS if using secrets:

Edit the secrets file

cd /home/ed/Flake
sops sops/secrets.yaml

Add under users:

users:

newuser:

hashedPassword:

Generate hashed password:
mkpasswd -m yescrypt

  1. Rebuild - the user will be automatically created:

sudo nixos-rebuild switch --flake .


How to Add New Hosts/Systems

  1. Create Hardware Configuration

On the new system, generate hardware config

nixos-generate-config --show-hardware-config > /tmp/hardware-configuration.nix

Copy this to hosts/newhostname/hardware-configuration.nix

  1. Create Host Configuration

hosts/newhostname/configuration.nix

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

{
imports = [
./hardware-configuration.nix
../common.nix
../profiles/desktop.nix # or laptop.nix, or create custom
];

# Hostname
networking.hostName = "newhostname";

# Feature flags
features = {
  gaming.enable = true;
  nvidia.enable = false;
  virtualisation.enable = true;
  desktop = {
    hyprland.enable = true;
    gnome.enable = false;
    autoSuspend = false;
  };
  # ... other features
};

# Host-specific configuration
# (monitors, custom services, mounts, etc.)

}

  1. Add to Flake

flake.nix

outputs = { ... }: {
nixosConfigurations = {
# ... existing hosts

  newhostname = mkHost {
    hostname = "newhostname";
    system = "x86_64-linux";  # or aarch64-linux, etc.
  };
};

};

  1. Assign Users to Host

Edit users/users.nix and add the hostname to users' hosts list:

ed = {
# ...
hosts = [ "fullmetal-desktop" "fullmetal-lappy" "newhostname" ];
};

  1. Build and Deploy

Build the configuration

nixos-rebuild build --flake .#newhostname

On the new system, switch to it

sudo nixos-rebuild switch --flake .#newhostname


Architecture Diagram

flake.nix
├── nixosConfigurations
│ ├── fullmetal-desktop ──┐
│ └── fullmetal-lappy ────┤
│ │
├── modules/features/ ◄──────┤
│ ├── gaming.nix │
│ ├── nvidia.nix │
│ ├── virtualisation.nix │
│ ├── desktop.nix │
│ ├── laptop.nix │
│ └── ... │
│ │
├── hosts/ │
│ ├── common.nix ◄─────────┤ (imports all features)
│ ├── profiles/ │
│ │ ├── desktop.nix │
│ │ └── laptop.nix │
│ ├── fullmetal-desktop/ │
│ │ └── configuration.nix◄┘
│ └── fullmetal-lappy/
│ └── configuration.nix
│
├── users/
│ ├── users.nix ──────────► Declarative user definitions
│ └── user-management.nix ► Auto-wires users to hosts
│
└── home/
├── home.nix ───────────► Universal entry point
├── base/ ──────────────► User-agnostic modules
├── desktops/ ──────────► DE-specific configs
└── hosts/ ─────────────► Host-specific home configs


Key Principles

  1. Conditional Everything - Use lib.mkIf to enable/disable based on options
  2. User-Agnostic - Modules read from config.userSettings, never hardcode users
  3. DRY - Define once, reuse everywhere
  4. Feature Flags - Toggle features per-host and per-user
  5. Declarative Users - Define users centrally, assign to hosts with hosts = [ ... ]
  6. Single Home Config - One home.nix serves all users with conditional logic

Common Tasks

Change a User's Desktop Environment

Edit users/users.nix:
ed = {
# ...
desktopEnvironment = "gnome"; # was "hyprland"
};

Add a Package to All Systems

Edit hosts/common.nix:
environment.systemPackages = with pkgs; [
# ... existing packages
newpackage
];

Add a Package to Specific User

Edit home/base/packages.nix and add conditional logic:
home.packages = with pkgs; [
# ... existing
] ++ lib.optionals (currentUser.username == "ed") [
special-package
];

Disable a Feature on One Host

Edit host configuration:
features.gaming.enable = false;


File Reference

File Purpose
flake.nix Entry point, defines all systems
modules/features/*.nix Reusable feature modules
hosts/common.nix Shared configuration for all hosts
hosts/profiles/*.nix Reusable host profiles
hosts/*/configuration.nix Per-host configuration
users/users.nix Declarative user definitions
users/user-management.nix Auto-wires users to hosts
home/home.nix Universal home-manager entry
home/base/*.nix User-agnostic home modules
home/desktops/*/ Desktop environment configs
sops/secrets.yaml Encrypted secrets

IT related RSS feed Published with Oblish

Keyboard shortcuts

/
Search
[
Previous page
]
Next page
?
Show this list
Esc
Close