Personal macOS development environment, managed declaratively with chezmoi (dotfiles), mise (CLI tools), and Homebrew (GUI apps). One command on a bare machine → a fully configured setup. Secrets never touch the repo.
git clone https://github.com/sanchpet/dotfiles ~/dotfiles && ~/dotfiles/bootstrap.shbootstrap.sh is idempotent and runs, in order (mise-first):
- mise — install the base tool manager (
curl https://mise.run), thenchezmoivia mise - chezmoi init — clone this repo into chezmoi's source
- apply mise config — lay down
~/.config/mise/config.tomlbefore installing tools (breaks the chicken-and-egg: the mise config is itself a managed dotfile) - mise install — install CLI tools from the config (bitwarden-cli, uv, …)
- Bitwarden unlock — only if the source contains
*.tmplsecrets (interactive) - Oh My Zsh — install the zsh framework (without touching
.zshrcor changing the shell) - chezmoi apply — render and place all dotfiles
- brew bundle — GUI casks (Homebrew is installed lazily, only if the Brewfile needs it)
| Tool | Purpose | Link |
|---|---|---|
| mise | Polyglot tool & runtime manager — single declarative source for CLI tooling | https://mise.jdx.dev · github |
| chezmoi | Dotfiles manager — templating, per-machine, secrets | https://www.chezmoi.io · github |
| Oh My Zsh | Zsh configuration framework | https://ohmyz.sh · github |
| Homebrew | macOS package manager — used only for GUI casks | https://brew.sh |
| Tool | Purpose | Link |
|---|---|---|
Bitwarden CLI (bw) |
Secret retrieval at chezmoi apply |
https://bitwarden.com/help/cli/ · github |
| uv | Fast Python package & project manager — also backs mise's pipx: tools (settings.pipx.uvx) |
docs · github |
Yandex Cloud CLI (yc) |
Manage Yandex Cloud resources (IAM, compute, k8s, …) | docs |
Claude Code (claude) |
Anthropic agentic CLI — self-update off (DISABLE_AUTOUPDATER), update via mise up claude |
docs |
GitHub CLI (gh) |
GitHub from the terminal | docs |
GitLab CLI (glab) |
GitLab from the terminal | docs |
| kubectl | Kubernetes cluster CLI | docs |
| kubectx | Switch kubectl context / namespace | github |
| node | Node.js runtime | docs |
| Starship | Cross-shell prompt (zsh prompt; starship init in .zshrc) |
docs |
| zoxide | Frecency cd — replaces cd (--cmd cd); cdi = interactive |
github |
| fzf | Fuzzy finder (fzf --zsh in .zshrc) |
github |
ripgrep (rg) |
Fast recursive search | github |
| bat | cat with syntax highlighting & paging (aliased to cat) |
github |
| eza | Modern ls — git-aware, colors (aliased to ls/ll/la/tree) |
github |
| delta | Syntax-highlighting pager for git diffs (wired as git core.pager) |
github |
| dust | Intuitive du — disk-usage tree (aliased to du) |
github |
| duf | Better df — disk free, tabular (aliased to df) |
github |
dua (dua i) |
Interactive disk-usage explorer — find & delete big dirs | github |
| fd | Fast, user-friendly find |
github |
| python | Python runtime | docs |
| helm | Kubernetes package manager | docs |
| terragrunt | Terraform/OpenTofu wrapper | docs |
| yq | YAML/JSON processor | github |
awscli (aws) |
AWS CLI | docs |
| go | Go toolchain | docs |
| terraform | Infrastructure as code | docs |
| vault | Secrets management CLI | docs |
flux2 (flux) |
GitOps continuous delivery for Kubernetes | docs |
| cfssl | Cloudflare PKI/TLS toolkit | github |
| typst | Markup-based typesetting (LaTeX alternative) | github |
ansible (ansible-core) |
IT automation engine — installed via uv (pipx: backend) |
docs |
| ansible-lint | Ansible playbook linter (via uv) | github |
| yamllint | YAML linter (via uv) | github |
| Tool | Purpose | Link |
|---|---|---|
| pre-commit | Git pre-commit hook framework | https://pre-commit.com · github |
| shellcheck | Static analysis for shell scripts (via shellcheck-py) |
shellcheck · hook |
| pre-commit-hooks | Standard hygiene hooks (whitespace, EOF, YAML, …) | github |
| Tool | Purpose | Profile | Link |
|---|---|---|---|
| Visual Studio Code | Primary code editor (self-updating; adopted into brew) | all | docs |
| Freelens | Kubernetes IDE (open-source Lens fork) | all | github |
| WakaTime | Menu-bar time tracker — whole-system activity beyond editor plugins | all | docs |
| Pearcleaner | App uninstaller + orphaned-file finder (open-source CleanMyMac alt) | all | github |
| .NET SDK | .NET toolchain | work only |
docs |
| Tool | Purpose | Profile | Link |
|---|---|---|---|
| sshpass | Non-interactive ssh password auth (used by ansible) — not in the mise registry | all | docs |
The prompt is Starship (dot_config/starship.toml — the kubernetes, aws
and terraform modules are on, so the active cluster / profile / workspace is always visible). Oh
My Zsh loads plugins only (theme off — Starship draws the prompt). Built-in plugins ship with
Oh My Zsh; external ones are cloned into $ZSH_CUSTOM/plugins by bootstrap.sh.
| Plugin | Source | Purpose |
|---|---|---|
| git | built-in | Git aliases (gst, gco, gp, …) |
| kubectl | built-in | k* aliases + completion (kgp, kgaa, kdp, …) |
| helm | built-in | Helm completion |
| terraform | built-in | tf* aliases + completion + workspace |
| aws | built-in | asp/acp profile switch + completion |
| ansible | built-in | Ansible aliases + completion |
| gh | built-in | GitHub CLI completion |
| colored-man-pages | built-in | Colored man pages |
| extract | built-in | x <archive> — extract any archive |
| sudo | built-in | Double-Esc prepends sudo |
| copypath / copybuffer | built-in | Copy $PWD / the current command line to the clipboard |
| dirhistory | built-in | Alt+←/→ directory history, Alt+↑ parent dir |
| forgit | external | fzf-powered git (ga, glo, gd) |
| zsh-completions | external | Extra completion definitions |
| zsh-autosuggestions | external | Fish-style suggestions from history |
| zsh-you-should-use | external | Reminds you when a typed command already has an alias |
| zsh-syntax-highlighting | external | Command-line syntax highlighting |
| zsh-autocomplete | external | Live menu completion (loaded last so its keybindings win) |
Load order matters.
zsh-autocompleteowns the completion/history UI, so it loads last, and plugins that fight over the same keys —fzf-tab,zsh-history-substring-search— are deliberately not used. Beyond the plugins,dot_zshrc.tmpladds custom aliases (kg,kgy,kctx; modern-CLI swapscat→bat,ls→eza,du→dust,df→duf) and themiseg/misermhelpers (add / remove a global mise tool and re-import the config).
| Path | Role |
|---|---|
dot_* |
Dotfiles rendered into $HOME by chezmoi (e.g. dot_gitconfig → ~/.gitconfig) |
dot_config/mise/config.toml |
Global mise config → ~/.config/mise/config.toml (user CLI tools) |
dot_config/starship.toml |
Starship prompt config → ~/.config/starship.toml (kubernetes/aws/terraform modules) |
dot_zshrc.tmpl |
~/.zshrc — Oh My Zsh (plugins only) + Starship prompt + zoxide + mise + aliases (kubectl, modern CLI); secrets pending |
dot_local/bin/executable_cleanup |
~/.local/bin/cleanup — disk-reclaim tool (reports by default; --apply deletes Tier 1 caches + orphan caches of removed tools, --deep adds Go modcache) |
.chezmoi.toml.tmpl |
Generates per-machine chezmoi config at init (prompts profile); never deployed |
bootstrap.sh |
Bare-machine bootstrap (operational, not deployed) |
Brewfile.tmpl |
GUI casks for brew bundle, templated per profile (operational; rendered at bootstrap) |
mise.toml |
Repo-local dev tooling (pre-commit) |
.pre-commit-config.yaml |
Lint hooks (shellcheck + hygiene) |
.chezmoiignore |
Keeps operational files in the repo but out of $HOME |
- chezmoi over GNU Stow / bare-git. Needed templating (per-machine values), first-class
secret handling, and a source tree where dotfiles stay visible (
dot_prefix) instead of hidden. Stow only symlinks; bare-git has no templating or secrets. - mise-first for CLI tools. All CLI tooling is declared in mise (
config.toml), versioned and cross-machine. Homebrew is reserved for what mise can't provide — GUI casks, plus the rare CLI with heavy native deps or no upstream release (e.g.sshpass). This keeps the toolchain reproducible and the Brewfile minimal. - Bitwarden for secrets. Secrets are pulled from Bitwarden at
chezmoi applyvia{{ bitwarden ... }}templates — nothing secret (encrypted or otherwise) lives in this public repo. Trade-off: bootstrap needs an interactivebw unlockbefore applying secret-bearing files (vs.age/secrets.env, which keep apply offline but place material in/near the repo). - pre-commit + shellcheck. Every commit lints shell scripts and runs hygiene checks, so
bootstrap.shand friends stay correct. pre-commit itself is installed via mise (postinstallwires the git hooks automatically). - Bootstrap ordering. The mise config is itself a managed dotfile, so it is applied before
mise installto break the chicken-and-egg; Homebrew is installed lazily, only when GUI casks are present. - Per-machine via
profile, not per-machine directories. One source tree; machine-specific variation is driven by a singleprofilevalue (work/personal), prompted once atchezmoi init(override in CI/headless withDOTFILES_PROFILE) and stored in the machine-local chezmoi config (never in this repo). Templates branch on it —Brewfile.tmplinstalls the .NET SDK only whenprofile == "work", anddot_gitconfig.tmplselects the work vs personal git identity. This keeps a single declarative source of truth and avoids the duplication/drift of per-machine dirs.
chezmoi has two locations: the source (this repo, chezmoi source-path) and the live files
in $HOME. Always edit the source, then push it to live — never edit the live file directly.
| Scenario | Command |
|---|---|
Changed a dotfile (e.g. .zshrc) |
edit the source (dot_zshrc.tmpl), then chezmoi apply ~/.zshrc (alias cza) |
| Pull latest on another machine | chezmoi update (= git pull + apply) (alias czu) |
| Check source ↔ live drift | chezmoi diff (alias czd) |
A tool wrote to a non-templated target (e.g. mise use -g → ~/.config/mise/config.toml) |
re-import: chezmoi add <target> (see the miseg helper) |
Never run
chezmoi add ~/.zshrc. It is a template (dot_zshrc.tmpl) —addwould overwrite it with the rendered content and destroy the{{ ... }}directives (incl. future secrets). Templated files are source-edited only;chezmoi addis for non-templated targets.
Secrets are never committed. They are resolved at apply time from
Bitwarden via chezmoi templates. On a fresh machine, bootstrap.sh
prompts for bw unlock only when the source actually contains secret templates.
The zsh config (dot_zshrc.tmpl) is kept as a .tmpl so a {{ bitwarden ... }} secret line can
be added later without a rename — see Zsh shell for the plugin set and
prompt.
Pending: the
OBSIDIAN_API_KEYsecret reference (via Bitwarden) is not wired yet —.zshrcis kept as a.tmplso the{{ bitwarden ... }}line can be added without a rename.