Files
home/docs/nix-architecture.md
Morten Olsen f42a092b60 init
2025-12-15 13:15:09 +01:00

37 KiB

Nix Home Manager Flake Architecture

Overview

This document defines the architecture for migrating from chezmoi to Nix Home Manager with flakes. The design supports two target environments (personal and work) while maintaining a clean, modular, and extensible structure.


1. Directory Structure

.
├── flake.nix                    # Main flake entry point
├── flake.lock                   # Locked dependencies
├── README.md                    # Setup and usage documentation
│
├── hosts/                       # Machine-specific configurations
│   ├── personal/
│   │   └── default.nix          # Personal machine darwin config
│   └── work/
│       └── default.nix          # Work machine darwin config
│
├── home/                        # Home Manager configurations
│   ├── default.nix              # Shared home configuration
│   ├── personal.nix             # Personal profile overrides
│   └── work.nix                 # Work profile overrides
│
├── modules/                     # Reusable Nix modules
│   ├── darwin/                  # macOS-specific modules
│   │   ├── default.nix          # Common darwin settings
│   │   ├── homebrew.nix         # Homebrew cask management
│   │   └── system.nix           # System preferences
│   │
│   └── home/                    # Home Manager modules
│       ├── shell/
│       │   ├── default.nix      # Shell module entry point
│       │   ├── zsh.nix          # Zsh configuration
│       │   ├── starship.nix     # Starship prompt
│       │   ├── atuin.nix        # Shell history
│       │   ├── direnv.nix       # Directory environments
│       │   └── aliases.nix      # Shell aliases
│       │
│       ├── git/
│       │   ├── default.nix      # Git module entry point
│       │   ├── config.nix       # Core git configuration
│       │   └── identities.nix   # Git identity management
│       │
│       ├── ssh/
│       │   ├── default.nix      # SSH module entry point
│       │   └── hosts.nix        # SSH host configurations
│       │
│       ├── terminal/
│       │   ├── default.nix      # Terminal module entry point
│       │   ├── tmux.nix         # Tmux configuration
│       │   └── neovim.nix       # Neovim configuration
│       │
│       ├── packages/
│       │   ├── default.nix      # Package module entry point
│       │   ├── cli.nix          # CLI tools
│       │   ├── development.nix  # Development tools
│       │   └── kubernetes.nix   # K8s tools
│       │
│       └── apps/
│           ├── default.nix      # Apps module entry point
│           └── aerospace.nix    # Window manager config
│
├── lib/                         # Helper functions
│   └── default.nix              # Utility functions
│
├── overlays/                    # Nixpkgs overlays
│   └── default.nix              # Custom package overlays
│
├── secrets/                     # Encrypted secrets (sops-nix)
│   ├── secrets.yaml             # Encrypted secrets file
│   └── .sops.yaml               # SOPS configuration
│
└── docs/                        # Documentation
    ├── migration-analysis.md    # Migration analysis
    └── nix-architecture.md      # This document

Design Rationale

  1. hosts/ - Machine-level configurations using nix-darwin. Each host can have unique system settings while sharing common darwin modules.

  2. home/ - Profile-specific home-manager configurations. The default.nix contains shared settings, while personal.nix and work.nix contain profile-specific overrides.

  3. modules/ - Reusable, composable modules organized by functionality. Split into darwin/ (system-level) and home/ (user-level) to maintain clear separation.

  4. lib/ - Helper functions for reducing boilerplate and improving maintainability.

  5. overlays/ - Custom package modifications or additions to nixpkgs.

  6. secrets/ - Encrypted secrets managed by sops-nix for sensitive data like API tokens.


2. Flake Structure

2.1 Inputs

{
  inputs = {
    # Core dependencies
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    
    # Home Manager for user environment
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # nix-darwin for macOS system configuration
    nix-darwin = {
      url = "github:LnL7/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # Secrets management
    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # Optional: Homebrew management through nix-darwin
    nix-homebrew = {
      url = "github:zhaofengli-wip/nix-homebrew";
    };
  };
}

2.2 Outputs Structure

{
  outputs = { self, nixpkgs, home-manager, nix-darwin, sops-nix, nix-homebrew, ... }@inputs:
  let
    # Supported systems (macOS for now, Linux can be added later)
    systems = [ "aarch64-darwin" "x86_64-darwin" ];
    
    # Helper to generate per-system attributes
    forAllSystems = nixpkgs.lib.genAttrs systems;
    
    # Common special args passed to all modules
    specialArgs = { inherit inputs; };
  in
  {
    # Darwin (macOS) system configurations
    darwinConfigurations = {
      # Personal machine configuration
      "personal" = nix-darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        inherit specialArgs;
        modules = [
          ./hosts/personal
          ./modules/darwin
          home-manager.darwinModules.home-manager
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              extraSpecialArgs = specialArgs;
              users.alice = { ... }: {
                imports = [
                  ./home
                  ./home/personal.nix
                ];
              };
            };
          }
        ];
      };
      
      # Work machine configuration
      "work" = nix-darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        inherit specialArgs;
        modules = [
          ./hosts/work
          ./modules/darwin
          home-manager.darwinModules.home-manager
          {
            home-manager = {
              useGlobalPkgs = true;
              useUserPackages = true;
              extraSpecialArgs = specialArgs;
              users.alice = { ... }: {
                imports = [
                  ./home
                  ./home/work.nix
                ];
              };
            };
          }
        ];
      };
    };
    
    # Standalone Home Manager configurations (for non-darwin systems)
    homeConfigurations = {
      "personal@linux" = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages.x86_64-linux;
        extraSpecialArgs = specialArgs;
        modules = [
          ./home
          ./home/personal.nix
        ];
      };
      
      "work@linux" = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages.x86_64-linux;
        extraSpecialArgs = specialArgs;
        modules = [
          ./home
          ./home/work.nix
        ];
      };
    };
    
    # Development shells for this repository
    devShells = forAllSystems (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        default = pkgs.mkShell {
          packages = with pkgs; [ nixfmt sops age ];
        };
      }
    );
  };
}

2.3 Key Design Decisions

  1. nix-darwin as primary entry point - On macOS, use darwinConfigurations which integrates home-manager as a module. This allows managing both system-level settings (Homebrew, system preferences) and user-level dotfiles in one command.

  2. Standalone homeConfigurations - Provided for future Linux support or cases where nix-darwin isn't desired.

  3. useGlobalPkgs and useUserPackages - Ensures home-manager uses the same nixpkgs instance as darwin, avoiding duplicate package downloads.

  4. specialArgs - Passes inputs to all modules, enabling access to flake inputs anywhere in the configuration.


3. Module Design

3.1 Module Architecture Diagram

graph TB
    subgraph Flake
        F[flake.nix]
    end
    
    subgraph Darwin Configs
        DP[hosts/personal]
        DW[hosts/work]
    end
    
    subgraph Darwin Modules
        DM[modules/darwin/default.nix]
        HB[modules/darwin/homebrew.nix]
        SYS[modules/darwin/system.nix]
    end
    
    subgraph Home Configs
        HC[home/default.nix]
        HP[home/personal.nix]
        HW[home/work.nix]
    end
    
    subgraph Home Modules
        SH[modules/home/shell]
        GIT[modules/home/git]
        SSH[modules/home/ssh]
        TM[modules/home/terminal]
        PKG[modules/home/packages]
        APP[modules/home/apps]
    end
    
    F --> DP
    F --> DW
    DP --> DM
    DW --> DM
    DM --> HB
    DM --> SYS
    
    DP --> HC
    DW --> HC
    DP --> HP
    DW --> HW
    
    HC --> SH
    HC --> GIT
    HC --> SSH
    HC --> TM
    HC --> PKG
    HC --> APP

3.2 Shared vs Profile-Specific Content

Component Shared Personal-Specific Work-Specific
Shell zsh, starship, atuin, aliases - -
Git Core config, aliases, delta Personal signing key, personal email Work signing key, work email, zeronorth config
SSH 1Password agent, control master github-private host github-zeronorth host
Packages CLI tools, dev tools, k8s Personal apps (darktable, proton, steam) -
Homebrew Casks 1password, ghostty, raycast signal, proton-*, steam -
Project Configs - ~/Projects/.gitconfig ~/Projects/private/.gitconfig, ~/Projects/zeronorth/.gitconfig

3.3 Module Option Pattern

Each module should follow this pattern for configurability:

# modules/home/git/default.nix
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.modules.git;
in
{
  options.modules.git = {
    enable = mkEnableOption "Git configuration";
    
    userName = mkOption {
      type = types.str;
      default = "Morten Olsen";
      description = "Git user name";
    };
    
    userEmail = mkOption {
      type = types.str;
      description = "Git user email";
    };
    
    signing = {
      enable = mkEnableOption "Git commit signing";
      
      key = mkOption {
        type = types.str;
        description = "SSH signing key";
      };
    };
    
    includes = mkOption {
      type = types.listOf (types.submodule {
        options = {
          condition = mkOption { type = types.str; };
          path = mkOption { type = types.str; };
        };
      });
      default = [];
      description = "Conditional git config includes";
    };
  };

  config = mkIf cfg.enable {
    programs.git = {
      enable = true;
      userName = cfg.userName;
      userEmail = cfg.userEmail;
      
      signing = mkIf cfg.signing.enable {
        key = cfg.signing.key;
        signByDefault = true;
      };
      
      extraConfig = {
        gpg.format = "ssh";
        gpg.ssh.program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign";
        # ... other shared config
      };
      
      includes = cfg.includes;
    };
  };
}

3.4 Module Implementations

Shell Module (modules/home/shell/default.nix)

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

{
  imports = [
    ./zsh.nix
    ./starship.nix
    ./atuin.nix
    ./direnv.nix
    ./aliases.nix
  ];

  options.modules.shell = {
    enable = lib.mkEnableOption "Shell configuration";
  };

  config = lib.mkIf config.modules.shell.enable {
    # Enable all shell sub-modules
    modules.shell.zsh.enable = true;
    modules.shell.starship.enable = true;
    modules.shell.atuin.enable = true;
    modules.shell.direnv.enable = true;
  };
}

Git Module (modules/home/git/default.nix)

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

{
  imports = [
    ./config.nix
    ./identities.nix
  ];

  options.modules.git = {
    enable = lib.mkEnableOption "Git configuration";
    
    profile = lib.mkOption {
      type = lib.types.enum [ "personal" "work" ];
      description = "Git profile to use";
    };
  };
}

SSH Module (modules/home/ssh/default.nix)

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

{
  imports = [ ./hosts.nix ];

  options.modules.ssh = {
    enable = lib.mkEnableOption "SSH configuration";
    
    onePasswordAgent = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = "Use 1Password SSH agent";
    };
  };

  config = lib.mkIf config.modules.ssh.enable {
    programs.ssh = {
      enable = true;
      controlMaster = "auto";
      controlPath = "/tmp/ssh-%r@%h:%p";
      forwardAgent = true;
      
      extraConfig = lib.mkIf config.modules.ssh.onePasswordAgent ''
        IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
      '';
      
      includes = [ "~/.colima/ssh_config" ];
    };
  };
}

Homebrew Module (modules/darwin/homebrew.nix)

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

let
  cfg = config.modules.homebrew;
in
{
  options.modules.homebrew = {
    enable = lib.mkEnableOption "Homebrew management";
    
    personalCasks = lib.mkOption {
      type = lib.types.bool;
      default = false;
      description = "Include personal-only casks";
    };
  };

  config = lib.mkIf cfg.enable {
    homebrew = {
      enable = true;
      onActivation = {
        autoUpdate = true;
        cleanup = "zap";
        upgrade = true;
      };
      
      taps = [
        "nikitabobko/tap"
        "coder/coder"
        "fluxcd/tap"
        "sst/tap"
      ];
      
      casks = [
        "1password"
        "1password-cli"
        "aerospace"
        "dbeaver-community"
        "ghostty"
        "home-assistant"
        "jellyfin-media-player"
        "lens"
        "localsend"
        "mqtt-explorer"
        "obsidian"
        "ollama-app"
        "raycast"
      ] ++ lib.optionals cfg.personalCasks [
        "darktable"
        "signal"
        "proton-mail-bridge"
        "proton-pass"
        "protonvpn"
        "steam"
      ];
    };
  };
}

3.5 Secrets Management

For sensitive data, use sops-nix with age encryption:

# secrets/.sops.yaml
keys:
  - &personal age1...  # Personal machine key
  - &work age1...      # Work machine key

creation_rules:
  - path_regex: secrets/personal\.yaml$
    key_groups:
      - age:
        - *personal
  - path_regex: secrets/work\.yaml$
    key_groups:
      - age:
        - *work
  - path_regex: secrets/secrets\.yaml$
    key_groups:
      - age:
        - *personal
        - *work

For 1Password integration (already used in current setup), reference secrets directly:

# In modules that need secrets
home.sessionVariables = {
  NODE_AUTH_TOKEN = "op://Employee/Github NPM Token/password";
};

4. Configuration Profiles

4.1 Shared Configuration (home/default.nix)

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

{
  imports = [
    ../modules/home/shell
    ../modules/home/git
    ../modules/home/ssh
    ../modules/home/terminal
    ../modules/home/packages
    ../modules/home/apps
  ];

  home = {
    stateVersion = "24.05";
    
    sessionVariables = {
      EDITOR = "nvim";
      LANG = "en_US.UTF-8";
    };
    
    sessionPath = [
      "$HOME/.local/bin"
      "$HOME/.cargo/bin"
      "/opt/homebrew/bin"
    ];
  };

  # Enable shared modules
  modules = {
    shell.enable = true;
    git.enable = true;
    ssh.enable = true;
    terminal.enable = true;
    packages.enable = true;
    apps.enable = true;
  };
}

4.2 Personal Profile (home/personal.nix)

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

{
  # Git configuration for personal
  modules.git = {
    profile = "personal";
    userEmail = "fbtijfdq@void.black";
    signing.key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFaIAP/ZJ7+7jeR44e1yIJjfQAB6MN351LDKJAXVF62P";
    
    includes = [
      {
        condition = "gitdir:~/Projects/";
        path = "~/Projects/.gitconfig";
      }
    ];
  };

  # SSH hosts for personal
  modules.ssh.hosts = {
    github-private = {
      hostname = "ssh.github.com";
      user = "git";
      port = 443;
      identityFile = "~/.ssh/keys/github-private.pub";
    };
    gitea-ssh-olsen-cloud = {
      hostname = "gitea-ssh.olsen.cloud";
      user = "git";
      port = 2202;
      identityFile = "~/.ssh/keys/github-private.pub";
    };
  };

  # Personal-only packages
  modules.packages.personal = true;

  # Project-specific git config
  home.file.".Projects/.gitconfig".text = ''
    [user]
      email = fbtijfdq@void.black
      name = Morten Olsen
      signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFaIAP/ZJ7+7jeR44e1yIJjfQAB6MN351LDKJAXVF62P
    
    [commit]
      gpgsign = true
    
    [gpg]
      format = ssh
    
    [gpg "ssh"]
      program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
    
    [url "ssh://git@ssh-gitea.olsen.cloud:2205/"]
      insteadOf = "https://gitea.olsen.cloud/"
    
    [url "git@github-private:"]
      insteadOf = https://github.com/
  '';
}

4.3 Work Profile (home/work.nix)

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

{
  # Git configuration for work
  modules.git = {
    profile = "work";
    userEmail = "fbtijfdq@void.black";  # Default email
    signing.key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAzuPy7D/54GxMq9Zhz0CUjaDnEQ6RkQ/yqVYl7U55k";
    
    includes = [
      {
        condition = "gitdir:~/Projects/private/";
        path = "~/Projects/private/.gitconfig";
      }
      {
        condition = "gitdir:~/Projects/zeronorth/";
        path = "~/Projects/zeronorth/.gitconfig";
      }
    ];
  };

  # SSH hosts for work
  modules.ssh.hosts = {
    github-private = {
      hostname = "ssh.github.com";
      user = "git";
      port = 443;
      identityFile = "~/.ssh/keys/github-private.pub";
    };
    github-zeronorth = {
      hostname = "ssh.github.com";
      user = "git";
      port = 443;
      identityFile = "~/.ssh/keys/github-zeronorth.pub";
    };
    gitea-ssh-olsen-cloud = {
      hostname = "gitea-ssh.olsen.cloud";
      user = "git";
      port = 2202;
      identityFile = "~/.ssh/keys/github-private.pub";
    };
  };

  # Work-only packages (none currently)
  modules.packages.work = true;

  # Project-specific git configs
  home.file = {
    "Projects/private/.gitconfig".text = ''
      [user]
        email = fbtijfdq@void.black
        name = Morten Olsen
        signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAzuPy7D/54GxMq9Zhz0CUjaDnEQ6RkQ/yqVYl7U55k
      
      [commit]
        gpgsign = true
      
      [gpg]
        format = ssh
      
      [gpg "ssh"]
        program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
      
      [url "ssh://git@ssh-gitea.olsen.cloud:2205/"]
        insteadOf = "https://gitea.olsen.cloud/"
      
      [url "git@github-private:"]
        insteadOf = https://github.com/
    '';
    
    "Projects/zeronorth/.gitconfig".text = ''
      [user]
        email = morten.olsen@zeronorth.com
        name = Morten Olsen
        signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKDbZITpz5QrVIxPn9gKVWMPK+3W3YZZGszFOQvO/h7M
      
      [commit]
        gpgsign = true
      
      [gpg]
        format = ssh
      
      [gpg "ssh"]
        program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
      
      [url "git@github-zeronorth:"]
        insteadOf = https://github.com/
    '';
    
    "Projects/zeronorth/.envrc".text = ''
      export NODE_AUTH_TOKEN="op://Employee/Github NPM Token/password"
      export NPM_TOKEN="op://Employee/Github NPM Token/password"
      export NPM_GITHUB_TOKEN="op://Employee/Github NPM Token/password"
      export AWS_PROFILE=zntest
    '';
  };
}

4.4 Profile Selection Flow

flowchart TD
    A[New Machine Setup] --> B{Machine Type?}
    B -->|Personal| C[darwin-rebuild switch --flake .#personal]
    B -->|Work| D[darwin-rebuild switch --flake .#work]
    
    C --> E[Loads hosts/personal + home/personal.nix]
    D --> F[Loads hosts/work + home/work.nix]
    
    E --> G[Personal SSH keys, git config, packages]
    F --> H[Work SSH keys, git config, zeronorth setup]
    
    G --> I[System Ready]
    H --> I

5. Usage Patterns

5.1 Initial Setup on New Machine

Prerequisites

  1. Install Nix with flakes enabled:
curl -L https://nixos.org/nix/install | sh
  1. Enable flakes in Nix configuration:
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf

Personal Machine Setup

# Clone the dotfiles repository
git clone https://github.com/username/dotfiles.git ~/.dotfiles
cd ~/.dotfiles

# Build and activate the personal configuration
nix build .#darwinConfigurations.personal.system
./result/sw/bin/darwin-rebuild switch --flake .#personal

# Subsequent rebuilds
darwin-rebuild switch --flake .#personal

Work Machine Setup

# Clone the dotfiles repository
git clone https://github.com/username/dotfiles.git ~/.dotfiles
cd ~/.dotfiles

# Build and activate the work configuration
nix build .#darwinConfigurations.work.system
./result/sw/bin/darwin-rebuild switch --flake .#work

# Subsequent rebuilds
darwin-rebuild switch --flake .#work

5.2 Switching Configurations

If you need to switch a machine from personal to work (or vice versa):

# Switch to work configuration
darwin-rebuild switch --flake .#work

# Switch back to personal configuration
darwin-rebuild switch --flake .#personal

Note: Switching profiles will change git configs, SSH hosts, and installed packages. Some manual cleanup may be needed for files that were created by the previous profile.

5.3 Updating Configuration

After Editing Nix Files

# Rebuild and switch to new configuration
darwin-rebuild switch --flake .#personal  # or .#work

# Build without switching (for testing)
darwin-rebuild build --flake .#personal

Updating Flake Inputs

# Update all inputs
nix flake update

# Update specific input
nix flake lock --update-input nixpkgs
nix flake lock --update-input home-manager

# Rebuild after update
darwin-rebuild switch --flake .#personal

5.4 Common Commands Reference

Command Description
darwin-rebuild switch --flake .#personal Apply personal configuration
darwin-rebuild switch --flake .#work Apply work configuration
darwin-rebuild build --flake .#personal Build without applying
nix flake update Update all flake inputs
nix flake check Validate flake configuration
nix repl --expr 'builtins.getFlake "."' Interactive debugging
darwin-rebuild --rollback Rollback to previous generation
nix-collect-garbage -d Clean up old generations

5.5 Development Workflow

# Enter development shell with tools for editing this repo
nix develop

# Format Nix files
nixfmt **/*.nix

# Check configuration validity
nix flake check

6. Future Extensibility

6.1 Adding Linux Support

The architecture already includes placeholder homeConfigurations for Linux. To fully support Linux:

  1. Add Linux-specific modules in modules/home/ with platform conditionals:
{ config, lib, pkgs, ... }:

{
  config = lib.mkMerge [
    # Common configuration
    { ... }
    
    # macOS-specific
    (lib.mkIf pkgs.stdenv.isDarwin { ... })
    
    # Linux-specific
    (lib.mkIf pkgs.stdenv.isLinux { ... })
  ];
}
  1. Create Linux host configurations in hosts/linux/.

  2. Use homeConfigurations for standalone home-manager on Linux.

6.2 Adding New Machines

To add a new machine configuration:

  1. Create a new directory under hosts/:
hosts/
├── personal/
├── work/
└── new-machine/
    └── default.nix
  1. Add the configuration to flake.nix:
darwinConfigurations."new-machine" = nix-darwin.lib.darwinSystem {
  # ...
};

6.3 Adding New Modules

To add a new module (e.g., for a new application):

  1. Create the module file:
modules/home/apps/new-app.nix
  1. Import it in the parent module:
# modules/home/apps/default.nix
imports = [
  ./aerospace.nix
  ./new-app.nix
];
  1. Add options and configuration following the established pattern.

7. Migration Checklist

When implementing this architecture, follow this order:

  • Create basic flake.nix with inputs
  • Set up directory structure
  • Implement darwin modules (homebrew, system)
  • Implement shell modules (zsh, starship, atuin)
  • Implement git modules with profile support
  • Implement SSH modules
  • Implement terminal modules (tmux)
  • Implement package modules
  • Implement apps modules (aerospace)
  • Create personal profile configuration
  • Create work profile configuration
  • Set up secrets management (sops-nix)
  • Test on personal machine
  • Test on work machine
  • Document any manual steps
  • Remove chezmoi after successful migration

8. Summary

This architecture provides:

  1. Clean separation of concerns - Darwin system config, home-manager user config, and reusable modules are clearly separated.

  2. Profile-based configuration - Personal and work environments are distinct profiles that share common modules but override specific settings.

  3. Modular design - Each functional area (shell, git, ssh, packages) is a self-contained module with configurable options.

  4. macOS-first with extensibility - Designed for macOS with nix-darwin, but structured to easily add Linux support later.

  5. Declarative package management - CLI tools via nixpkgs, GUI apps via Homebrew casks managed through nix-darwin.

  6. Secrets handling - Support for both sops-nix encrypted secrets and 1Password CLI references.

  7. Simple usage patterns - Single command to apply configuration, easy switching between profiles.

The migration from chezmoi to this Nix-based setup will provide reproducible, declarative configuration management with the full power of the Nix ecosystem.


9. Final Implementation

This section documents the actual implemented structure, which may differ slightly from the original design above.

9.1 Implemented Directory Structure

.
├── flake.nix                    # Main flake entry point
├── flake.lock                   # Locked dependencies
├── README.md                    # Setup and usage documentation
│
├── home/                        # Home Manager configurations
│   ├── default.nix              # Shared home configuration
│   ├── personal.nix             # Personal profile overrides
│   └── work.nix                 # Work profile overrides
│
├── hosts/                       # Machine-specific nix-darwin configs
│   ├── personal/
│   │   └── default.nix          # Personal machine darwin settings
│   └── work/
│       └── default.nix          # Work machine darwin settings
│
├── modules/                     # Reusable Nix modules
│   ├── darwin/
│   │   └── homebrew.nix         # Homebrew cask management
│   │
│   └── home/                    # Home Manager modules
│       ├── apps.nix             # Application configs (aerospace, jellyfin-tui)
│       ├── git.nix              # Git configuration with signing
│       ├── git-files.nix        # Project-specific git configs
│       ├── packages.nix         # CLI packages via Nix
│       ├── shell.nix            # Shell environment (zsh, starship, etc.)
│       ├── ssh.nix              # SSH configuration with 1Password
│       └── tmux.nix             # Tmux configuration
│
└── docs/                        # Documentation
    ├── migration-analysis.md    # Original chezmoi analysis
    ├── nix-architecture.md      # This document
    └── usage.md                 # Detailed usage guide

9.2 Module Reference

Darwin Modules

Module File Purpose
Homebrew modules/darwin/homebrew.nix Manages Homebrew taps, formulae, and casks declaratively

Home Manager Modules

Module File Purpose
Shell modules/home/shell.nix Zsh, Starship, Atuin, Direnv, Zoxide, FZF, Pyenv
Packages modules/home/packages.nix CLI tools and development packages
Git modules/home/git.nix Git configuration with delta, signing, aliases
Git Files modules/home/git-files.nix Project-specific gitconfig files
SSH modules/home/ssh.nix SSH with 1Password agent, host configurations
Tmux modules/home/tmux.nix Tmux with vim navigation, plugins
Apps modules/home/apps.nix Aerospace window manager, Jellyfin TUI

9.3 Module Configuration Options

modules.homebrew (Darwin)

Option Type Default Description
enable bool false Enable Homebrew management
casks.shared list of str (see module) Casks installed on all machines
casks.personal list of str (see module) Casks installed only on personal machines
casks.enablePersonal bool false Whether to install personal casks
brews list of str (see module) Homebrew formulae to install
taps list of str (see module) Homebrew taps to add
cleanup enum "zap" Cleanup behavior: "none", "uninstall", "zap"

modules.ssh (Home)

Option Type Default Description
enable bool false Enable SSH configuration
enableGitHubPrivate bool false Enable github-private host
enableGitHubZeronorth bool false Enable github-zeronorth host
enableCoder bool false Enable Coder SSH hosts
enableGiteaPrivate bool false Enable gitea-ssh.olsen.cloud host
githubPrivateKeyPath str "~/.ssh/keys/github-private.pub" Path to GitHub private key
githubZeronorthKeyPath str "~/.ssh/keys/github-zeronorth.pub" Path to GitHub Zeronorth key

modules.git (Home)

Option Type Default Description
enable bool false Enable Git configuration
userName str "Morten Olsen" Git user name
userEmail str (required) Git user email
signingKey str (required) SSH signing key (public key)
includes list of {condition, path} [] Conditional includes for project configs

modules.gitFiles (Home)

Option Type Default Description
enable bool false Enable project-specific git files
personal.enable bool false Create ~/Projects/.gitconfig
personal.email str (example) Email for personal projects
personal.signingKey str (required) Signing key for personal projects
private.enable bool false Create ~/Projects/private/.gitconfig
private.email str (example) Email for private projects
private.signingKey str (required) Signing key for private projects
zeronorth.enable bool false Create ~/Projects/zeronorth/.gitconfig
zeronorth.email str (example) Email for zeronorth projects
zeronorth.signingKey str (required) Signing key for zeronorth projects

modules.apps (Home)

Option Type Default Description
enable bool false Enable application configurations
aerospace.enable bool true Enable Aerospace window manager config
jellyfin-tui.enable bool false Enable Jellyfin TUI config
jellyfin-tui.serverUrl str "" Jellyfin server URL
jellyfin-tui.serverName str "Home Server" Display name for server
jellyfin-tui.username str "" Jellyfin username
jellyfin-tui.passwordFile str "" Path to password file

9.4 Profile Configuration Summary

Personal Profile (home/personal.nix)

modules.git = {
  enable = true;
  userEmail = "alice@personal.example.com";
  signingKey = "ssh-ed25519 AAAAC3...";
  includes = [{ condition = "gitdir:~/Projects/"; path = "~/Projects/.gitconfig"; }];
};

modules.gitFiles = {
  enable = true;
  personal = { enable = true; email = "..."; signingKey = "..."; };
};

modules.ssh = {
  enableGitHubPrivate = true;
  enableGiteaPrivate = true;
  enableGitHubZeronorth = false;
  enableCoder = false;
};

modules.apps = {
  jellyfin-tui = { enable = true; serverUrl = "..."; username = "..."; };
};

Work Profile (home/work.nix)

modules.git = {
  enable = true;
  userEmail = "alice@work.example.com";
  signingKey = "ssh-ed25519 AAAAC3...";
  includes = [
    { condition = "gitdir:~/Projects/private/"; path = "~/Projects/private/.gitconfig"; }
    { condition = "gitdir:~/Projects/zeronorth/"; path = "~/Projects/zeronorth/.gitconfig"; }
  ];
};

modules.gitFiles = {
  enable = true;
  private = { enable = true; email = "..."; signingKey = "..."; };
  zeronorth = { enable = true; email = "..."; signingKey = "..."; };
};

modules.ssh = {
  enableGitHubPrivate = true;
  enableGitHubZeronorth = true;
  enableCoder = true;
  enableGiteaPrivate = false;
};

9.5 Packages Installed

Via Nix (from modules/home/packages.nix)

Category Packages
Modern CLI bat, eza, fd, ripgrep, delta
File Utils jq, yq, tree, rsync, unzip, curl, wget, watch
Git git, gh, jujutsu, lazygit
Development neovim, tmux, nodejs_22, deno, rustup, python313, uv, gnumake, cmake
Containers docker, docker-buildx, docker-compose, colima
Kubernetes kubectl, kubernetes-helm, helmfile, k9s, istioctl, fluxcd
Infrastructure terraform, ansible, sshpass, awscli2
Media ffmpeg
Security gnupg, age, sops
Misc graphviz, tree-sitter, htop, ncdu, tldr, nixfmt-rfc-style, nil

Via Homebrew Casks (from modules/darwin/homebrew.nix)

Shared (all machines):

  • 1password, 1password-cli
  • ghostty, dbeaver-community, lens
  • aerospace
  • raycast, obsidian
  • jellyfin-media-player, ollama-app
  • localsend, mqtt-explorer, home-assistant

Personal only:

  • darktable
  • proton-mail-bridge, proton-pass, protonvpn
  • signal
  • steam

Via Homebrew Formulae

  • coder/coder/coder
  • fluxcd/tap/flux
  • sst/tap/opencode
  • tree-sitter-cli

9.6 Key Differences from Original Design

  1. Simplified module structure - Instead of deeply nested modules (e.g., modules/home/shell/zsh.nix), the implementation uses flatter files (e.g., modules/home/shell.nix) for simplicity.

  2. No sops-nix - Secrets are managed via 1Password CLI references rather than sops-nix encrypted files.

  3. No lib/ directory - Helper functions are defined inline in modules rather than in a separate lib directory.

  4. No overlays/ - No custom package overlays are currently needed.

  5. Combined shell module - All shell-related configuration (zsh, starship, atuin, direnv, zoxide, fzf, pyenv) is in a single shell.nix file.

  6. Separate git-files module - Project-specific gitconfig files are managed in a dedicated git-files.nix module rather than inline in profile files.

9.7 Migration Status

The following chezmoi files have been migrated:

Chezmoi File Nix Module Status
dot_zshrc modules/home/shell.nix Migrated
dot_tmux.conf modules/home/tmux.nix Migrated
dot_gitconfig.tmpl modules/home/git.nix Migrated
dot_gitignore_global modules/home/git.nix Migrated
dot_Brewfile.tmpl modules/darwin/homebrew.nix + modules/home/packages.nix Migrated
dot_aerospace.toml modules/home/apps.nix Migrated
dot_config/atuin/config.toml modules/home/shell.nix Migrated
dot_shellrc/rc.d/* modules/home/shell.nix Migrated
dot_ssh/config modules/home/ssh.nix Migrated
dot_ssh/config.d/* modules/home/ssh.nix Migrated
private_Projects/* modules/home/git-files.nix Migrated
private_Library/.../jellyfin-tui/* modules/home/apps.nix Migrated

The original chezmoi files are kept in the repository for reference during the transition period.