# 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: ```nix { 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`: ```nix # 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) ```nix 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) ```nix 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: ```bash # 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 ```bash nix flake check # Syntax validation nix flake show # Check all outputs sudo nixos-rebuild test # Build without activating ``` ### Rollback if Issues ```bash # 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 ```bash # 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