Skip to content

Setting up a Mac

This is the order for a fresh machine. Each step depends on the one before it. The short version in Installation covers steps 3 to 5.

1. Xcode command line tools

Homebrew and git need them.

xcode-select --install

just macos::xcode-bootstrap runs the same command once just exists.

2. Homebrew

Install Homebrew from brew.sh. On Apple silicon it lands in /opt/homebrew, which the shell config already puts on the PATH.

Install the two tools the next steps need:

brew install just
gem install homesick
homesick clone jefftriplett/dotfiles
homesick symlink dotfiles

homesick symlink links every entry under home/ into ~. Folders listed in .homesick_subdir, such as .config and Library/Application Support, are not linked whole; their children are linked one by one, so other apps can keep writing their own files next to yours.

Open a new shell after this step. The prompt and the shell functions come from the linked files.

Configure Git identity

Git's shared settings are linked to ~/.config/git/config. Identity and signing values are intentionally kept outside this repository in ~/.config/git-private/config. On a new machine, create the private file from the value-free example:

mkdir -p ~/.config/git-private
cp ~/.config/git/private.example ~/.config/git-private/config
chmod 600 ~/.config/git-private/config
${EDITOR:-nano} ~/.config/git-private/config

Confirm that Git sees the private values before making a commit:

git config --show-origin --name-only --get-regexp '^(user|github)\.'
git var GIT_AUTHOR_IDENT

4. Bootstrap

just --justfile=./home/justfile bootstrap

From then on ~/justfile is linked, so plain just <recipe> works from any directory. The recipe does this, in order:

Step What it runs Result
homesick gem install homesick, homesick symlink Links refreshed
Homebrew brew bundle install from Brewfile.<Hostname> when it exists, else the generic Brewfile Formulae, casks, and App Store apps
Xcode just macos::xcode-bootstrap Command line tools, if still missing
mise mise install for go, node, ruby, rust, then mise reshim Language runtimes
Python just python::bootstrap See below

python::bootstrap installs the uv-managed interpreters (3.11 to 3.15), runs uv python install --default 3.14 so python and python3 exist in ~/.local/bin, and installs the CLI tools with uv tool install.

Note

The generic home/Brewfile is only the starting point for a Mac that has no per-host file yet. The per-host files, home/Brewfile.<Hostname>, are what each Mac really has, and bootstrap prefers them. Step 10 creates this Mac's own file.

5. Tailscale and ssh names

Every other Mac must be reachable by name. Install Tailscale, sign in, and confirm MagicDNS resolves the others:

ssh -o BatchMode=yes mac-studio-2023 hostname

If a name does not resolve, add a Host entry in ~/.ssh/config. Key-based auth is required. The scripts never type a password.

6. Register the machine

projects, workon, and mkproject are backed by a Rust binary. Build it once mise has installed Rust in step 4. The same recipe also installs homesick-new. See Rust Rewrites.

just rust-install     # installs projects and homesick-new to ~/.local/bin

The registry lives in ~/Projects/projects.toml. If this Mac does not have it yet, Syncthing brings it over in step 7, so do this step after Syncthing has finished its first sync. Then add the machine:

projects machines add air --host mba-2025 --hostname MacBook-Air-2025 --check

--hostname is needed only when the machine's own hostname differs from its ssh name. Without it, the machine would try to ssh to itself. --check refuses to add a host that does not answer. See Machine List.

7. Syncthing

Install Syncthing from the Brewfile, then run it from our own LaunchAgent instead of the Homebrew service:

just syncthing::service-install

The Homebrew service pipes Syncthing's output into one file that never rotates; it reached hundreds of gigabytes on every Mac. Our agent passes --log-file with a 50 MiB limit and three old files, so the log stays under 200 MB. just syncthing::status shows the agent and the log sizes.

Then share ~/Projects and ~/Work with the other Macs. home/.stignore is linked into ~ and holds the global ignore patterns; each project can add its own .stignore. Virtualenvs are ignored, which is why a project's .venv is rebuilt on each Mac the first time you enter the directory.

8. ssh-clipboard

One clipboard across the Macs. It comes from npm, not the Brewfile:

just ssh-clipboard::install

That installs the CLI and opens the setup TUI. Pick the other Macs from the Tailscale list. Setup verifies passwordless ssh to each one, installs the daemon there, and starts the service on both ends. The list uses full tailnet names, so the Host entries in ~/.ssh/config must match them; see ssh-clipboard.

9. tmux plugins

The plugin manager is not in the repository. Clone it, then install the plugins from inside tmux:

git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

Start tmux and press prefix + I (capital i). That installs tmux-sensible and the Dracula theme named in ~/.tmux.conf.

10. Freeze this Mac's Brewfile

After the apps you want are installed:

just freeze

This writes home/Brewfile.<Hostname>. Commit it. See Maintenance for the caveat about tap formulae.

11. Hammerspoon

Open Hammerspoon once. macOS asks for Accessibility permission; grant it in System Settings, otherwise no hotkey can move a window. The config is already linked at ~/.hammerspoon. Press hyper + r to reload it after changes. The hotkeys are listed in Hammerspoon.

On the Studio, press hyper + 9 to dump the screen names and UUIDs to the console. If the bottom-left display changed, copy its UUID into display_grid.lua. See Hammerspoon.

12. Alfred

Alfred installs from the Brewfile. Its preferences are per-Mac and not in the repository. Sign in to the Powerpack, import the Dracula theme, then install the workflows listed in Alfred. The Backup Preferences workflow can also restore another Mac's archive from the iCloud Backups folder.

13. Optional: default apps

just macos::duti-setup installs duti and sets the default app for a list of file types.

14. Screenshots

just macos::screenshots-setup points macOS at ~/Screenshots for new screenshots. The Desktop is one iCloud folder shared by every Mac, so a screenshot saved there shows up on all of them; ~/Screenshots is local to each machine. just macos::screenshots-sweep moves any screenshots that still landed on the Desktop into ~/Screenshots without overwriting. For other defaults write keys worth setting, see macos-defaults.com.

15. Optional: Sublime Text settings

The preferences and the Package Control package list live in home/.config/sublime-text/. just macos::sublime-link symlinks them into ~/Library/Application Support/Sublime Text/Packages/User/. The recipe never overwrites a real file that is already there; just macos::sublime-diff shows what differs, so you can merge by hand and rerun the link.

For a machine that needs different values, add Preferences.<Hostname>.sublime-settings next to the shared file with only the keys that differ, such as a font size. The recipe then writes a generated preferences file for that machine instead of a link, with the overlay applied.

Verify

python --version            # 3.14.x from ~/.local/bin
mise ls                     # go, node, ruby, rust present
just --list                 # recipes resolve
tmux-remote-ls              # the other Macs answer
command -v projects         # ~/.local/bin/projects, the Rust build
homesick-new show-path      # prints ~/.homesick/repos/dotfiles
workon                      # lists registered projects
just lint                   # every hook passes

If workon lists nothing, the registry has not synced yet or the machine is not in it. projects machines shows what the registry knows.