My personal macOS workstation setup: Homebrew tooling, scheduled maintenance, diagnostics, and file/security triage.
A consistent, low-effort way to bootstrap my Mac, keep core tooling maintained, and support day-to-day IT, DevOps, AI, and security work.
One-time setup. Runs automatically. Manual control when needed.
Using this yourself? The app list in the
Brewfileis mine. Fork the repo and replace it with your own before running the installer.
mmstands for Mac Manager.
| Script | Purpose |
|---|---|
mm_install.sh |
Bootstrap setup (repo, CLI, launchd) |
mm_auto.sh |
Automated weekly maintenance (launchd) |
mm_maintain.sh |
Run maintenance now: Homebrew, optional cask upgrades, DNS flush, macOS updates, optional SSH backup, optional QuickTime history cleanup |
mm_doctor.sh |
Health checks and diagnostics (mm doctor) |
mm_triage.sh |
Quick file/malware triage with hash, VirusTotal and strings (mm triage) |
mm_backup_ssh.sh |
Backup ~/.ssh to an encrypted iCloud sparsebundle (called by mm maintain) |
mm_backup_gpg.sh |
Backup GPG keys, ownertrust and ~/.gnupg to the encrypted iCloud sparsebundle |
mm_common.sh |
Shared configuration and helpers |
The managed apps and CLI tools are declared in the repo-root Brewfile and installed with brew bundle.
Scripts are managed using a repo + CLI model:
~/Repositories/dev/mac-workstation → source and runtime (git repo)
~/.local/bin/mm → CLI entrypoint
~/Library/Logs/mac_manager/ → logs
- The repo contains the runtime scripts and is version-controlled
- The
mmcommand provides a simple interface - launchd runs the auto-maintenance script directly from the repository
Clone the repo and run the installer once:
mkdir -p ~/Repositories/dev
git clone https://github.com/tommiec/mac-workstation.git ~/Repositories/dev/mac-workstation
bash ~/Repositories/dev/mac-workstation/scripts/mm_install.shIf git is not available yet, run xcode-select --install, complete the
Command Line Tools installation, and then repeat the commands above.
The installer will:
- set up Homebrew (if needed)
- install all apps and CLI tools from the
Brewfile(viabrew bundle) - install the
mmcommand in~/.local/bin - configure global Git excludes and a local Git hooks path
- start Ollama as a persistent user service and pull the managed models
- register the weekly launchd job
The installer ensures that the user-local command folder is in your shell path
by adding this line to ~/.zshrc when it is not already present:
export PATH="$HOME/.local/bin:$PATH"To update later:
cd ~/Repositories/dev/mac-workstation
git pull --ff-onlyNormal script changes are active directly after git pull. Run mm install only if you changed installer-managed setup: the app list, LaunchAgent schedule, or mm wrapper.
Homebrew installs the Ollama binary. mm install stops Homebrew's optional
service and registers one Mac Manager-owned LaunchAgent
(local.mac-manager.ollama) that listens on all network interfaces at port
11434. Its settings cannot be overwritten by brew services or an Ollama
upgrade. The installer then installs these models when missing:
devstral:24b
qwen3-coder:30b
gemma3:27b
qwen2.5-coder:14b
Allow roughly 60 GB of model storage under ~/.ollama/models. From the NAS,
test the connection using the Mac's stable LAN address:
curl http://MAC_LAN_IP:11434/api/tagsLarge model pulls use up to four attempts with delays of 10, 20, and 40
seconds. Ollama retains partial downloads between attempts, resumes them on the
next pull, and verifies every downloaded blob against its SHA-256 digest
before writing the model manifest. The installer additionally requires
ollama show to read the completed model successfully. Re-running mm install
continues any model that still failed after all attempts.
The service enables Flash Attention, uses a q8_0 KV cache, and limits Ollama
to one loaded model and one parallel request. This is intentional for a Mac
with 36 GB unified memory: every managed model fits individually without trying
to keep multiple 14–19 GB model images resident at once.
Mac Manager owns the persistent configuration at
~/Library/LaunchAgents/local.mac-manager.ollama.plist. RunAtLoad and
KeepAlive make it start after login and restart after a crash. mm auto
compares the running server version with the installed CLI and restarts a loaded
agent when they differ, including after a manual or partially failed Homebrew
upgrade. Re-running mm install leaves an identical loaded service untouched,
so active models and NAS requests are not interrupted.
Ollama's local API has no authentication. Binding it to 0.0.0.0:11434
therefore makes model execution available to every device that can reach that
port. Use this only on a trusted LAN, do not forward port 11434 on the router,
and restrict access with the macOS firewall or a separate authenticated reverse
proxy when needed. The Mac must be logged in and awake because Ollama runs as a
user LaunchAgent; reserving its LAN address in DHCP keeps the NAS endpoint
stable. Remember that a MacBook roams: on café, hotel, or guest Wi-Fi, the
unauthenticated API is exposed to peers on that network too. Stop it before
leaving a trusted LAN with:
launchctl bootout "gui/$(id -u)/local.mac-manager.ollama"Start it again from its persistent configuration with:
launchctl bootstrap "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/local.mac-manager.ollama.plist"mm install configures the machine-wide Git hygiene baseline:
configs/git-ignore-globalis copied to~/.config/git/ignore- Git is configured with
core.excludesFile=~/.config/git/ignore - Git is configured with
core.hooksPath=~/.config/git/hooks
The repository also contains the managed local ignore rules and commit-message hook:
configs/
ignore.local
hooks/
commit-msg
During installation:
ignore.localis copied to~/.config/git/ignore.localcommit-msgis copied to~/.config/git/hooks/commit-msgand made executableignore.localis appended to the generated~/.config/git/ignore
This keeps all Git hygiene configuration versioned and reproducible from the
same source. mm doctor verifies that the installed local excludes and
commit-msg hook match the repository versions.
Automatic — runs every Saturday at 02:00 via launchd.
Commands:
mm auto # run automated maintenance now
mm maintain # run maintenance now (interactive prompts)
mm install # re-run setup
mm doctor # check system health
mm triage <file> # inspect a suspicious file
mm help # show available commandsmm maintain reports drift so keeping, uninstalling, or adopting into the Brewfile stays a deliberate choice: Homebrew packages installed outside the Brewfile, and apps in /Applications that did not come in through Homebrew (labelled App Store or manual install). It then asks before taking optional actions: upgrading outdated Homebrew casks, installing macOS updates, backing up ~/.ssh and GPG keys/trust to the encrypted iCloud vault, and clearing QuickTime Player's recent documents history. The QuickTime cleanup removes QuickTime's app-specific recent-document shared-file-list entries and legacy QuickTime preference keys. It does not delete media files and does not clear system-wide macOS Recent Items.
File triage is a deliberate part of this workstation setup, not an add-on: the machine should be able to handle quick, self-contained file research on its own, without reaching for a separate analysis environment first.
Use mm triage for a quick first look at a suspicious file:
mm triage ~/Downloads/example.exeThe command:
- identifies the file type using
file - calculates the SHA256 hash
- looks up the hash in VirusTotal when the
vtCLI is available - shows a short hex preview
- checks magic bytes against common file types
- flags mismatches between file extension and detected content
- extracts quick indicators such as URLs, IPs, shell commands and suspicious strings
- prints a simple triage score
- opens extracted strings in
lessfor manual review
The installer installs virustotal-cli. The triage script uses the CLI command vt for lookups, so configure the vt CLI with your VirusTotal API key first. The string view opens in less; press q to exit it.
Avoid storing API keys and tokens as plain text in dotfiles. On macOS, Keychain or Apple Passwords is a better place for them.
For one-off Keychain use, source the helper file first:
source ~/Repositories/dev/mac-workstation/scripts/mm_common.sh
keychain_set "ANTHROPIC_API_KEY" # store once; prompts for the secret
keychain_get "ANTHROPIC_API_KEY" # retrieveIn ~/.zshrc, load the key directly from Keychain instead of hardcoding it:
export ANTHROPIC_API_KEY="$(security find-generic-password -a "$USER" -s ANTHROPIC_API_KEY -w 2>/dev/null)"mm doctor scans shell dotfiles for likely plain-text secrets and masks their values in the output. It also shows an inventory of SSH private keys in ~/.ssh — name, type, bits, fingerprint, and modification date — and warns on loose directory/file permissions, DSA keys, and short RSA keys (< 3072b). Only group or other access triggers a warning; common safe modes are 600 and 400. For SSH trust awareness, it also summarizes known_hosts with visible and hashed host patterns, modification date, and a small visible sample when available.
Use passphrases for SSH private keys. macOS can remember those passphrases in Keychain.
For secrets that should be recoverable on a new Mac but should not live as plain files in iCloud Drive, this setup uses one encrypted sparsebundle:
~/Library/Mobile Documents/com~apple~CloudDocs/Secure Vault/Secrets.sparsebundlemacOS asks for the vault password each time it needs to be mounted. On the first run it also asks you to choose that password — store it in your password manager. The script never stores or logs the vault password.
Inside the mounted vault, SSH, GPG and PEM material have different lifecycles:
ssh-backup/
gpg-backup/
pem-archive/
ssh-backup/ is managed by the optional SSH backup prompt in mm maintain. It mirrors ~/.ssh into the encrypted vault and may overwrite that backup on future runs.
gpg-backup/ is managed by the optional GPG backup prompt in mm maintain. It stores latest/portable/ exports (public-keys.asc, secret-keys.asc, ownertrust.txt, and secret-keys-list.txt), latest/full-gnupg/.gnupg/, and timestamped archives under archives/.
To restore the portable GPG backup on a new Mac:
cd "/Volumes/Secrets/gpg-backup/latest/portable"
gpg --import public-keys.asc
gpg --import secret-keys.asc
gpg --import-ownertrust ownertrust.txtIf you need an exact full restore instead, copy gpg-backup/latest/full-gnupg/.gnupg/ back to ~/.gnupg while GPG is not running, then restart GPG/GPG Suite.
pem-archive/ is manual storage for PEM/private-key files that should live only inside the encrypted vault. The SSH backup command creates the folder but never syncs, cleans, or overwrites it.
Unmount the vault after use and let iCloud Drive finish syncing before shutting down or editing it elsewhere.
- Uses a LaunchAgent (user context, no root daemon)
- Writes logs and last-run status under
~/Library/Logs/mac_manager/ - Weekly maintenance removes
auto_*.logandmaintain_*.logfiles older than 60 days and truncates Ollama output logs when they exceed 50 MiB - Safe to re-run
mm installat any time, but usually only needed after installer-managed setup changes mm doctorcan be used to validate the setup and inspect the last recorded run for each scriptmm doctoris read-only: it reports drift (including whether the checkout is behind GitHub) but never changes the system; updating happens viagit pull --ff-onlyor the weekly auto run- Global Git hygiene is installed by
mm install: shared excludes come from this repo, while machine-local excludes and hooks live under~/.config/gitand are not stored here.
MIT — Thomas Coppens
AI was used as a sounding board for shell-scripting choices, error analysis and documentation structure. The design, implementation, validation and maintenance are mine.