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.fileand 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_DIRandLANG - Sources scripts from
~/.shellrc/rc.d/*.shand~/.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 | 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/coderfelixkratz/formulaefluxcd/tapnikitabobko/tapsst/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
# 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:
- Use
nix-darwinwith homebrew module for casks - Keep a minimal Brewfile for casks only
- Use
home.packagesfor 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:
- Use
programs.tmux.pluginswith nixpkgs tmux plugins - Package custom plugins as Nix derivations
- 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:
- Use
programs.nodejswith specific version - Use
nix-shellordirenvwithuse nixfor project-specific Node versions - Use
devenvorflake.nixper-project
6.5 External Key Fetching
Challenge: .chezmoiexternal.toml fetches SSH keys from GitHub.
Solutions:
- Create a derivation that fetches at build time (keys become stale)
- Use a launchd agent to periodically update
- Manual management with documentation
6.6 Private/Sensitive Files
Challenge: Files prefixed with private_ contain sensitive data.
Solutions:
- Use
sops-nixfor encrypted secrets - Use
agenixfor age-encrypted secrets - Keep sensitive files outside Nix, managed separately
- 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
- Set up flake structure with nix-darwin and home-manager
- Create base configuration with common packages
- Implement personal/work profile switching
Phase 2: Shell Environment
- Migrate zsh configuration
- Set up starship, atuin, zoxide, direnv
- Configure shell aliases and environment variables
Phase 3: Development Tools
- Migrate git configuration with conditional includes
- Set up SSH configuration
- Configure tmux (decide on TPM approach)
- Set up neovim (if managed by chezmoi elsewhere)
Phase 4: Applications
- Configure aerospace (via home.file)
- Set up application configs (atuin, jellyfin-tui)
- Handle Homebrew casks via nix-darwin
Phase 5: Secrets and Sensitive Data
- Implement sops-nix or agenix for secrets
- Migrate SSH keys
- Handle 1Password integrations
Phase 6: Testing and Validation
- Test on personal machine
- Test on work machine
- Document any manual steps required
8. Summary
Key Findings
- Two distinct profiles: Personal and Work, differentiated by
.workboolean - Template usage: Limited to Brewfile, gitconfig, SSH keys, and ignore rules
- External dependencies: SSH authorized keys fetched from GitHub
- macOS-centric: Heavy use of Homebrew casks, 1Password, aerospace
- Secrets: 1Password CLI references used for work environment
Recommended Approach
- Use nix-darwin for macOS system configuration and Homebrew cask management
- Use home-manager for user-level dotfiles and packages
- Implement profiles using Nix modules with
mkIfconditionals - Keep Homebrew for casks that aren't available in nixpkgs
- Use sops-nix for managing sensitive configuration
- 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