Install all your applications and configure everything the way you like it with one command.
TODO:
- add brew to path when installing. (/opt/homebrew/bin)
- add GitHub to known hosts: ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
- unencrypt and add SSH key to ssh session
- figure out Emacs symbolic link internal directory
- make caps lock -> ESC remapping work
- fix glances webserver “msg: Destination directory /Users/mpereira/Library/LaunchAgents does not exist”
- install insta360 link ap
- wait 1s after install spotify and steam
- GraalVM on apple silicon?
- kubectl-krew isn’t available right after installing
- launch BTT on startup, load configuration and license
- symlink id_rsa to mpereira@pluto SSH key
- turn on dictation
- Table of Contents
- Tested on
- Bootstrap machine (make bootstrap)
- Configure machine (make converge)
- Roles
- Installs
- Desktop Applications
- Text Editors
- Configuration
- Programming Languages
- Multimedia
- Fonts
- Browser Plugins
- Virtualization, Provisioning, Containers and System Tools
- Package Managers and Build Tools
- Shell
- Programming Utilities
- Data Systems
- Configuration, Monitoring and Debugging
- Document Processors and Plotting
- Markup Tools
- Command line tools
- Security
- GNU Command Line Tools
- Miscellaneous
- Configures
- Installs
- Author
- License
I’ve been using macbook-playbook since 2013. I’ve used it on at least eight
MacBook Pros with different macOS versions. As of May 2023 I use it on my
personal 16” 2023 Macbook Pro with Ventura installed and on my work 16” 2019
Macbook Pro with Monterey installed.
Please open an issue if you’re trying this out and bump into anything.
These are one-time steps that need to be done on machines that are running
macbook-playbook for the first time.
Open the “Terminal” application, type git into the shell and follow the
instructions to install the Apple Developer Tools.
Now your machine should have git and python3 installed.
git clone https://github.com/mpereira/macbook-playbook.gitDepending on your macOS version you will be queried or not for assistive
access while make converge runs. This is required for example to remap
caps lock to control.
In case that task fails, or if you want to do it beforehand just in case, go
to “System Preferences > Security & Privacy > Privacy > Accessibility” and
add the application running macbook-playbook (Terminal/iTerm2/Emacs/etc.)
to the list.
group_vars/localhost/vars.yml. This
file is git-ignored in this project.
These are the roles that use secrets:
| Description | Secret name | Role |
| AWS CLI credentials | aws_credentials_file_base64 | awscli |
| BetterTouchTool license | better_touch_tool_license_file_base64 | better-touch-tool |
| DaisyDisk license | daisydisk_registration_key_file_base64 | daisydisk |
| Enviroment variables for dotfiles | dotfiles_environment_yml_file_base64 | dotfiles |
| iStat Menus settings | istat_menus_settings_file_base64 | istat-menus |
| Prey API key | prey_api_key_yaml_file_base64 | prey |
| Private SSH key | mpereira_at_pluto_ssh_private_key_base64 | ssh-keys |
| s3cmd configuration | s3cmd_cfg_file_base64 | s3cmd |
If a group_vars/localhost/vars.yml file with those secrets is absent
running these roles will fail.
You have two choices: skip these roles, or write your
group_vars/localhost/vars.yml.
To skip them, when you reach the provision machine step, make Ansible skip
roles tagged with uses-secrets. You don’t need to run this now, the
command below is just an example.
make converge ARGS='--skip-tags uses-secrets'This will:
- Set up passwordless
sudo - Install a user Python3
- Install Ansible
make bootstrapYour machine should now be ready to be provisioned! You won’t need to run the above steps again.
Now that the machine is bootstrapped, we can provision it.
This runs all non-disabled roles in =main.yml=.make converge ARGS='--skip-tags disabled'ansible-playbook arguments can be passed via the ARGS environment variable.
For example, --tags can be passed so that only matching roles are run.
make converge ARGS='--tags google-chrome'--skip-tags can also be passed to avoid running certain roles.
make converge ARGS='--skip-tags disabled,unity'All role tags can be seen in =main.yml=.
Tasks may fail due to intermittent reasons like temporary server
unavailability. When a task fails you can either disable its role via
--skip-tags or use --start-at-task with the name value of some task to
cause Ansible to start the playbook exactly there.
For example, if the “Install Emacs” task from the “build-emacs” role fails for what seems to be an intermittent issue, you can pick up provisioning from there so that previous tasks don’t have to re-run.
make converge ARGS='--skip-tags disabled --start-at-task "Install Emacs"'Check the official Ansible documentation for more details.
These are steps that are currently not automated because:
- it would be difficult
- it would be impossible
- or I just didn’t have the time
- System Preferences -> Keyboard -> Input Sources
- Click +
- Select “English” on left column
- Select “U.S. International - PC” on right column
- Click “Add”
- Remove other keyboard layouts from the left column
- Import license from
roles/istat-menus/files/iStat Menus Settings.ismp
- Register license
- Register license
Set to Hack Regular 18 pt.
System Preferences > Security & Privacy > Privacy > Accessibility
- BetterTouchTool.app
- Emacs-*.app
- MacGPT
- RescueTime
- Terminal
- VLC
Uncheck:
- Mission Control
- Move left a space
- Move right a space
- Switch to desktop 1
I use these keybindings on Emacs.
- Android File Transfer
- BitBar
- ChatGPT
- Claude
- CleanShot X
- Cursorcerer
- DaisyDisk
- Dash
- DBeaver
- Divvy
- Dropbox
- Elgato Dock
- Elgato Control Center
- f.lux
- Firefox
- Finicky
- Ghostty
- Google Chrome
- Google Photos
- Grammarly
- iStat Menus
- LICEcap
- Maccy
- Obsidian
- PDF Expert
- Perplexity
- Persephone
- RescueTime
- Skype
- Slack
- Spotify
- Steam
- Telegram
- Teensy Loader
- ToggleDarkMode
- Unity
- Unity Hub
- VLC
- Wireshark
- XQuartz
- YNAB (disabled by default, I use the online version and the application binary isn’t available anymore)
- Zoom
- Zwift
Managed by the skills role. The canonical copy of every skill lives under
~/.agents/skills (which Codex reads natively) and is symlinked into
~/.claude/skills for Claude Code. No ~/.codex/skills symlinks are
created, since that would double-list each skill in Codex. Codex’s own
.system and codex-primary-runtime directories are left untouched.
make converge updates git-backed skills; add a version: to an entry in
the defaults to pin it. Locally-maintained skills are copied from
roles/skills/files and updated with the playbook. Sources (configured in
roles/skills/defaults/main.yml):
- Humanizer (standalone repo)
asd-ste100(maintained here inroles/skills/files/asd-ste100/SKILL.md, with links to ASD’s official standard)- HumanLayer skills (
show-me) - marketingskills (monorepo, all skills)
- anthropics/skills (
frontend-design) - vercel-labs/skills (
find-skills)
The clickup skill is the exception: it is generated by the cup CLI
itself (cup skill --print) and provisioned by the clickup-cli role, so
it always matches the installed CLI version.
Invoke these skills in Codex with $asd-ste100 or $show-me, and in
Claude Code with /asd-ste100 or /show-me. Our ASD-STE100 skill defaults
to an “80% STE” style for readable technical explanations, with stricter
wording when requested. It does not certify compliance with the official
dictionary. Both skills allow automatic selection when relevant. Show-me’s
auto_invoke: true setting creates a configured copy with automatic
selection enabled for Codex and Claude Code, keeping the upstream Git
checkout clean.
Local skills are supported in the ChatGPT desktop app. This role provisions the local Codex discovery path; it does not publish a plugin for ChatGPT web or mobile. See OpenAI’s skill documentation.
Claude chat and Cowork require a separate upload. Enable code execution in Settings > Capabilities, then upload each skill folder as a ZIP in Customize > Skills and enable it. Use the resolved skill folder, including supporting scripts and references, rather than archiving only its symlink. See Claude’s skill setup instructions.
- Emacs 28.2
- Emacs 30
- MacVim
- Neovim
- Rider
- Vim (disabled by default until I figure out why it isn’t compiling on macOS Big Sur with LLVM 12)
- VSCode
Biome’s user-level config is provisioned directly by this playbook at
~/Library/Application Support/biome/biome.json.
Claude local settings are ignored through .gitignore.
- Clojure
- GNU Octave
- Go
- Haskell
- Java (AdoptOpenJDK)
- Lua
- LuaJIT
- .NET
- Node.js
- PureScript (disabled by default until I figure out why
stack install purescriptis currently failing) - Python 3.14.4
- R
- Ruby
- Rust
- Docker (using OrbStack instead)
- gcloud
- krew
- kubectl
- kubectl-tree
- OpenZFS (disabled by default until it works on macOS Big Sur)
- OrbStack
- Terraform
- Vagrant
- Vagrant vagrant-vbguest plugin
- VirtualBox
- Black
- Biome
- clojure-lsp
- Ctags
- YAPF
- zprint
- yq
- shfmt
- node-cljfmt
- gron
- ktlint
- lefthook
- Prettier
- Pyre
- rust-analyzer
- ShellCheck
- Apache Hadoop (disabled by default, it conflicts with the
yarnJavaScript package manager)
- AWS CLI
- ClickUp CLI
- defaultbrowser
- delta
- delta
- git
- gh
The
ghrole links the legacy~/.config/ghpath to the macOS Application Support config directory. - fd
- jq
- local-ssl-proxy
- Mole
- p7zip
- parallel
- pgsanity
- pngpaste
- ripgrep
- s3cmd (disabled by default, I use the AWS CLI)
- scc
- stripe-cli
- tealdeer
- terminal-notifier
- tree
- vercel
- websocat
- wrk
- xz
- Prey
- GnuPG
- pinentry-mac
- gpg-agent-config (configures gpg-agent.conf with pinentry-mac)
- vault
- binutils
- coreutils
- diffutils
- ed
- findutils
- gawk
- gnu-indent
- gnu-sed
- gnu-tar
- gnu-which
- gnutls
- grep
- gzip
- screen
- watch
- wdiff
- wget
- Mutagen
- ChromeDriver
- FontForge
- Qt 5 (disabled by default)
- WordNet
The finicky role sets Finicky as the HTTP/HTTPS handler, routes
app.clickup.com task and Doc links to the ClickUp desktop app, and uses
Google Chrome for other links. It manages ~/.finicky.js and preserves
the URL path, query, and anchor. Run it with
make converge ARGS“–tags finicky”=. Accept macOS’s browser-change
prompt if one appears. ClickUp’s desktop app must already be installed
and signed in.
Links clicked inside Chrome normally stay in Chrome. With ClickUp running, open a ClickUp page in Chrome and select the notification checkbox to automatically open future links in the desktop app. This is ClickUp’s Detect Desktop App setting. Tracked notification URLs and public Doc URLs are left to Chrome to resolve.
Uses an AppleScript helper for the current System Settings flow.