ADR-004: Chezmoi + Custom CLI Wrapper Architecture¶
Status: Accepted Date: 2026-02-09 Authors: @sebastienrousseau
Context¶
Managing dotfiles requires:
- Tracking file changes and applying them consistently
- Handling platform-specific configurations
- Supporting encrypted secrets
- Providing a good developer experience
Options considered:
- Bare git repository: Simple but poor UX, no templating
- GNU Stow: Symlink-based, limited features
- Chezmoi only: Powerful but complex CLI
- Custom from scratch: High maintenance burden
- Chezmoi + wrapper: Best of both worlds
Decision¶
Use Chezmoi as the core engine with a custom dot CLI wrapper that:
Architecture¶
┌─────────────────────────────────────────────┐
│ dot CLI │
│ (User-friendly interface, custom commands) │
├─────────────────────────────────────────────┤
│ Command Modules │
│ core │ diagnostics │ tools │ appearance │
│ secrets │ security │ meta │
├─────────────────────────────────────────────┤
│ Shared Library │
│ utils.sh (resolve_source_dir, run_script) │
├─────────────────────────────────────────────┤
│ Chezmoi │
│ (Template engine, state management, apply) │
└─────────────────────────────────────────────┘
Core Principles¶
1. Chezmoi handles complexity:
- Template rendering with Go text/template
- Encrypted secrets with age
- State tracking (what's applied vs source)
- Cross-platform path handling
2. dot CLI handles UX:
- Memorable command names (
dot syncvschezmoi apply) - Domain-specific commands (
dot doctor,dot theme) - Integration with external tools (Nix, Docker, Neovim)
- Consistent help and error messages
3. Modular command structure:
scripts/dot/
├── lib/
│ └── utils.sh # Shared functions
└── commands/
├── core.sh # apply, sync, update, add, diff
├── diagnostics.sh # doctor, heal, health, benchmark
├── tools.sh # tools, new, packages
├── appearance.sh # theme, wallpaper, fonts
├── secrets.sh # secrets-init, secrets
├── security.sh # firewall, backup, encrypt-check
└── meta.sh # upgrade, docs, learn
4. Delegation pattern:
# Main dispatcher in dot CLI
dispatch() {
local module="$1" cmd="$2"
shift 2
exec bash "$src_dir/scripts/dot/commands/$module.sh" "$cmd" "$@"
}
Chezmoi Integration Points¶
| Feature | Chezmoi | dot CLI |
|---|---|---|
| Apply changes | chezmoi apply |
dot sync |
| View diff | chezmoi diff |
dot diff |
| Edit secrets | chezmoi edit --encrypted |
dot secrets |
| Source directory | chezmoi source-path |
dot cd |
| Health check | chezmoi doctor |
dot doctor (extended) |
Extension Points¶
Custom commands can:
- Wrap chezmoi commands with better defaults
- Add entirely new functionality (benchmarks, themes)
- Integrate with system tools (nix, docker, brew)
- Provide interactive experiences (tour, learn)
Consequences¶
Positive¶
- Leverage Chezmoi's battle-tested engine
- User-friendly interface for common tasks
- Easy to add domain-specific commands
- Modular structure enables testing and maintenance
- Single entry point (
dot) for all operations
Negative¶
- Two layers to understand (chezmoi + dot)
- Version coupling between chezmoi and scripts
- Some chezmoi features not exposed via dot
Neutral¶
- Advanced users can still use chezmoi directly
- Documentation needed for both layers
- Upgrade path when chezmoi adds new features
Implementation Notes¶
Adding a New Command¶
- Identify the appropriate module (or create new one)
- Add function
cmd_<name>()to module - Add case to module's dispatch
- Add case to main dot CLI dispatcher
- Update help text
- Add tests if complex
Module Template¶
#!/usr/bin/env bash
# Dotfiles CLI - <Category> Commands
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/utils.sh"
cmd_example() {
# Implementation
}
case "${1:-}" in
example) shift; cmd_example "$@" ;;
*) echo "Unknown command: ${1:-}" >&2; exit 1 ;;
esac