macOS · Apple Silicon · 2026  ·  CI

dotfiles

Automated macOS dev environment — reproducible from scratch in one command.

Homebrew GNU Stow mise zinit starship Ansible

Everything you need, nothing you don't

A curated, pinned set of tools assembled over time — automated with Ansible and wired together with GNU Stow.

🍺

Homebrew + Brewfile

Every package declared in mac/Brewfile with pinned versions and inline docs. --no-upgrade prevents drift. Critical tools are brew pin'd.

50+ packages
🗂️

GNU Stow

Dotfiles live in the repo. Stow creates symlinks into $HOME. Conflict-safe: existing files are backed up to *.pre-stow before linking.

Zero copy

zinit — fast shell

Replaces oh-my-zsh entirely. Loads plugins in parallel, uses OMZ snippets without the framework overhead. 3× faster shell startup.

v3.14.0
🔀

mise — runtime manager

One tool replaces nvm, sdkman, pyenv, goenv. Node, Python, Go pinned in .config/mise/config.toml. Per-project overrides via .mise.toml.

v2026.5.x
🚀

starship prompt

Cross-shell prompt showing git branch/status, active k8s context, runtime versions (node/python/go/rust), AWS profile, and command duration.

v1.25.1
📦

Ansible playbook

One playbook wires everything: installs packages, configures ZSH, pins runtimes. Idempotent — safe to re-run. Works on fresh Mac and existing setups.

Idempotent

Up and running in minutes

Three steps from zero to a fully configured dev environment.

01

Prerequisites

Xcode Command Line Tools must be installed first. The install script checks for this and exits cleanly if missing.

xcode-select --install
02

Clone & run

Clone the repo anywhere, then run the bootstrap script. It installs Homebrew, Ansible, all packages, and stows your dotfiles.

# clone
git clone https://github.com/madhank93/dotfiles ~/dotfiles

# bootstrap
cd ~/dotfiles && ./install.sh
03

Install runtimes

After reopening your shell, install the pinned Node, Python, and Go versions via mise.

mise install

See it run

Three key moments — install, first shell, runtime setup.

install.sh
./install.sh
Xcode CLT found
Homebrew 4.x ready
brew install ansible
Running Ansible playbook...
TASK: Install packages from Brewfile
50 packages installed
Dotfiles stowed to ~
Done. Open a new shell, then run: mise install
~ — zsh (new session)
zinitloading plugins...
zsh-users/zsh-autosuggestions
zsh-users/zsh-syntax-highlighting
Aloxaf/fzf-tab
starship prompt active
atuin history ready
zoxide loaded
mise node 22.4.0 active
~/dotfiles
on main
~ — mise install
mise install
node 22.4.0
python 3.13.7
go 1.25.1
All runtimes installed
node -v
v22.4.0
python3 --version
Python 3.13.7
go version
go1.25.1 darwin/arm64

Tools & versions

All packages pinned. Versions in mac/Brewfile and .config/mise/config.toml.

Shell

zinit
Plugin manager, replaces oh-my-zsh
3.14.0
starship
Cross-shell prompt
1.25.1
atuin
Encrypted shell history
18.16.1
zoxide
Smarter cd with frecency
0.9.9
direnv
Per-directory env vars
2.37.1

Git

lazygit
Full TUI — stage, rebase, log
0.62.1
git-delta
Syntax-highlighted diffs
0.19.2
gh
GitHub CLI
2.93.0

Search & Navigation

fzf
Fuzzy finder
0.73.1
fd
Better find
10.4.2
ripgrep
Better grep
15.1.0
navi
Interactive cheatsheets
2.24.0

Kubernetes

k9s
Kubernetes TUI
0.50.18
kubectx
Context & namespace switcher
0.11.0
stern
Multi-pod log tailing
1.34.0
helm
Kubernetes package manager
4.2.0

System

bottom
System monitor TUI (btm)
0.12.3
dust
Visual disk usage (du)
1.2.4
procs
Searchable process list (ps)
0.14.11
duf
Disk usage overview (df)
0.9.1

Runtimes (mise)

node
LTS — replaces nvm
22.4.0
python
Replaces pyenv / pipenv
3.13.7
go
Replaces goenv
1.25.1

Detailed walkthrough

Everything that happens under the hood, step by step.

1

Xcode Command Line Tools

Required by Homebrew for compiler toolchain. The install script checks for CLT and exits with instructions if missing.

⚠ This triggers a macOS GUI prompt. Must be done before running install.sh.
xcode-select --install
# Verify: xcode-select -p
2

Run install.sh

The script auto-detects Apple Silicon vs Intel, installs Homebrew if missing, installs Ansible, runs the Ansible playbook, backs up conflicting dotfiles, then stows symlinks.

git clone https://github.com/madhank93/dotfiles ~/dotfiles
cd ~/dotfiles && ./install.sh
# Prompts once for sudo password (for chsh + /etc/shells)
ℹ Existing dotfiles are backed up to *.pre-stow, not deleted.
3

Ansible provisions packages

The playbook runs brew bundle install --no-upgrade against mac/Brewfile. All 50+ packages install. Critical tools are pinned to prevent accidental brew upgrade breakage.

# Pinned: kubernetes-cli, helm, awscli, mise, neovim
brew bundle check --file=mac/Brewfile

# To upgrade a pinned package:
brew unpin kubernetes-cli && brew upgrade kubernetes-cli && brew pin kubernetes-cli
4

GNU Stow creates symlinks

Stow maps the dotfiles repo into $HOME. Files listed in .stow-local-ignore are skipped (mac/, linux/, windows/, README.*).

# Re-stow after any change to dotfiles:
stow --target=$HOME --restow .

# Verify symlinks:
ls -la ~/.zshrc # should show → ~/dotfiles/.zshrc
5

First shell — zinit loads plugins

On the first shell open, zinit clones three plugins from GitHub (requires internet, one-time, ~5s). Subsequent starts load from cache.

# Plugins loaded:
zinit light zsh-users/zsh-autosuggestions
zinit light zsh-users/zsh-syntax-highlighting
zinit light Aloxaf/fzf-tab

# OMZ snippets (no full framework):
zinit snippet OMZP::git # ga, gst, gco, gp…
zinit snippet OMZP::sudo # ESC+ESC → prepend sudo
6

Install runtimes with mise

mise manages Node, Python, and Go. Versions are pinned in .config/mise/config.toml. Brew keeps the same runtimes installed as dependencies for other tools — mise shims shadow them on PATH automatically.

mise install # installs node, python, go, npm:ts-node
mise ls # verify all runtimes
ℹ Do NOT brew uninstall node python@3.13 go — they are deps for awscli, cdk8s, yt-dlp. mise shims override them on PATH.

Global npm packages declared in .config/mise/config.toml are installed automatically by mise install and reinstalled on node version upgrades — no manual npm install -g needed.

# Add more npm globals (persisted in mise config):
mise use --global npm:some-tool

# cdk8s + typescript already in Brewfile — no npm install needed:
cdk8s --version # from brew
tsc --version # from brew

# Go tools (manual, per-machine):
go install golang.org/x/tools/gopls@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest