Files
nix-los/ARCHITECTURE.md
2026-04-07 02:34:03 +02:00

12 KiB

Architecture & Design Principles

An overview of how your portable NixOS configuration is structured and why.

Core Design Goals

  1. Portability: Deploy to new machines, existing NixOS, or non-NixOS with minimal effort
  2. Modularity: Enable/disable features independently without tight coupling
  3. Reproducibility: Flakes + lock files ensure identical environments across machines
  4. Maintainability: Clear separation of concerns (system vs home, shared vs per-machine)
  5. Secrets Management: Encrypted, portable secrets with sops-nix
  6. Scalability: Easy to add new machines and modules

Architecture Overview

nix-los/
├── flake.nix              # Single source of truth for all configurations
│                          # Defines: inputs, outputs, configurations
│
├── hosts/                 # Per-machine specific overrides
│   ├── laptop/           # Machine-specific hardware, features
│   └── server/           # (template for new machines)
│
├── nixos/                 # System-level modules (NixOS only)
│   ├── default.nix       # Imports all modules
│   └── modules/          # Toggleable features
│       ├── system.nix    # Users, sudo, locale
│       ├── development.nix  # Languages, tools
│       ├── shell.nix     # Shell config
│       └── secrets-example.nix (inactive)
│
├── home/                  # User-level configuration (portable)
│   ├── default.nix       # Imports all modules, can run standalone
│   └── modules/          # User-level features
│       ├── shell.nix     # Zsh, direnv, starship
│       ├── editor.nix    # Neovim, VSCode
│       ├── git.nix       # Git configuration
│       └── dev-tools.nix # Tmux, utilities
│
├── secrets/              # Encrypted credentials
│   ├── .sops.yaml       # Encryption keys (DO NOT COMMIT UNENCRYPTED)
│   └── secrets.yaml     # Encrypted secrets
│
├── flake.lock           # Pinned versions (COMMIT THIS)
└── docs/
    ├── README.md        # Getting started
    ├── SETUP.md         # Detailed setup instructions
    ├── QUICKREF.md      # Command reference
    ├── CUSTOMIZATION.md # Extension patterns
    └── ARCHITECTURE.md  # This file

Data Flow

On a NixOS System

┌─────────────────────────────────────────┐
│         flake.nix (configuration)       │
│  - Defines all inputs (nixpkgs, etc)   │
│  - Specifies nixosConfigurations       │
└──────────────────┬──────────────────────┘
                   │
        ┌──────────┴──────────┬─────────────────┐
        │                     │                 │
   ┌────▼────┐         ┌─────▼──────┐    ┌─────▼──────┐
   │ hosts/* │         │ nixos/*    │    │ home/*     │
   │(device) │ ──────► │(system cfg)│    │(user cfg)  │
   └────┬────┘         └─────┬──────┘    └─────┬──────┘
        │                    │                 │
        └────────────────────┼─────────────────┘
                             │
                ┌────────────▼──────────────┐
                │ nixos-rebuild switch      │
                │ (applies both system+home)│
                └───────────┬────────────────┘
                            │
        ┌───────────────────┴───────────────────┐
        │                                       │
   ┌────▼──────────────┐        ┌──────────────▼─────┐
   │ NixOS System      │        │ Home Manager       │
   │ /etc/nixos/*      │        │ ~/.config/*        │
   │ Services, kernel  │        │ Packages, dotfiles │
   └───────────────────┘        └────────────────────┘

On Non-NixOS System

┌──────────────────────────────────────┐
│ flake.nix (homeConfigurations only) │
└──────────────────┬───────────────────┘
                   │
        ┌──────────▼──────────────┐
        │ home/* (user config)    │
        └──────────┬───────────────┘
                   │
        ┌──────────▼──────────────────┐
        │ home-manager switch --flake │
        └──────────┬───────────────────┘
                   │
        ┌──────────▼──────────────────┐
        │ Home Manager               │
        │ ~/.config, ~/.local/share  │
        │ Packages, dotfiles         │
        └────────────────────────────┘

Module Design Pattern

Each module follows this pattern for maximum flexibility:

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

{
  # 1. Define options (schema)
  options.custom.feature = {
    enable = lib.mkEnableOption "Feature";
    setting1 = lib.mkOption { /* ... */ };
  };

  # 2. Conditionally implement
  config = let
    cfg = config.custom.feature;
  in lib.mkIf cfg.enable {
    # Implementation here
    environment.systemPackages = [ /* ... */ ];
  };
}

Why this pattern:

  • Declarative: Clear what can be configured
  • Composable: Modules don't interfere
  • Overridable: Host can override any setting
  • Conditional: Only included when enabled

Separation of Concerns

System Level (nixos/) - Run once, affects all users

  • OS-level packages (compilers, tools)
  • Services (SSH, web servers)
  • Bootloader, kernel, hardware
  • User creation and permissions
  • Firewall, networking
  • System-wide environment variables

When to put config here:

  • Affects the entire system
  • Required by multiple users
  • Needs root/sudo privileges

User Level (home/) - Per-user, portable

  • Shell configuration and aliases
  • Editor settings and plugins
  • User-installed packages
  • Dotfiles (~/.config/*)
  • Environment variables (user-specific)
  • Git, SSH client configuration

When to put config here:

  • Only one user needs it
  • Doesn't require system privileges
  • Configurable per-user
  • Want to port to non-NixOS systems

Configuration Hierarchy

Settings are applied in this order (later overrides earlier):

  1. nixos/default.nix - Base system defaults
  2. nixos/modules/*.nix - System feature modules
  3. hosts/hostname/default.nix - Machine-specific overrides
  4. home/default.nix - User base defaults
  5. home/modules/*.nix - User feature modules

Example with custom.development.languages:

# Start: undefined

# nixos/modules/development.nix:
# options.custom.development.languages = lib.mkOption { default = []; ... };

# hosts/laptop/default.nix:
custom.development.languages = [ "rust" "python" ];  # Override

# Result: [ "rust" "python" ]

Flakes Architecture

Inputs (Dependencies)

inputs = {
  nixpkgs = "...";           # Stable packages
  nixpkgs-unstable = "...";  # Cutting-edge packages
  home-manager = "...";      # User config management
  sops-nix = "...";          # Secrets management
  disko = "...";             # Disk partitioning
};

Why these inputs:

  • nixpkgs + unstable: Mix stable (secure) with latest (features)
  • home-manager: User config separate from system
  • sops-nix: Encrypted secrets, portable
  • disko: Declarative disk setup for new machines

Outputs (What's available)

outputs = {
  nixosConfigurations = {
    laptop = nixosSystem { ... };   # Full system config
    server = nixosSystem { ... };
  };
  homeConfigurations = {
    "myuser@linux" = homeManagerConfiguration { ... };  # Standalone HM
  };
  devShells = {
    default = mkShell { ... };      # Development environment
  };
  apps = {
    installer = { ... };            # Bootstrap helper
  };
}

Secrets Flow

secrets/
├── .sops.yaml              # Key configuration (WHO can decrypt)
└── secrets.yaml (encrypted)
     │
     ├─ SSH keys
     ├─ API tokens  
     └─ Passwords
     
When applied:
     │
     ▼
Sops decrypts (using ~/.config/sops/age/keys.txt)
     │
     ▼
sops.secrets.* paths available in Nix
     │
     ▼
Placed in /run/secrets/* at boot
     │
     ▼
Referenced in system/home config

Key insight: Secrets are encrypted on disk, decrypted at boot, never stored in nix store in plaintext.

Multi-Machine Scaling

To support N machines:

  1. Add new host in hosts/newhost/default.nix
  2. Register in flake.nix under nixosConfigurations.newhost
  3. Deploy with: sudo nixos-rebuild switch --flake .#newhost

Each machine:

  • Shares nixos/ modules
  • Shares home/ modules
  • Has unique hosts/ overrides
  • Uses same secrets.yaml (all machines can decrypt)
Common Base         Per-Machine Override
─────────────       ──────────────────
nixos/default.nix  →  hosts/laptop/default.nix
nixos/modules/     →  (machine-specific features)
home/modules/      →  (shared everywhere)

Update Strategy

Stable ← └─ Pinned via flake.lock
   │
   ├─ Low risk, predictable
   └─ Most system components

Unstable → Override in modules when needed
   │
   ├─ Fast-moving
   └─ For specific packages (bleeding edge editor, tools)

Update process:

# Pin current state
git commit flake.lock

# Update specific input
nix flake update nixpkgs

# Test
sudo nixos-rebuild test --flake .#laptop

# Deploy
sudo nixos-rebuild switch --flake .#laptop

# Save new state
git commit flake.lock

Extension Points

Add Feature: Create a new module

  1. Create nixos/modules/myfeature.nix or home/modules/myfeature.nix
  2. Follow module pattern (options + config)
  3. Import in nixos/default.nix or home/default.nix
  4. Enable in host config: custom.myfeature.enable = true

Add Machine: Copy host template

  1. Create hosts/newmachine/default.nix
  2. Customize for that machine
  3. Add to flake.nix under nixosConfigurations
  4. Deploy with sudo nixos-rebuild switch --flake .#newmachine

Add Dependency: Update flake inputs

  1. Edit flake.nix inputs section
  2. Run nix flake update
  3. Commit flake.lock

Add Secret: Edit secrets.yaml

  1. Run sops secrets/secrets.yaml
  2. Add key-value pair
  3. Reference in module with config.sops.secrets."key".path

Testing & Safety

Before Deploying

nix flake check              # Syntax validation
nix flake show              # Check all outputs
sudo nixos-rebuild test     # Build without activating

Rollback if Issues

# NixOS (automatic previous generation)
sudo nixos-rebuild --rollback switch

# Home Manager (manual)
home-manager switch --flake .#myuser@linux ~/.local/state/home-manager/previous

Validate Changes

# What changed?
sudo nixos-rebuild diff --flake .#laptop

# What will happen?
sudo nixos-rebuild test --flake .#laptop

# Apply
sudo nixos-rebuild switch --flake .#laptop

Performance Considerations

  • Flakes: Slow evaluation (1-2 seconds) but reproducible
  • Modularity: More files but faster edits
  • Secrets: Decryption only at boot (fast)
  • Unstable: More binary cache misses (compilation time)

See Also

  • README.md - Getting started
  • SETUP.md - Step-by-step installation
  • QUICKREF.md - Common commands
  • CUSTOMIZATION.md - Extension patterns