# Chezmoi to Nix Home Manager Migration Analysis ## Executive Summary This document analyzes the current chezmoi-based dotfiles configuration to prepare for migration to Nix Home Manager with flakes. The configuration manages a macOS development environment with two distinct profiles: **personal** and **work** (ZeroNorth). --- ## 1. Managed Dotfiles Overview ### 1.1 File Structure | Chezmoi Path | Target Path | Type | Templated | |--------------|-------------|------|-----------| | `dot_zshrc` | `~/.zshrc` | Shell config | No | | `dot_tmux.conf` | `~/.tmux.conf` | Tmux config | No | | `dot_gitconfig.tmpl` | `~/.gitconfig` | Git config | **Yes** | | `dot_gitignore_global` | `~/.gitignore_global` | Git ignore | No | | `dot_Brewfile.tmpl` | `~/.Brewfile` | Homebrew packages | **Yes** | | `dot_aerospace.toml` | `~/.aerospace.toml` | Window manager | No | | `dot_config/atuin/config.toml` | `~/.config/atuin/config.toml` | Shell history | No | | `dot_shellrc/rc.d/*` | `~/.shellrc/rc.d/*` | Shell scripts | No | | `dot_ssh/config` | `~/.ssh/config` | SSH config | No | | `dot_ssh/config.d/*` | `~/.ssh/config.d/*` | SSH host configs | No | | `dot_ssh/private_keys/*` | `~/.ssh/keys/*` | SSH public keys | **Partial** | | `private_Library/...` | `~/Library/...` | App configs | No | | `private_Projects/*` | `~/Projects/*` | Project configs | No | ### 1.2 External Dependencies From [`.chezmoiexternal.toml`](.chezmoiexternal.toml:1): ```toml [".ssh/authorized_keys"] type = "file" url = "https://github.com/morten-olsen.keys" refreshPeriod = "168h" ``` **Migration consideration:** This fetches SSH authorized keys from GitHub. In Nix, this could be handled by: - A custom derivation that fetches the keys at build time - A systemd service/launchd agent that periodically updates the file - Manual management with `home.file` and periodic updates --- ## 2. Personal vs Work Differentiation ### 2.1 Template Variable: `.work` The configuration uses a boolean `.work` template variable to differentiate between personal and work environments. ### 2.2 Conditional Logic Summary #### `.chezmoiignore` - File Exclusions From [`.chezmoiignore`](.chezmoiignore:2): | Condition | Ignored Files | |-----------|---------------| | `work = false` (Personal) | `Projects/private`, `Projects/zeronorth` | | `work = true` (Work) | `Projects/.gitconfig` | **Interpretation:** - **Personal machines:** Don't deploy work-related project configs - **Work machines:** Don't deploy the personal Projects/.gitconfig, use project-specific ones instead #### `dot_Brewfile.tmpl` - Package Differences From [`dot_Brewfile.tmpl`](dot_Brewfile.tmpl:66): | Environment | Additional Packages | |-------------|---------------------| | Personal only (`work != true`) | `darktable`, `signal`, `proton-mail-bridge`, `proton-pass`, `protonvpn`, `steam` | | Work | (none additional) | #### `dot_gitconfig.tmpl` - Git Configuration From [`dot_gitconfig.tmpl`](dot_gitconfig.tmpl:45): | Environment | Git Include Configuration | |-------------|---------------------------| | Personal (`work = false`) | Single includeIf for `~/Projects/` → `~/Projects/.gitconfig` | | Work (`work = true`) | Two includeIfs: `~/Projects/private/` and `~/Projects/zeronorth/` with separate configs | #### `dot_ssh/private_keys/private_github-private.pub.tmpl` - SSH Keys From [`dot_ssh/private_keys/private_github-private.pub.tmpl`](dot_ssh/private_keys/private_github-private.pub.tmpl:1): | Environment | SSH Key | |-------------|---------| | Personal | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFaIAP/ZJ7+7jeR44e1yIJjfQAB6MN351LDKJAXVF62P` | | Work | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAzuPy7D/54GxMq9Zhz0CUjaDnEQ6RkQ/yqVYl7U55k` | --- ## 3. Detailed Configuration Analysis ### 3.1 Shell Configuration #### Zsh ([`dot_zshrc`](dot_zshrc:1)) - Sets `XDG_RUNTIME_DIR` and `LANG` - Sources scripts from `~/.shellrc/rc.d/*.sh` and `~/.shellrc/zshrc.d/*.zsh` - Configures GPG TTY for SSH sessions - Displays ASCII art welcome banner - Configures FZF with Catppuccin color scheme - Initializes: `atuin`, `direnv`, `starship`, `zoxide`, `pyenv` - Adds Rust/Cargo to PATH #### Shell RC Scripts | File | Purpose | |------|---------| | [`01-env.sh`](dot_shellrc/rc.d/01-env.sh:1) | PATH setup, aliases (ls→eza, cat→bat, grep→rg, diff→delta, less→bat) | | [`01-nvim.sh`](dot_shellrc/rc.d/01-nvim.sh:1) | Sets EDITOR=nvim, alias vim=nvim | | [`05-nvm.sh`](dot_shellrc/rc.d/05-nvm.sh:1) | NVM initialization | ### 3.2 Git Configuration #### Global Config ([`dot_gitconfig.tmpl`](dot_gitconfig.tmpl:1)) - **Aliases:** graph, ll, st, cm, append, submodules, df, last, br, brr, undo, unstage - **Core:** Uses delta as pager, disables hooks, references global gitignore - **Pull:** Fast-forward only - **Push:** Auto setup remote - **LFS:** Configured - **Difftool:** nvim diff #### Project-Specific Configs | Config | Email | Signing Key | URL Rewrites | |--------|-------|-------------|--------------| | [`Projects/.gitconfig`](private_Projects/dot_gitconfig:1) (Personal) | `fbtijfdq@void.black` | Personal key | github-private, gitea.olsen.cloud | | [`Projects/private/.gitconfig`](private_Projects/private_private/dot_gitconfig:1) (Work) | `fbtijfdq@void.black` | Work key | github-private, gitea.olsen.cloud | | [`Projects/zeronorth/.gitconfig`](private_Projects/private_zeronorth/dot_gitconfig:1) (Work) | `morten.olsen@zeronorth.com` | ZeroNorth key | github-zeronorth | ### 3.3 SSH Configuration #### Main Config ([`dot_ssh/config`](dot_ssh/config:1)) - Includes colima SSH config - Includes all files from `~/.ssh/config.d/` - Global settings: ControlMaster, 1Password SSH agent, ForwardAgent #### Host Configurations | Host | Hostname | Port | Identity | |------|----------|------|----------| | [`github.com`](dot_ssh/config.d/github:1) | ssh.github.com | 443 | Default | | [`github-private`](dot_ssh/config.d/git-private:1) | ssh.github.com | 443 | github-private.pub | | [`github-zeronorth`](dot_ssh/config.d/git-zeronorth:1) | ssh.github.com | 443 | github-zeronorth.pub | | [`gitea-ssh.olsen.cloud`](dot_ssh/config.d/git-private:8) | gitea-ssh.olsen.cloud | 2202 | github-private.pub | | [`coder.*`](dot_ssh/config.d/git-coder:7) | (proxy) | - | Coder CLI | ### 3.4 Tmux Configuration ([`dot_tmux.conf`](dot_tmux.conf:1)) - Mouse enabled - 256-color terminal support - Vim-style keybindings (hjkl navigation) - TPM (Tmux Plugin Manager) with auto-install - Plugins: tmux-power, tmux-yank, tmux-sensible - Vim-tmux navigator integration - Custom bindings for splits, copy mode, lazygit popup ### 3.5 Aerospace Window Manager ([`dot_aerospace.toml`](dot_aerospace.toml:1)) - Starts at login - Tiling layout with gaps - QWERTY key mapping - Workspace navigation (alt+1-6) - Window movement (cmd+shift+hjkl) - Service mode for layout management - Floating layout for Elgato apps ### 3.6 Application Configs #### Atuin ([`dot_config/atuin/config.toml`](dot_config/atuin/config.toml:1)) ```toml style = "compact" keymap_mode = "vim-normal" ``` #### Jellyfin TUI ([`private_Library/private_Application Support/jellyfin-tui/private_config.yaml`](private_Library/private_Application Support/jellyfin-tui/private_config.yaml:1)) - Server: jellyfin.olsen.cloud - Username: morten - Password stored in separate file ### 3.7 Environment Variables (Work) From [`Projects/zeronorth/.envrc`](private_Projects/private_zeronorth/dot_envrc:1): ```bash 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 ``` Uses 1Password CLI references for secrets. --- ## 4. Homebrew Packages ### 4.1 Taps - `coder/coder` - `felixkratz/formulae` - `fluxcd/tap` - `nikitabobko/tap` - `sst/tap` ### 4.2 Formulae (All Environments) | Category | Packages | |----------|----------| | **Languages/Runtimes** | python@3.13, deno, rustup, pyenv, uv | | **Shell Tools** | zsh, atuin, starship, fzf, zoxide, direnv | | **File Utils** | bat, eza, fd, ripgrep, rsync, unzip | | **Git** | git, gh, git-delta, jj | | **Containers/K8s** | docker, docker-buildx, docker-compose, colima, kubernetes-cli, helm, helmfile, k9s, istioctl, flux | | **Dev Tools** | neovim, tmux, jq, graphviz, terraform, ansible, sshpass | | **Media** | ffmpeg | | **Security** | gnupg | | **Other** | curl, watch, coder, opencode, tree-sitter-cli | ### 4.3 Casks (All Environments) - **Terminal:** ghostty - **Productivity:** 1password, 1password-cli, raycast, obsidian - **Development:** dbeaver-community, lens - **Media:** jellyfin-media-player, ollama-app - **Window Management:** aerospace - **Networking:** localsend, mqtt-explorer - **Home Automation:** home-assistant ### 4.4 Casks (Personal Only) - darktable (photo editing) - signal (messaging) - proton-mail-bridge, proton-pass, protonvpn (Proton suite) - steam (gaming) --- ## 5. Recommended Nix Home Manager Structure ### 5.1 Flake Structure ``` flake.nix ├── flake.lock ├── home/ │ ├── default.nix # Common configuration │ ├── personal.nix # Personal-specific config │ ├── work.nix # Work-specific config │ └── modules/ │ ├── shell/ │ │ ├── zsh.nix │ │ ├── starship.nix │ │ ├── atuin.nix │ │ └── aliases.nix │ ├── git/ │ │ ├── default.nix │ │ ├── personal.nix │ │ └── work.nix │ ├── ssh/ │ │ ├── default.nix │ │ └── hosts.nix │ ├── tmux.nix │ ├── aerospace.nix │ ├── neovim.nix │ └── packages.nix └── docs/ └── migration-analysis.md ``` ### 5.2 Configuration Approach ```nix # flake.nix (simplified) { inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; # Darwin support for macOS nix-darwin = { url = "github:LnL7/nix-darwin"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, home-manager, nix-darwin, ... }: { homeConfigurations = { "personal" = home-manager.lib.homeManagerConfiguration { # Personal machine config modules = [ ./home/default.nix ./home/personal.nix ]; }; "work" = home-manager.lib.homeManagerConfiguration { # Work machine config modules = [ ./home/default.nix ./home/work.nix ]; }; }; }; } ``` ### 5.3 Module Mapping | Chezmoi File | Nix Module | Home Manager Option | |--------------|------------|---------------------| | `dot_zshrc` | `shell/zsh.nix` | `programs.zsh` | | `dot_tmux.conf` | `tmux.nix` | `programs.tmux` | | `dot_gitconfig.tmpl` | `git/default.nix` | `programs.git` | | `dot_gitignore_global` | `git/default.nix` | `programs.git.ignores` | | `dot_Brewfile.tmpl` | `packages.nix` | `home.packages` + homebrew module | | `dot_aerospace.toml` | `aerospace.nix` | `home.file` | | `dot_config/atuin/*` | `shell/atuin.nix` | `programs.atuin` | | `dot_ssh/*` | `ssh/default.nix` | `programs.ssh` | --- ## 6. Migration Challenges and Considerations ### 6.1 Homebrew Integration **Challenge:** Many casks are macOS-specific and not available in nixpkgs. **Solutions:** 1. Use `nix-darwin` with homebrew module for casks 2. Keep a minimal Brewfile for casks only 3. Use `home.packages` for CLI tools available in nixpkgs **Packages requiring Homebrew:** - All casks (1password, ghostty, aerospace, etc.) - Some taps (coder, flux, opencode) ### 6.2 1Password SSH Agent Integration **Challenge:** SSH config references 1Password agent socket. **Solution:** Use conditional paths or environment-specific SSH config: ```nix programs.ssh = { extraConfig = '' IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" ''; }; ``` ### 6.3 Tmux Plugin Manager (TPM) **Challenge:** TPM auto-installs from git, which doesn't fit Nix's declarative model. **Solutions:** 1. Use `programs.tmux.plugins` with nixpkgs tmux plugins 2. Package custom plugins as Nix derivations 3. Keep TPM but manage it outside Nix (hybrid approach) ### 6.4 NVM (Node Version Manager) **Challenge:** NVM is imperative and conflicts with Nix's approach. **Solutions:** 1. Use `programs.nodejs` with specific version 2. Use `nix-shell` or `direnv` with `use nix` for project-specific Node versions 3. Use `devenv` or `flake.nix` per-project ### 6.5 External Key Fetching **Challenge:** `.chezmoiexternal.toml` fetches SSH keys from GitHub. **Solutions:** 1. Create a derivation that fetches at build time (keys become stale) 2. Use a launchd agent to periodically update 3. Manual management with documentation ### 6.6 Private/Sensitive Files **Challenge:** Files prefixed with `private_` contain sensitive data. **Solutions:** 1. Use `sops-nix` for encrypted secrets 2. Use `agenix` for age-encrypted secrets 3. Keep sensitive files outside Nix, managed separately 4. Use 1Password CLI references (already used in `.envrc`) ### 6.7 Project-Specific Git Configs **Challenge:** Multiple git configs for different project directories. **Solution:** Use `programs.git.includes`: ```nix programs.git = { includes = [ { condition = "gitdir:~/Projects/zeronorth/"; path = "~/Projects/zeronorth/.gitconfig"; } ]; }; ``` ### 6.8 macOS-Specific Paths **Challenge:** Hardcoded paths like `/opt/homebrew/bin`, `/Applications/`. **Solution:** Use Nix variables and conditionals: ```nix home.sessionPath = [ "/opt/homebrew/bin" "$HOME/.local/bin" ]; ``` --- ## 7. Migration Phases ### Phase 1: Foundation 1. Set up flake structure with nix-darwin and home-manager 2. Create base configuration with common packages 3. Implement personal/work profile switching ### Phase 2: Shell Environment 1. Migrate zsh configuration 2. Set up starship, atuin, zoxide, direnv 3. Configure shell aliases and environment variables ### Phase 3: Development Tools 1. Migrate git configuration with conditional includes 2. Set up SSH configuration 3. Configure tmux (decide on TPM approach) 4. Set up neovim (if managed by chezmoi elsewhere) ### Phase 4: Applications 1. Configure aerospace (via home.file) 2. Set up application configs (atuin, jellyfin-tui) 3. Handle Homebrew casks via nix-darwin ### Phase 5: Secrets and Sensitive Data 1. Implement sops-nix or agenix for secrets 2. Migrate SSH keys 3. Handle 1Password integrations ### Phase 6: Testing and Validation 1. Test on personal machine 2. Test on work machine 3. Document any manual steps required --- ## 8. Summary ### Key Findings 1. **Two distinct profiles:** Personal and Work, differentiated by `.work` boolean 2. **Template usage:** Limited to Brewfile, gitconfig, SSH keys, and ignore rules 3. **External dependencies:** SSH authorized keys fetched from GitHub 4. **macOS-centric:** Heavy use of Homebrew casks, 1Password, aerospace 5. **Secrets:** 1Password CLI references used for work environment ### Recommended Approach 1. **Use nix-darwin** for macOS system configuration and Homebrew cask management 2. **Use home-manager** for user-level dotfiles and packages 3. **Implement profiles** using Nix modules with `mkIf` conditionals 4. **Keep Homebrew** for casks that aren't available in nixpkgs 5. **Use sops-nix** for managing sensitive configuration 6. **Gradual migration** - start with shell and git, then expand ### Files to Create ``` flake.nix # Main flake with darwin and home-manager home/default.nix # Common home-manager config home/personal.nix # Personal-specific settings home/work.nix # Work-specific settings home/modules/shell/zsh.nix # Zsh configuration home/modules/git/default.nix # Git configuration home/modules/ssh/default.nix # SSH configuration home/modules/tmux.nix # Tmux configuration home/modules/packages.nix # Package declarations