macOS setup as code. Clone, run one command, and a bare Mac comes up as my machine — shell, git, editor, terminal, apps, and system preferences, all configured the way I like them.
Built to be idempotent (safe to re-run), interactive where it matters
(you pick which apps), and honest about secrets (none live here — see
SECRETS.md).
# 1. Sign into 1Password first — secret + SSH restore depend on it.
# 2. Clone over HTTPS (a fresh Mac has no SSH key yet):
git clone https://github.com/martinlaws/dotfiles.git ~/dotfiles
cd ~/dotfiles
# 3. Run the one entrypoint:
sh setupThat's it. setup detects your architecture, installs Xcode Command Line Tools
and Homebrew, then walks through each phase below. Run it again any time — it
flips into update mode and refreshes rather than redoing first-time setup.
Bringing up a fresh Mac? Follow the interactive checklist at martinlaws.github.io/dotfiles — tick steps off as you go (progress saves in the browser, every command has a copy button). Same runbook in plain text:
FIRST-RUN.md.
| Phase | What happens |
|---|---|
| 1 · Foundation | Xcode CLT, Homebrew, CLI tools from config/Brewfile, Node LTS pinned via fnm |
| 2 · Dev config | Symlinks shell / terminal / editor / ssh configs (via GNU Stow), generates ~/.gitconfig from a template, sets up SSH |
| 3 · Apps & system | Installs GUI apps from config/Brewfile.apps (all / by category / pick-and-choose), reinstalls editor extensions, applies opinionated macOS defaults |
| 4 · Claude & tooling | Restores my private Claude Code config into ~/.claude, builds the hey CLI from source, clones my repos (list in ~/.claude/repos.txt), and loads the chaos autosave agent (snapshots my knowledge repo to a safety branch) |
A couple of steps stay manual by design — setup will prompt you:
- Git identity — it asks for the name/email to stamp into
~/.gitconfig. - SSH key on GitHub — see the SSH note below; you'll paste a public key into github.com/settings/keys.
And after it finishes:
- Restore secrets — follow
SECRETS.md(1Password +vercel env pull). - Re-auth Claude Code — sign in once; it regenerates its own credentials.
gh auth login— re-authenticate the GitHub CLI; its token doesn't migrate.- Mac App Store apps — sign into the App Store, then
brew bundle --file=config/Brewfile.mas. - Caps Lock → Esc — System Settings → Keyboard → Modifier Keys. (The one thing macOS won't let me script cleanly.)
Shell — zsh + Starship prompt. History, navigation,
and listing are upgraded with a modern CLI kit, each wired into .zshrc behind a
command -v guard so the shell never errors if a tool isn't installed yet:
| Tool | What it does |
|---|---|
atuin |
Shell history → searchable, synced SQLite (Ctrl-R) |
zoxide |
cd that learns — z <partial> from anywhere |
eza |
ls with icons, git status, tree view |
bat |
cat with syntax highlighting |
fzf |
Fuzzy finder (Ctrl-T / Ctrl-R / Alt-C) |
ripgrep |
Fast recursive search (rg) |
git-delta |
Git's pager + diff filter, made readable |
Node — managed by fnm only. .zshrc runs
fnm env --use-on-cd (auto-switch per directory), and setup pins lts-latest as
the default so a fresh machine has a working node/npm immediately.
Git — sensible aliases, delta for diffs, zdiff3 conflict style, and a
~/.git-template that seeds every git init.
There's no per-machine SSH key to manage. The committed ~/.ssh/config points
SSH at the 1Password SSH agent (IdentityAgent), so a single key lives in
1Password and follows me to any machine — nothing private ever touches disk.
scripts/setup-ssh.sh checks the agent: if it already holds a key, setup skips
key generation entirely. If 1Password isn't present (or the key hasn't been added
yet), it falls back to minting a local ~/.ssh/id_ed25519 and prints the public
half to add to GitHub. Either way you end up connected.
The same key also signs my git commits — via 1Password's op-ssh-sign, wired
in the gitconfig template — so GitHub shows them Verified.
setup ← the only entrypoint: sh setup
config/
Brewfile ← CLI formulae
Brewfile.apps ← GUI casks, grouped by category
Brewfile.mas ← Mac App Store apps (via mas)
launchd/ ← LaunchAgent templates (chaos autosave)
dotfiles/ ← Stow packages, symlinked into ~
shell/ .zshrc · .vimrc · .config/starship.toml
git/ .gitconfig.template · .gitignore_global · .git-template/
terminal/ .config/ghostty/config · .hyper.js
editors/ VS Code settings
ssh/ .ssh/config
bin/ ~/.bin personal scripts (chaos autosave)
wm/ .config/aerospace/aerospace.toml (window manager)
scripts/ ← one file per concern (+ lib/ helpers)
docs/ ← interactive first-run checklist (GitHub Pages)
FIRST-RUN.md ← step-by-step new-machine runbook
SECRETS.md ← how to restore secrets (no secrets in here)
Nothing sensitive lives in this repo — that's the whole point, and it's why I'm
comfortable making it public. 1Password is the source of truth; runtime
secrets land in per-project .env.local files (gitignored), and Vercel-managed
projects pull their own. The full restore playbook is in SECRETS.md.
.gitignore blocks *.local and .claude/ as a backstop. Anything
machine-specific goes in a .local file; anything private goes in a private
repo. The public repo stays clean by construction.
martinlaws/claude-config(private) — my Claude Code skills, agents, hooks, and memory; restored into~/.claudebyscripts/setup-claude.sh.