Re-runnable bootstrap for a macOS developer machine. The mac/ scripts install
Homebrew-managed tools, shared dotfiles, a uv-managed main Python
environment based on Homebrew Python, conservative Git defaults, and a small
set of macOS defaults without taking over the whole machine.
- macOS only.
~/.sshmust already exist with your keys and config.- Internet is required unless you run with
NO_NET=1. - The bootstrap checks global
git user.name/git user.emailearly in the run and only prompts if either is missing. Any entered values are written immediately so reruns do not ask again. You can preseed them withuser_name=... user_email=.... - When sudo is available, the bootstrap prompts once up front and keeps that
sudo session alive for the rest of the run. By default it also sets the
system-wide sudo credential timeout to 30 minutes through
/etc/sudoers.d/pcprep-timestamp-timeout. - Each run writes a full log to
~/Library/Logs/pcprep/prepare_new_box.<timestamp>.logand refreshes~/Library/Logs/pcprep/prepare_new_box.latest.log.
Normal bootstrap:
bash mac/prepare_new_box.shPreseed Git identity:
user_name="Your Name" user_email="you@example.com" bash mac/prepare_new_box.shSkip Miniconda:
INSTALL_MINICONDA=0 bash mac/prepare_new_box.shEnable the managed compact Powerlevel10k zsh prompt:
USE_POWERLEVEL10K_PROMPT=1 bash mac/prepare_new_box.shLocal-only rerun:
NO_NET=1 bash mac/prepare_new_box.shRefresh only the managed main Python environment:
bash mac/setup_python_ai.shVerify the current machine state:
bash mac/verify_setup.sh- Homebrew core CLI tools from Brewfile.core
- Codex / Claude / Copilot AI tools are installed immediately after the core CLI bundle so they are available early for debugging later setup failures
- Optional GUI apps from Brewfile.cask
- Shared dotfiles and helper scripts via apply_dotfiles.sh
- Managed
mainPython environment based on Homebrew Python 3.12 via setup_python_ai.sh - Conservative macOS defaults via apply_defaults.sh
- Final validation via verify_setup.sh
Normal runs end with:
tmux,zellij, andscreenavailable onPATH- Apple Clang C/C++ compilation working, plus
cmake,ninja, andpkg-config - Azure CLI dynamic extension installs preconfigured under
~/.azure/cliextensions - Managed
mainPython environment installed at~/.venvs/mainby default - Miniconda installed by default, but left dormant and off
PATH - Existing manually installed GUI app bundles are reused or adopted instead of causing the bootstrap to fail
Common toggles:
| Flag | Default | Effect |
|---|---|---|
NO_NET |
0 |
Skip network-backed installs and run only local steps |
SKIP_BREW_UPDATE |
0 |
Reuse existing Homebrew metadata |
INSTALL_GUI_APPS |
1 |
Install the GUI Brewfile (iTerm2, VS Code, Rectangle) |
INSTALL_DOCKER |
1 |
Install Docker Desktop |
INSTALL_GITHUB_COPILOT_CLI |
1 |
Install the GitHub Copilot CLI cask |
INSTALL_CODEX_APP |
1 |
Install Codex.app |
INSTALL_CLAUDE_APP |
1 |
Install Claude.app |
INSTALL_CODEX |
1 |
Install the Codex CLI npm package |
INSTALL_CLAUDE_CODE |
1 |
Install the Claude Code npm package |
INSTALL_AI_ENV |
1 |
Install the managed main Python environment |
INSTALL_MINICONDA |
1 |
Install Miniconda into ~/miniconda3 without conda init |
AUTO_ACTIVATE_MAIN |
1 |
Auto-activate the managed main Python environment in interactive shells |
USE_POWERLEVEL10K_PROMPT |
0 |
Install Powerlevel10k and use the managed compact Powerlevel10k zsh prompt |
APPLY_MACOS_DEFAULTS |
1 |
Apply the managed macOS defaults |
APPLY_DOTFILES |
1 |
Install the managed bash/zsh fragments and copy shared dotfiles |
ENABLE_FIREWALL |
1 |
Enable the macOS application firewall |
ENABLE_FIREWALL_STEALTH |
0 |
Also enable firewall stealth mode |
ENABLE_TOUCH_ID_FOR_SUDO |
1 |
Configure Touch ID for sudo where supported |
CONFIGURE_SUDO_TIMESTAMP_TIMEOUT |
1 |
Set a global sudo credential timeout drop-in under /etc/sudoers.d/ |
SUDO_TIMESTAMP_TIMEOUT_MINUTES |
30 |
Minutes before sudo re-prompts system-wide |
UPGRADE_NODE_GLOBALS |
0 |
Upgrade Codex CLI / Claude Code instead of install-if-missing |
Optional developer extras, all default 1:
| Flag | Effect |
|---|---|
INSTALL_EXTRA_CLIS |
Install the mac-compatible dormant subset of ubuntu/extra_install.sh |
INSTALL_OLLAMA |
Install the Ollama formula only, not the GUI cask |
INSTALL_TAILSCALE |
Install the Tailscale formula only, not the GUI cask |
INSTALL_RUST |
Install Rust through rustup-init |
INSTALL_GO |
Install Go through Homebrew |
INSTALL_DEV_FONTS |
Install JetBrains Mono, MesloLGS Nerd Font, and Fira Code |
INSTALL_FIREFOX |
Install Firefox |
INSTALL_CHROME |
Install Google Chrome |
INSTALL_LLAMA_CPP |
Install llama.cpp |
INSTALL_MLX |
Install MLX extras on Apple Silicon; skipped automatically on Intel Macs |
Other supported path/config overrides:
MINICONDA_DIR=/custom/pathMAIN_VENV_DIR=/custom/pathuser_name=...user_email=...
setup_python_ai.sh also supports:
INSTALL_JUPYTER_KERNEL=0INSTALL_MLX=0
Prompt:
- Default zsh prompt is the plain built-in
%2~ %#. - Enable Powerlevel10k with
USE_POWERLEVEL10K_PROMPT=1. - If Powerlevel10k glyphs look wrong, set your terminal font to
MesloLGS Nerd Font. USE_POWERLEVEL10K_PROMPT=1only takes effect automatically whenAPPLY_DOTFILES=1, because that is what manages~/.zshrc.
Miniconda:
- Installed by default into
~/miniconda3 - Not added to
PATH auto_activate_baseis disabled- Use
condaonto activate base - Use
condaon ENV_NAMEto activate a named environment - Use
condaoffto fully deactivate conda
Python / AI stack:
- Built from Homebrew
python@3.12, not Apple’s Python - Installed into the managed
mainenvironment atMAIN_VENV_DIRinstead of into Homebrew’s base interpreter - Interactive shells auto-activate
mainby default - Use
mainoffto return to plain Homebrew Python in the current shell - Use
mainonto re-entermain - Set
AUTO_ACTIVATE_MAIN=0if you do not wantmainauto-activated in new shells - Includes notebook/data-science basics, TensorFlow/Keras, PyTorch, TensorBoard, and the mainstream LLM tooling stack
- Registers a Jupyter kernel named
Python 3.12 (main) setup_python_ai.shandverify_setup.shvalidate against the repo's requirements files, so the full managed package list is checked rather than a hand-picked subset- MLX is treated as an Apple-Silicon-only layer on top of that stack
Python switching:
- Default interactive shell:
mainis active unlessAUTO_ACTIVATE_MAIN=0 - Plain Homebrew Python: run
mainoff - Miniconda: run
condaonorcondaon ENV_NAME; it deactivatesmainfirst - Leave Miniconda: run
condaoff, thenmainonif you wantmainback immediately - Apple Python: use
/usr/bin/python3explicitly, orapplepyas a short wrapper
Terminal multiplexers:
tmuxis the best default for SSH and remote hostszellijis available for a friendlier local UXscreenis present as a compatibility fallback
C/C++ toolchain:
prepare_new_box.shrequires Apple Command Line Toolsverify_setup.shcompiles and runs a tiny C program and a tiny C++ program
Azure CLI:
azandazcopyare part of the default CLI set- The bootstrap enables
extension.use_dynamic_install=yes_without_prompt - Run
az loginwhen you want to authenticate
Git:
- Existing global
user.name/user.emailare left alone - Missing values are prompted for once and written immediately
https://github.com/...remotes are rewritten to SSH so GitHub keys are used automatically
Sudo:
- The bootstrap refreshes the sudo timestamp in the background during long runs
- By default it also sets
timestamp_timeout=30globally - Use
CONFIGURE_SUDO_TIMESTAMP_TIMEOUT=0to leave the system sudo timeout unchanged - Override the global timeout with
SUDO_TIMESTAMP_TIMEOUT_MINUTES=...
Managed under ~/.config/pcprep/:
macos-shellenv.shpcprep-shell.bashpcprep-shell.zshpcprep-shell.common.shpcprep-p10k.zshpcprep-aliases.sh
Managed blocks are added to:
~/.zprofile~/.zshrc~/.bashrc~/.bash_profile
Copy-if-absent files seeded from ubuntu/:
~/.inputrc~/.tmux.conf~/.claude/settings.json~/.codex/config.toml- helper scripts copied into
~/.local/bin
- Remove the
pcprepmanaged blocks from~/.zprofile,~/.zshrc,~/.bashrc, and~/.bash_profile - Delete the managed files under
~/.config/pcprep/ - Run
bash mac/revert_defaults.sh - Remove any copy-if-absent files from your home directory if you no longer want them