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

16 KiB

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:

[".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:

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:

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:

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:

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)

  • 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 PATH setup, aliases (ls→eza, cat→bat, grep→rg, diff→delta, less→bat)
01-nvim.sh Sets EDITOR=nvim, alias vim=nvim
05-nvm.sh NVM initialization

3.2 Git Configuration

Global Config (dot_gitconfig.tmpl)

  • 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 (Personal) fbtijfdq@void.black Personal key github-private, gitea.olsen.cloud
Projects/private/.gitconfig (Work) fbtijfdq@void.black Work key github-private, gitea.olsen.cloud
Projects/zeronorth/.gitconfig (Work) morten.olsen@zeronorth.com ZeroNorth key github-zeronorth

3.3 SSH Configuration

Main Config (dot_ssh/config)

  • 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 ssh.github.com 443 Default
github-private ssh.github.com 443 github-private.pub
github-zeronorth ssh.github.com 443 github-zeronorth.pub
gitea-ssh.olsen.cloud gitea-ssh.olsen.cloud 2202 github-private.pub
coder.* (proxy) - Coder CLI

3.4 Tmux Configuration (dot_tmux.conf)

  • 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)

  • 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)

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:

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.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

# 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:

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:

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:

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
  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