This is my setup for my Macbook Pro (M4 Pro) for development purposes.
Previously used on:
- MacBook Air (2020, M1)
bootstrap.sh applies the repo-managed version of these configs:
fish/fisher/git/ssh/omp/β Oh My Pi agent (~/.omp/agent)claude/β Claude Code (~/.claude)codex/β Codex CLI (~/.codex)skills/β agent skills, reinstalled from a manifestherdr/β herdr (~/.config/herdr) and its pluginsworktrunk/β Worktrunk (~/.config/worktrunk)btop/β btop (~/.config/btop)gh/β GitHub CLI (~/.config/gh)macos/β macOS system preferences (defaults)ghostty/β Ghostty terminal (~/.config/ghostty)zed/β Zed editor (~/.config/zed)gnupg/starship/
RayCast/ is only an encrypted backup artifact. It is not restored by bootstrap.sh.
Each tool is backed up as a curated, secret-free subset of its live config:
- OMP (
~/.omp/agent):AGENTS.md(the global agent rules, also linked as Claude Code's~/.claude/CLAUDE.md) andconfig.yml(with secret-bearing values such assearxng.token,searxng.basicPassword, andauth.broker.tokenblanked to""; booleans and numbers are left as-is). Provider credentials live in theagent.dbauth store, sessions and history are machine state, andmodels.ymlcan pin literal API keys β none of those are synced. - Claude Code (
~/.claude):settings.jsonand the herdr hook script it references.settings.jsonis stored with__HOME__in place of the home directory (hook commands embed absolute paths in quotes) andclaude/setup.shswaps it back.CLAUDE.mdis not copied; it is re-linked to~/.omp/agent/AGENTS.md, and a real file already there is kept asCLAUDE.md.bak.~/.claude.json(OAuth account, user id), history, sessions, caches, and plugin installs are machine state and are not synced; plugins re-install fromenabledPluginson launch. - Codex (
~/.codex):config.toml,hooks.jsonand the herdr hook script.update.shdrops the per-machine sections ofconfig.toml([projects.*]trusted paths,[hooks.state.*]trust hash,[tui.model_availability_nux]) and storeshooks.jsonwith__HOME__for the home directory.auth.json(login), sessions, history, and the sqlite databases are never synced. Runcodexand sign in on a new machine; it asks you to trust the session hook on first launch. - Skills (
~/.agents/skills, linked into~/.claude/skillsand other agents):skills/skills.listrecords each GitHub source and its skills, generated byupdate.shfrom~/.agents/.skill-lock.json(hashes and timestamps left out).skills/setup.shreinstalls them withpnpm dlx skills add -g; it needs the network and reports failures without stopping bootstrap. Skills are not copied into this repo. - Packages (
packages/):Brewfileis generated byupdate.shfrombrew bundle dump. App Store apps go toBrewfile.appstore(viamas) so that a Mac not yet signed in to the App Store only gets a warning instead of failing the wholebrew bundle. List app ids inpackages/appstore.skipto keep an installed app out of that file (currently Office and HP). Ifmasis not installed,update.shleavesBrewfile.appstoreuntouched. Prefer brew overgo install:buupgrades brew packages but never thegoentries. - herdr (
~/.config/herdr):config.toml(keybindings) plusplugins.list, one GitHubowner/repoper installed plugin, generated byupdate.shfromherdr plugin list --json.herdr/setup.shreinstalls them withherdr plugin install -yand reports failures without stopping bootstrap. Plugin folders,plugins.json, sockets, logs, and session snapshots are machine state. Locally linked plugins have no GitHub source and are not captured. - Worktrunk (
~/.config/worktrunk):config.toml. - btop (
~/.config/btop):btop.conf. Keepcolor_themeset to a theme name ("tomorrow-night"), not a path: a Cellar path like/opt/homebrew/Cellar/btop/<version>/...breaks on the next btop upgrade. - GitHub CLI (
~/.config/gh):config.ymlonly.hosts.ymlis login state; rungh auth loginon a new machine. - macOS settings (
macos/):keys.listis the curated list of<domain> <key>preferences (Dock, Finder, trackpad, appearance, clock, screenshots, window manager).update.shreads each live value intodefaults.list(tab-separated, with itsdefaultstype) andmacos/setup.shwrites them back withdefaults write, then restarts Dock, Finder and the menu bar. To track another setting, add a line tokeys.listand run./update.sh. Only scalar values are supported; arrays/dicts (e.g.AppleLanguages, keyboard shortcuts) and unset keys are skipped. Some keys (trackpad, global) need a log out and back in, andcom.apple.universalaccessmay need a privacy grant; failures warn without stopping bootstrap. The computer name is not captured. - Ghostty (
~/.config/ghostty):config.ghostty/setup.shalso sets Ghostty as the default terminal viaduti. - Zed (
~/.config/zed):settings.jsonandkeymap.json. The prompt-library database,settings_backup.json, andthemes/are excluded as machine state. No redaction is needed because Zed stores provider API keys in the macOS keychain, not insettings.json. Extensions are synced declaratively through theauto_install_extensionsblock insettings.json(Zed's recommended approach β it auto-installs them on launch).update.shregenerates that block on every run from the live installed-extensions directory (~/Library/Application Support/Zed/extensions/installed/), so just install or remove extensions in Zed and run./update.shβ no manual ID editing. The regeneration is a full rebuild, so manual"id": false("never install") pins are not preserved. The compiled extension binaries under~/Library/Application Support/Zed/are machine state and are not synced.
Because the repo is public, update.sh sanitizes on capture: it blanks secret-bearing
set -gx exports in fish/config.fish and secret values in omp/config.yml, so keys and
tokens never get committed.
Local install artifacts such as node_modules/, package manager files, and other machine-specific state are intentionally excluded.
These steps must be followed to ensure smooth installation:
bootstrap.sh prompts for sudo, installs Homebrew if it is missing, installs packages from packages/Brewfile, applies the managed config listed above, and sets fish as the login shell if needed.
Use this command to install the dotfiles setup:
./bootstrap.sh
Do not run
./bootstrap.shon a machine whose live config you want to keep. It overwrites~/.config/fish/config.fishand~/.omp/agent/config.ymlwith the repo copies, whose secret values (*_API_KEY, tokens) are blanked. Run./update.shfirst if you want the repo to match.
A few things bootstrap intentionally cannot restore:
-
API keys: the
set -gx *_API_KEY ""lines infish/config.fishare blanked. Re-enter them in~/.config/fish/config.fish(e.g.MORPH_API_KEY,FIRECRAWL_API_KEY,TINYFISH_API_KEY). -
GPG signing key: commits and tags are signed (
commit.gpgsign,tag.gpgSign) with the key ingit/.gitconfig/gnupg/gpg.conf. The private key is not in this repo. Import it (gpg --import) or generate a new one and updatesigningkeyanddefault-key; otherwisegit commitfails. -
SSH keys: keys live in Secretive (Secure Enclave) and cannot be exported. Create new ones in Secretive and register them on GitHub, then rebuild
~/.ssh/allowed_signers(referenced bygpg.ssh.allowedSignersFile; not stored here). -
ghlogin: rungh auth login(git usesghas its credential helper). -
OMP provider credentials:
omp/config.ymlships without them β stored credentials live in~/.omp/agent/agent.db(not in this repo). Runompand/login <provider>on a fresh machine. -
Raycast: import the encrypted backup from
RayCast/via the Raycast app (Settings β Advanced β Import). SeeRayCast/README.md.
Run this to sync the live machine back into the repo:
./update.sh
This builds a temp mirror of the managed live config, updates the Fisher manifest, and regenerates packages/Brewfile before applying the changes to the repo. If a managed live file or directory is missing, ./update.sh removes the corresponding repo snapshot on purpose. If snapshot or generation fails, the repo stays unchanged and the hidden repo-local .update.sh.* temp directory is cleaned up. If apply fails after changes start, ./update.sh tries to roll touched targets back; if rollback also fails, it reports that the repo may be partially updated and keeps that repo-local .update.sh.* artifacts path for inspection.
Run the regression tests with:
python3 -m unittest discover -s tests -p 'test_*.py' -v