Chezmoi-driven, reset-ready macOS + Linux workstation. Source of truth for packages, shell, editors, terminal, app preferences, system defaults, secrets, and automation.
V10 architecture (May 2026): bash engine + per-domain catalog +
profile-cube persona detection. See docs/architecture.md
for the layered map and docs/adr/0001-v10-decisions.md
for the locked design decisions.
sh -c "$(curl -fsLS https://binghzal.github.io/dotfiles/setup.sh)"This:
- Installs Homebrew (macOS) or chezmoi (Linux/WSL via
get.chezmoi.io) - Runs
chezmoi init binGhzal/dotfiles - Prompts persona cube (4 bools + identity); skipped in CI/Codespaces
Set DOTFILES_APPLY=1 when you want the installer to run chezmoi init --apply. Containers and Codespaces set that automatically.
Either set dotfiles_repo: binGhzal/dotfiles in your Codespaces user
settings, or add to .devcontainer/devcontainer.json:
{
"postCreateCommand": "sh -c \"$(curl -fsLS https://binghzal.github.io/dotfiles/setup.sh)\""
}Auto-detects ephemeral env, forces non-interactive apply, skips secrets.
DOTFILES_OWNER=<you> DOTFILES_REPO=dotfiles \
sh -c "$(curl -fsLS https://binghzal.github.io/dotfiles/setup.sh)"| Layer | Tool |
|---|---|
| Dotfile manager | chezmoi 2.70+ |
| macOS packages | Homebrew (brew + cask + mas) |
| Linux packages | apt / dnf / pacman / zypper / apk (auto-detected) |
| Cross-platform runtimes | mise (node/go/python/rust/...) |
| Shell | zsh + zinit |
| Terminal | Ghostty / iTerm2 (macOS), zellij multiplexer |
| Editors | nvim / VS Code / Cursor |
| VCS | git (+ jj opt-in) |
| Secrets | 1Password + age (encrypted_*.age committed) |
| Automation | bin/pkgwatch + bin/autosync |
make help # list available targets
chezmoi --source="$PWD/home" diff
chezmoi --source="$PWD/home" apply -v
make defaults-all # apply all macOS settings (idempotent)
make defaults-dock # apply just dock domain
make validate-fast # fast source gate
make validate # clean-HOME dry-run gate
make lint # pre-commit
make test # shellcheck + bats
make brew-check # rendered Brewfile is installed
make catalog-list # entry counts per macOS domain
bin/pkgwatch list-pending # see brew packages awaiting promotion
bin/autosync dry-run # show policy-driven capture plan
bin/autosync status # inspect autosync state
bin/manual-actions # post-bootstrap human-required checklistPersona-driven conditionals. Set once at chezmoi init, persisted in
~/.config/chezmoi/chezmoi.toml:
| Flag | Default | Drives |
|---|---|---|
use_secrets |
true | 1Password + age decryption |
personal_computer |
false | Personal apps + install_personal_extras follow-up |
homelab_member |
false | Tailscale, homelab services |
dev_computer |
true | mise, gh, language runtimes |
install_personal_extras |
false | Heavy personal apps (gated by personal_computer) |
hyprland_flavor |
none | Linux desktop only; v2 hook |
Auto-detected (never prompted): is_ephemeral, is_codespaces,
is_devcontainer, is_wsl, is_darwin, is_linux, is_server,
has_gui, chassis.
Full reference: docs/profile-cube.md.
.
├── home/ # chezmoi source (.chezmoiroot points here)
│ ├── .chezmoi.toml.tmpl # config template + persona cube
│ ├── .chezmoiignore.tmpl # profile-aware path filter
│ ├── .chezmoidata/ # chezmoi data tree (auto-merged)
│ │ ├── 10-darwin/ # macOS catalog (per-domain TOML)
│ │ ├── 20-linux/ # Linux catalog (gsettings/wsl/server)
│ │ └── packages.yaml # package source of truth
│ ├── .chezmoiscripts/ # OS-gated wrappers (templated, thin)
│ │ ├── darwin/ # defaults + pkgwatch/autosync LaunchAgents
│ │ └── linux/ # pkg/gsettings/sshd/autosync wrappers
│ ├── .chezmoitemplates/lib/ # sourced bash function libraries
│ │ ├── apply_setting.sh.tmpl # 14-branch dispatcher
│ │ ├── gs.sh.tmpl # gsettings idempotency
│ │ ├── pkg.sh.tmpl # cross-distro pkg installer
│ │ └── log.sh.tmpl # leveled stderr
│ ├── dot_* # deployed dotfiles (zsh, git, etc.)
│ ├── private_dot_ssh/ # encrypted SSH config
│ └── Library/ # macOS-specific app configs
├── install/ # raw bash engine entry-points
│ ├── macos/common/apply-defaults.sh
│ └── linux/{common,desktop,server}/*.sh
├── bin/ # user-facing helpers
│ ├── pkgwatch # brew install detection daemon
│ ├── snapshot # pre-apply tar.zst backup
│ ├── autosync # policy-driven source capture
│ ├── autosync-push # one-release compatibility shim
│ ├── manual-actions # post-bootstrap checklist
│ ├── recapture-plists # reviewed plist recapture
│ ├── nerd-font-smoke-test
│ └── discover-default # GUI-tweak → catalog suggestion
├── tests/ # bats test suites
│ ├── agent-tooling.bats
│ ├── chezmoi/ # integration
│ └── bin/ # bin helpers
├── scripts/ # validation, render, import, archive
├── docs/ # architecture, profile-cube, runbooks, ADRs
├── .chezmoiroot # "home"
├── .chezmoiversion # "2.70.0"
├── Makefile # `make help` for full target list
├── setup.sh # thin bootstrap handoff
└── .github/ # CI workflows + CODEOWNERS + dependabot
Every chezmoi apply is gated by 3 layers:
run_onchange_*content hash — chezmoi only fires the wrapper when source content changes (incl. lib hash baked into script comment).- Engine dirty-set — only entries whose
defaults readdiffers from desired contribute to thekillallunion. - Lib
_apply_*branches — read-before-write per entry. Outputsunchangedorchanged: A -> B.
Result: second make defaults-all produces zero defaults write and zero killall. Verify with make test-idempotence.
| Doc | What |
|---|---|
docs/architecture.md |
Top-down layered map, idempotency contract |
docs/profile-cube.md |
Persona flag truth tables + jq inspection |
docs/adr/0001-v10-decisions.md |
V10 design decisions |
docs/runbooks/new-machine.md |
Bootstrap walkthrough (mac/linux/wsl/codespaces) |
docs/runbooks/recovery.md |
When chezmoi apply breaks something |
docs/runbooks/secret-rotation.md |
Age key rotation procedure |
docs/macos-defaults.md, docs/packages.md, etc. |
Current package/defaults surfaces |
-
Fork on GitHub
-
Edit
home/.chezmoidata/personal.toml:[personal] default_email = "you@example.com" default_name = "Your Name" default_github_user = "your-handle"
-
Edit
home/.chezmoidata/preferences.tomlfor your theme/editor preferences -
Tweak
home/.chezmoidata/10-darwin/<domain>.tomlper-domain catalog as needed -
Run
DOTFILES_OWNER=your-handle sh -c "$(curl -fsLS https://your-handle.github.io/dotfiles/setup.sh)"
If you fork from V4 (pre-PR1), inspect scripts/archive/v4-migration/; those
scripts are preserved history, not normal bootstrap steps.
- PR1 #11: Restructure to
home/chezmoi source root, dropdotCLI - PR2 #12: Bash engine replaces 715-line Python
dotfiles_settings.py - PR3 #13: 4-bool persona cube + 9 detected flags + ephemeral skip
- PR4 #14: Catalog migration from V4
settings.toml(65 entries across 7 domains) - PR5 #15: Linux engine scaffold (apt/dnf/pacman/zypper/apk + GNOME + WSL)
- PR6 #16: pkgwatch LaunchAgent + 7 bin/ helpers + hooks wiring
- PR7 #17: Slim setup.sh (208 → 111 lines), 8 ADRs, architecture, recovery runbook
- PR8-PR20: macOS catalog split, Linux render/apply CI, package/runtime ownership, agent-tooling source split, and reset-readiness docs
- PR21: Source-root resolver, real gates, snapshot safety, autosync V2, bridge untracking, package drift merge, and command-surface cleanup
- PR21 cleanup follow-up: Make cleanup targets, approved generated-cache cleanup, reviewed Homebrew removal-candidate uninstall, and CI action cleanup
MIT. See LICENSE (if present).