12 KiB
12 KiB
Architecture & Design Principles
An overview of how your portable NixOS configuration is structured and why.
Core Design Goals
- Portability: Deploy to new machines, existing NixOS, or non-NixOS with minimal effort
- Modularity: Enable/disable features independently without tight coupling
- Reproducibility: Flakes + lock files ensure identical environments across machines
- Maintainability: Clear separation of concerns (system vs home, shared vs per-machine)
- Secrets Management: Encrypted, portable secrets with sops-nix
- 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):
- nixos/default.nix - Base system defaults
- nixos/modules/*.nix - System feature modules
- hosts/hostname/default.nix - Machine-specific overrides
- home/default.nix - User base defaults
- 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:
- Add new host in
hosts/newhost/default.nix - Register in
flake.nixundernixosConfigurations.newhost - 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
- Create
nixos/modules/myfeature.nixorhome/modules/myfeature.nix - Follow module pattern (options + config)
- Import in
nixos/default.nixorhome/default.nix - Enable in host config:
custom.myfeature.enable = true
Add Machine: Copy host template
- Create
hosts/newmachine/default.nix - Customize for that machine
- Add to
flake.nixundernixosConfigurations - Deploy with
sudo nixos-rebuild switch --flake .#newmachine
Add Dependency: Update flake inputs
- Edit
flake.nixinputs section - Run
nix flake update - Commit
flake.lock
Add Secret: Edit secrets.yaml
- Run
sops secrets/secrets.yaml - Add key-value pair
- 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