Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ test:
@echo "Checking bash syntax..."
@bash -n bootstrap.sh
@bash -n uninstall.sh
@bash -n home/.macos
@for f in home/.bin/*; do echo " $$f"; bash -n "$$f" || exit 1; done
@echo "Checking zsh syntax..."
@zsh -n home/.zshrc
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ On a bare macOS install, in order:
cd ~/projects/dotfiles
```
3. **Create `~/.extra`** with any secrets *before* the full bootstrap, so package/MCP setup can use them (see the env vars under [Customization](#customization) — `GITHUB_TOKEN`, and on the gateway host `OPENCLAW_GATEWAY_TOKEN` / `OBSIDIAN_API_KEY`).
4. **Run the full bootstrap** (installs Homebrew if missing, installs packages, inits submodules + themes, symlinks dotfiles, registers MCP servers, installs LaunchAgents):
4. **Run the full bootstrap** (installs Homebrew if missing, installs packages, inits submodules + themes, symlinks dotfiles, registers MCP servers, installs LaunchAgents, and applies macOS system defaults):
```bash
./bootstrap.sh -p # -p = pull + install/update packages, then sync
```
Re-running `./bootstrap.sh -f` later is safe and idempotent (and restores symlinks that atomic-writing tools like btop/zellij/CC replace).
The `-p` run also applies `home/.macos` (Dock, Finder, keyboard, trackpad, hot corners, etc. — see [macOS system defaults](#macos-system-defaults)) and restarts Dock/Finder. Re-running `./bootstrap.sh -f` later is safe and idempotent (and restores symlinks that atomic-writing tools like btop/zellij/CC replace) — but `-f` does **not** touch system defaults, so it won't restart your Dock.
5. **Restart the terminal** (or `source ~/.zshrc`).
6. **Manual post-install steps** (bootstrap can't automate these):
- **tmux:** press `prefix + I` to install TPM plugins.
Expand Down Expand Up @@ -202,6 +202,26 @@ export CUSTOM_VAR="value"
alias myalias="some command"
```

## macOS system defaults

`home/.macos` is a faithful snapshot of intentional macOS system settings — Dock
(autohide, size, hot corners), Finder (hidden files, list view, path bar),
keyboard/text (show extensions, no auto-correct, traditional scrolling),
trackpad (tap-to-click, three-finger drag), Stage Manager off, `en_GB` locale,
and a few misc tweaks. It's applied automatically by `./bootstrap.sh -p`; you can
also re-run it any time:

```bash
~/.macos # (symlinked) or: bash home/.macos
```

It's macOS-only, idempotent, and restarts Dock/Finder/SystemUIServer to apply.
To change settings, edit `home/.macos` (it's a plain `defaults write` script).

**Not captured** (can't be, via `defaults`): iCloud/Apple-ID settings, Login
Items, TCC permissions, network/Wi-Fi, Touch ID, and sandboxed apps (Safari,
Control Center) — configure those manually.

## Credits

Based on [Mathias Bynens' dotfiles](https://github.com/mathiasbynens/dotfiles), heavily simplified.
16 changes: 16 additions & 0 deletions bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,17 @@ for arg in "$@"; do
esac
done

# Apply macOS system defaults (faithful snapshot in home/.macos). Only on a full
# `-p` install — it restarts Dock/Finder, so we don't run it on routine `-f` syncs.
apply_macos_defaults() {
[[ "$(uname)" == "Darwin" ]] || return 0
local dotfiles_dir
dotfiles_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
if [[ -x "$dotfiles_dir/home/.macos" ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The guard tests for the executable bit, but the script is invoked via bash on the file path, which does not need the exec bit. If that bit is ever lost (e.g. core.fileMode=false, a non-exec filesystem, or an archive extraction), the defaults would silently not apply during bootstrap with no warning. A file-exists test matches how the file is actually run and is more resilient.

bash "$dotfiles_dir/home/.macos" || echo "Warning: some macOS defaults failed to apply"
fi
}

# Pull, install, and update packages if requested
if [[ "$PULL" == true ]]; then
pull_latest
Expand All @@ -1334,3 +1345,8 @@ else
sync_dotfiles
fi
fi

# Apply macOS system defaults on a full install only (after symlinks are in place).
if [[ "$PULL" == true ]]; then
apply_macos_defaults
fi
82 changes: 82 additions & 0 deletions home/.macos
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# macOS system defaults — a faithful snapshot of this user's intentional settings.
# Captured via `defaults read` on 2026-06; reproduces the same choices on a new Mac.
#
# Run directly (`~/.macos` once symlinked, or `bash home/.macos`). Idempotent.
# bootstrap.sh runs this during a full `-p` install (NOT on a plain `-f` sync, so
# routine dotfile syncs don't restart Dock/Finder).
#
# NOT captured (can't be, via `defaults`): iCloud/Apple-ID settings, Login Items,
# TCC permissions, network/Wi-Fi, Touch ID, and sandboxed apps (Safari, Control
# Center) which need Full Disk Access and are fragile — configure those manually.

set -euo pipefail

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] With set -euo pipefail, the first defaults write that returns non-zero aborts the whole script, skipping every later setting AND the final killall Dock/Finder/SystemUIServer loop. The result is a partially-written, un-applied state (bootstrap.sh only prints a generic warning). For a script billed as idempotent and re-runnable, consider relaxing errexit for the defaults section (or running the killall via a trap on EXIT) so a single failed domain write does not prevent the UI restart that applies everything that did succeed. defaults write rarely fails for valid domains, so this is robustness hardening, not a live bug.


# macOS only.
[[ "$(uname)" == "Darwin" ]] || { echo "Skipping .macos (not macOS)"; exit 0; }

echo "Applying macOS defaults…"

# Close System Settings so it can't overwrite changes we're about to make.
osascript -e 'tell application "System Settings" to quit' 2>/dev/null || true

# ── Keyboard / text input (NSGlobalDomain) ──────────────────────────────────
defaults write NSGlobalDomain AppleShowAllExtensions -bool true # show all file extensions
defaults write NSGlobalDomain NSAutomaticCapitalizationEnabled -bool false
defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled -bool false
defaults write NSGlobalDomain com.apple.swipescrolldirection -bool false # natural scrolling OFF
defaults write NSGlobalDomain AppleLocale -string "en_GB"

# ── Dock ────────────────────────────────────────────────────────────────────
defaults write com.apple.dock autohide -bool true
defaults write com.apple.dock tilesize -int 64
defaults write com.apple.dock largesize -int 16
defaults write com.apple.dock magnification -bool false
defaults write com.apple.dock show-recents -bool false
defaults write com.apple.dock mru-spaces -bool false # don't auto-rearrange Spaces

# Hot corners (action codes: 2=Mission Control, 4=Desktop, 11=Launchpad,
# 12=Notification Center). Modifier 0 = no modifier key.
defaults write com.apple.dock wvous-tl-corner -int 2; defaults write com.apple.dock wvous-tl-modifier -int 0
defaults write com.apple.dock wvous-tr-corner -int 12; defaults write com.apple.dock wvous-tr-modifier -int 0
defaults write com.apple.dock wvous-bl-corner -int 11; defaults write com.apple.dock wvous-bl-modifier -int 0
defaults write com.apple.dock wvous-br-corner -int 4; defaults write com.apple.dock wvous-br-modifier -int 0

# ── Finder ──────────────────────────────────────────────────────────────────
defaults write com.apple.finder AppleShowAllFiles -bool true # show hidden files
defaults write com.apple.finder ShowPathbar -bool true
defaults write com.apple.finder ShowStatusBar -bool false
defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" # list view
defaults write com.apple.finder ShowHardDrivesOnDesktop -bool false
defaults write com.apple.finder ShowExternalHardDrivesOnDesktop -bool true
defaults write com.apple.finder NewWindowTarget -string "PfHm" # new windows → Home

# ── Trackpad (write to both built-in and Magic Trackpad domains) ────────────
for d in com.apple.AppleMultitouchTrackpad com.apple.driver.AppleBluetoothMultitouch.trackpad; do
defaults write "$d" Clicking -bool true # tap to click
defaults write "$d" TrackpadThreeFingerDrag -bool true
defaults write "$d" TrackpadRightClick -bool true
defaults write "$d" TrackpadThreeFingerTapGesture -int 2 # 3-finger tap → look up
defaults write "$d" FirstClickThreshold -int 1 # light click
defaults write "$d" SecondClickThreshold -int 1
done
# Companion key so tap-to-click also applies at the login window / globally.
defaults write NSGlobalDomain com.apple.mouse.tapBehavior -int 1

# ── Window manager / Stage Manager ──────────────────────────────────────────
defaults write com.apple.WindowManager GloballyEnabled -bool false # Stage Manager off
defaults write com.apple.WindowManager HideDesktop -bool true

# ── Menu-bar clock ──────────────────────────────────────────────────────────
defaults write com.apple.menuextra.clock ShowDayOfWeek -bool false
defaults write com.apple.menuextra.clock ShowDate -int 0

# ── Misc ────────────────────────────────────────────────────────────────────
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true # no .DS_Store on network shares
defaults write com.apple.ActivityMonitor ShowCategory -int 102

# Apply: restart affected UI processes.
for app in Dock Finder SystemUIServer; do killall "$app" 2>/dev/null || true; done

echo "macOS defaults applied. Some changes need a logout/restart to fully take effect."
Loading