Personal Mac bootstrap: Homebrew, shell, dotfiles, AI agent rules, infra tools, and ~/myLab repo sync.
Repos live under ~/myLab/ (each project is usually its own git repo). initMe itself is ~/myLab/initMe.
Targets: macOS (bootstrap.sh) and Raspberry Pi OS (bootstrap-pi.sh). There is no single cross-platform bootstrap; each script refuses the wrong OS.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/apoorv-kulkarni/initMe/HEAD/install-macos.sh)"Then clone the rest of your repos (or let bootstrap do it automatically):
cd ~/myLab/initMe
bash clone-mylab.sh # optional if bootstrap already ran step 18
exec zshThe curl installer leaves initMe without .git until bootstrap converts it to a real
clone (step 17). sync-repos.sh cannot update initMe until then. After gh auth login,
bootstrap handles conversion automatically, or use git clone instead of the curl path
if you prefer a git checkout from the start.
git clone git@github.com:apoorv-kulkarni/initMe.git ~/myLab/initMe
cd ~/myLab/initMe
./install.sh # symlinks configs only
exec zshRun bash bootstrap.sh (macOS) or bash bootstrap-pi.sh (Pi) if you still need Homebrew packages, launchd sync, etc.
bootstrap.sh installs packages, changes macOS system defaults (with a prompt), registers a launchd job, and may generate SSH keys. --dry-run previews file/system mutations and skips Homebrew/pyenv environment initialization.
bash bootstrap.sh --dry-runOr preview the curl installer path:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/apoorv-kulkarni/initMe/HEAD/install-macos.sh)" -- --dry-runinitMe/
├── AGENTS.md # initMe-specific agent instructions
├── CLAUDE.md # @AGENTS.md for Claude Code in this repo
├── install-macos.sh # curl | bash entry (Homebrew-style)
├── install.sh # Symlink dotfiles into ~
├── bootstrap.sh # Full macOS setup (brew bundle, launchd, …)
├── bootstrap-pi.sh # Raspberry Pi setup
├── clone-mylab.sh # Clone personal repos into ~/myLab
├── sync-repos.sh # Fast-forward main/master only under ~/myLab
├── git/
│ └── gitconfig # Shared git behavior (included from ~/.gitconfig)
├── agent/ # Canonical agent rules (plain .md)
├── adapters/ # Claude Code entry point template
├── templates/ # Workspace AGENTS.md template → ~/myLab/
├── scripts/ # build-agent-adapters.sh
├── .github/workflows/ # CI (shellcheck on *.sh)
├── .shellcheckrc # ShellCheck defaults
├── Brewfile # Homebrew formulae and casks
├── zshrc # Shell config → ~/.zshrc
├── p10k.zsh # Powerlevel10k → ~/.p10k.zsh
├── ssh_config # GitHub SSH → ~/.ssh/config
├── gitignore_global # Global gitignore → core.excludesfile
├── mylab.cursorignore.example
└── vscode-extensions-list.txt
| Step | macOS | Pi |
|---|---|---|
| Core packages | brew bundle (Brewfile) |
apt + manual binaries |
| Languages | Go, Python (pyenv) | Go, Python (pyenv) |
| Infrastructure | Terraform, Vault (hashicorp/tap) |
Terraform (tfenv) |
| Kubernetes | kubectl, kubectx, kubelogin, k9s, minikube | kubectl, kubectx, k9s |
| Shell | oh-my-zsh + Powerlevel10k + plugins | oh-my-zsh + Powerlevel10k + plugins |
| Dotfiles | zshrc, p10k, SSH, gitconfig include, global gitignore |
zshrc + SSH symlinked |
| Agent rules | agent/ → ~/.cursor/rules/, ~/.claude/CLAUDE.md, ~/myLab/AGENTS.md |
— |
| SSH | id_ed25519 key + keychain; ssh_config for GitHub |
id_ed25519 + shared ssh_config |
| GitHub CLI | Install + gh auth login |
Install + gh auth login |
| macOS defaults | Finder, key repeat, Dock, screenshots (prompted) | — |
| iTerm2 | Profile imported from repo | — |
| Editors | VS Code + Cursor (casks); VS Code extensions from list | — |
| Repo sync | launchd every 6h: ff-only main/master under ~/myLab |
cron every 6h |
| CI | ShellCheck on push (.github/workflows/shellcheck.yml) |
— |
All steps are idempotent — safe to re-run if something fails partway through.
sync-repos.sh never touches feature branches. Preview:
bash ~/myLab/initMe/sync-repos.sh --dry-run- macOS only:
bootstrap.shandinstall-macos.sh(exit on Linux) - Pi / Linux only:
bootstrap-pi.sh(apt, cron sync; no Homebrew or macOS defaults) - Both:
install.sh,zshrc,sync-repos.sh,clone-mylab.sh,ssh_config
zshrc is shared; platform-specific bits use uname checks (e.g. ls colors, VS Code PATH). ssh_config uses IgnoreUnknown UseKeychain so macOS keychain options do not break OpenSSH on Linux/Pi.
Re-running is intended to be safe: Homebrew bundle upgrades, oh-my-zsh skips if present, SSH keygen only when no key exists, launchd plist skipped if already installed, macOS defaults gated by a marker file (and an interactive prompt on first run). Git name/email/GPG are prompted only when user.name is unset.
Not in this repo. .gitignore and gitignore_global block keys, .env, and common credential files.
On a fresh machine, bootstrap will:
- Generate or import an SSH key (
~/.ssh/id_ed25519) and load it into the macOS keychain (Pi: keygen without keychain) - Symlink
ssh_configto~/.ssh/config(backs up a plain file to~/.ssh/config.bak.<timestamp>first) - Run
gh auth loginfor GitHub - Prompt for git name / email / optional GPG key when
user.nameis not set (stored in~/.gitconfig, not in this repo)
ssh_config points GitHub at id_ed25519 (bootstrap default), then
gigithub_2024 and github_rsa as fallbacks. OpenSSH skips missing keys.
IdentitiesOnly yes limits which keys are offered to GitHub.
Terraform and Vault install via HashiCorp’s official Homebrew tap (hashicorp/tap, declared as trusted in the Brewfile). Log into Vault manually when you need it (vault login).
Bootstrap prompts for a signing key ID on first run. To enable later:
git config --global user.signingkey <KEY_ID>
git config --global commit.gpgsign true
git config --global gpg.program gpg # or /opt/homebrew/bin/gpg on Apple SiliconCanonical rules live in agent/ (plain Markdown). install.sh runs
scripts/build-agent-adapters.sh to install tool-specific adapters:
| Tool | Installed path |
|---|---|
| Cursor | ~/.cursor/rules/*.mdc (generated from agent/manifest.tsv) |
| Claude Code | ~/.claude/CLAUDE.md |
| Workspace index | ~/myLab/AGENTS.md and ~/myLab/CLAUDE.md (from adapters/mylab-CLAUDE.md) |
Read AGENTS.md at the initMe repo root before editing bootstrap scripts.
Open ~/myLab as the Cursor workspace; optionally copy
mylab.cursorignore.example to ~/myLab/.cursorignore.
See agent/README.md for layout and how to add rules.
Creates <dir>/initMe:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/apoorv-kulkarni/initMe/HEAD/install-macos.sh)" -- "$HOME/code"After gh auth login:
cd ~/myLab/initMe
git init
git remote add origin git@github.com:apoorv-kulkarni/initMe.git
git fetch origin
git reset --hard FETCH_HEAD
git branch -M mastergit clone git@github.com:apoorv-kulkarni/initMe.git ~/myLab/initMe
cd ~/myLab/initMe
bash bootstrap-pi.shcd ~/myLab/initMe && bash install.shThis also regenerates ~/.cursor/rules/ from agent/ and refreshes Claude
and ~/myLab/AGENTS.md adapters.
zshrc, p10k.zsh, ssh_config, and gitignore_global are symlinked from this repo. bootstrap.sh, bootstrap-pi.sh, and install.sh all link ssh_config → ~/.ssh/config; they back up an existing plain file to ~/.ssh/config.bak.<timestamp> before replacing it with a symlink.
git/gitconfig is included from ~/.gitconfig so your identity stays local. Agent rules in agent/ install to ~/.cursor/rules/ and ~/.claude/CLAUDE.md via install.sh.
Shell scripts are checked on push with ShellCheck (see .github/workflows/shellcheck.yml and .shellcheckrc). Local check:
git ls-files -z '*.sh' | xargs -0 shellcheckbrew bundle dump --force # regenerate Brewfile from installed packagesRepo sync: ~/logs/sync-repos.log. launchd (macOS) also writes ~/logs/sync-repos-launchd.log.