Skip to main content

Remote Mac setup and package management via SSH

Project description

macstrap

PyPI PyPI Downloads

A CLI tool for setting up and managing Macs — no Ansible knowledge required.

Define what you want installed in plain text files, then run a single command to apply the full setup. Works both locally (on the Mac itself) and remotely (over SSH from another machine). Safe to re-run anytime; already-installed items are skipped.


Quick Start: Fresh Mac Mini

Got a brand-new Mac mini? Here's the fastest path from unboxing to fully configured:

Step 0: Get Python on a fresh Mac

A brand-new macOS has no Python, no git, no compilers. You need developer tools first.

Option A — Install Xcode from the App Store (recommended)

  1. Open the App Store on the Mac
  2. Search for Xcode and install it (~30 GB, takes a while)
  3. After installation, open Terminal and run:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license accept    # accept the Xcode license (required before python3 works)
python3 --version                   # should show Python 3.9.x

Note: Until you accept the license, python3 will print a license error instead of running. The python command does not exist on macOS — always use python3.

Option B — Install Command Line Tools only (smaller)

xcode-select --install
# A dialog will appear — click "Install" and wait

Note: xcode-select --install requires a GUI dialog. If your Mac mini is headless (no display), use the App Store method via Screen Sharing, or install via softwareupdate:

sudo touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
LABEL=$(softwareupdate -l 2>&1 | grep -o "Label: Command Line Tools.*" | sort -r | head -1 | sed 's/Label: //')
sudo softwareupdate -i "$LABEL" --verbose
sudo rm -f /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress

Step 1: Install macstrap

curl -fsSL https://raw.githubusercontent.com/changyy/py-macstrap/main/install.sh | bash

This single command:

  1. Creates a venv at ~/.macstrap/venv (built-in pip 21.x is too old, so venv + upgrade is needed)
  2. Installs macstrap with Python 3.9
  3. Adds macstrap to your PATH

Alternative (for development):

git clone https://github.com/changyy/py-macstrap.git
cd py-macstrap && python3 -m venv .venv && source .venv/bin/activate && pip install --upgrade pip && pip install -e .

Step 2: Local setup on the Mac

source ~/.zshenv                       # or open a new terminal
macstrap local upgrade-python          # install Homebrew + Python 3.10+
macstrap local enable-ssh              # enable SSH server
macstrap local setup-server-mode       # configure always-on server settings

Step 3: Unlock remote features

Remote commands (macstrap run, macstrap verify, etc.) require Python 3.10+ and ansible-core. After Step 1:

/opt/homebrew/bin/python3 -m pip install macstrap[remote]

Step 4: Remote setup from another machine

pip install macstrap[remote]
macstrap init                          # create package + settings files
macstrap ssh-auth mac-mini.local       # register + store credentials
macstrap run mac-mini.local            # apply full setup

Prerequisites summary

What Why
Xcode or Xcode CLT Provides git, compilers, and Python 3.9
pip3 install macstrap Local commands work immediately on Python 3.9
Homebrew (optional) Provides Python 3.10+ for remote commands
pip3 install macstrap[remote] Adds ansible-core for remote Mac setup

Local Commands

Run these on the Mac itself for initial machine setup:

macstrap local enable-ssh

Enable the macOS SSH server (Remote Login) so other machines can connect:

% macstrap local enable-ssh
✓  Remote Login (SSH) enabled.

  This Mac is reachable at:  mac-mini.local
  SSH user:                  user

  Test from another machine:
    ssh user@mac-mini.local

macstrap local install-xcode

Install Xcode Command Line Tools (git, compilers, Python, etc.):

% macstrap local install-xcode
✓  Xcode Command Line Tools already installed (v16.2.0.0.1.1733547573).

If not yet installed, macstrap uses softwareupdate to install non-interactively.

macstrap local install-brew

Install Homebrew package manager:

% macstrap local install-brew
✓  Homebrew 5.0.13 is already installed.

Automatically adds brew shellenv to ~/.zprofile and ~/.zshrc.

macstrap local install-macports

Install MacPorts package manager (auto-detects macOS version):

% macstrap local install-macports
✓  MacPorts is already installed (Version: 2.11.6).

Downloads the correct .pkg from GitHub releases for your macOS version.

macstrap local upgrade-python

Ensure Python meets the minimum version for remote commands (currently 3.10+):

% macstrap local upgrade-python
  Current Python: 3.9.6
  Required:       3.10+ (for remote commands)

  Installing python@3.12 via Homebrew...
✓  Python 3.12.x installed via Homebrew.

Next step: reinstall macstrap with remote support using the new Python:
  /opt/homebrew/bin/python3 -m pip install macstrap[remote]

If Python already meets the requirement:

% macstrap local upgrade-python
✓  Python 3.12.8 already meets the minimum (3.10+).

macstrap local setup-server-mode

Configure macOS for always-on server use (ideal for a headless Mac mini):

% macstrap local setup-server-mode

Configuring server mode...
  You may be prompted for your sudo password.

  ✓  永不休眠 (system sleep disabled)
  ✓  螢幕永不關閉 (display sleep disabled)
  ✓  硬碟永不休眠 (disk sleep disabled)
  ✓  斷電後自動重新開機 (auto restart on power loss)
  ✓  網路喚醒 (Wake-on-LAN enabled)

  ✓  5 setting(s) applied, 0 already correct.

Safe to re-run — already-correct settings are skipped.


Remote Commands

Run these from another machine to manage Macs over SSH.

1. Initialise config files

% macstrap init
% macstrap init --examples

This creates a settings.toml and package list files:

settings.toml               # enable/disable roles (see below)
packages-macports.txt       # MacPorts packages
packages-brew.txt           # Homebrew formulae
packages-brew-casks.txt     # Homebrew casks (GUI apps)
packages-npm-global.txt     # Global npm packages (installed via nvm)
packages-pip-global.txt     # Global pip packages

Edit the package files like a shopping list — one package per line, # for comments:

# packages-brew.txt
git
gh
fzf
ripgrep

Use --examples to also create starter config directories under examples/ (such as ai-cli, openclaw, utilities-dev, php8.3-dev).


2. Configure roles via settings.toml

Control which setup roles are enabled:

# settings.toml
[roles]
macports = false
homebrew = true
nvm = false
openjdk = false
docker = false
shell = true
pip = true
Role Default What it does
macports off Installs MacPorts and packages from packages-macports.txt
homebrew on Installs Homebrew formulae from packages-brew.txt and casks from packages-brew-casks.txt
nvm off Installs nvm, Node.js (v22), and global npm packages from packages-npm-global.txt
openjdk off Configures Java symlink/PATH for Homebrew OpenJDK
docker off Checks Docker Desktop status (install via packages-brew-casks.txt)
shell on Deploys unified ~/.zshrc with PATH entries for enabled tools
pip on Installs global pip packages from packages-pip-global.txt

Only enabled roles run during macstrap run. If settings.toml is missing, defaults apply.


3. Register a host

ssh-auth does two things:

  1. Copy your SSH public key to the remote Mac (one-time, requires SSH password)
  2. Store the sudo password in the system credential store
% macstrap ssh-auth mac-mini.local
% macstrap ssh-auth mac-mini.local --user remoteuser
% macstrap ssh-auth 192.168.1.100 --user remoteuser

If SSH key auth is already set up, skip the key copy:

% macstrap ssh-auth mac-mini.local --skip-key-copy

You can register multiple machines — each gets its own credential entry.


4. Run setup

% macstrap run
% macstrap run mac-mini.local
% macstrap run 192.168.1.100
% macstrap run remoteuser@mac-mini.local
% macstrap run --config examples/ai-cli --config examples/openclaw 192.168.1.100

macstrap connects over SSH, reads your settings.toml and package files, and installs everything that isn't already there. First run takes 30-60 minutes (MacPorts compiles from source). Subsequent runs take about 1 minute.

SSH user resolution order (highest priority first):

  1. --user flag
  2. user@host inline syntax
  3. SSH user stored at ssh-auth time
  4. Local OS username (fallback)

5. Verify installation

Check whether every package in your files is installed on the remote Mac:

% macstrap verify
% macstrap verify mac-mini.local
% macstrap verify 192.168.1.100 --user remoteuser

Output example:

  Tools
  ✓  brew       Homebrew 4.4.12
  ✓  port       Version: 2.12.3
  ✓  nvm        0.40.1
  ✓  node       v22.14.0
  ✗  docker     not found
  ✓  java       openjdk version "21.0.7" 2025-04-15
  ✓  python3    Python 3.13.2

  Homebrew (2/3)
  ✓  git
  ✓  ripgrep
  ✗  htop  (missing)

  ⚠  2 installed, 1 missing

Exits with code 0 if everything is present, 1 if anything is missing — useful in CI or scripts.


Run only part of the setup

Use --tag to apply a single role:

% macstrap run --tag homebrew
% macstrap run mac-mini.local --tag macports
% macstrap run --tag nvm
% macstrap run --tag shell
% macstrap run --tag pip

Available tags: macports homebrew nvm npm docker openjdk shell pip

Note: --tag selects which roles to consider, but settings.toml is the master switch. A role disabled in settings will be skipped even if selected by --tag.

Dry run

Preview what would change without applying:

% macstrap run --check
% macstrap run mac-mini.local --check

Manage registered hosts

% macstrap list

  macstrap credential store  (macOS Keychain)
  Default target: mac-mini.local

  Host              User          Password
  mac-mini.local    remoteuser    ✓ stored   ← default
  192.168.1.200     admin         ✓ stored
% macstrap delete mac-mini.local
% macstrap delete-all

Example: install OpenJDK + Docker Desktop

macstrap init

# Enable the roles in settings.toml
# [roles]
# homebrew = true
# openjdk = true
# docker = true

echo "openjdk" >> packages-brew.txt
echo "docker" >> packages-brew-casks.txt
macstrap ssh-auth mac-mini.local
macstrap run mac-mini.local
macstrap verify mac-mini.local

After Docker Desktop is installed, sign in to the target Mac desktop and launch Docker once to accept the licence and complete first-run setup.


What gets installed

macstrap first bootstraps the target Mac over raw SSH (installing Xcode CLT if missing), then runs enabled Ansible roles:

Phase Role What it does
Bootstrap (raw SSH) Installs Xcode Command Line Tools via softwareupdate if missing
Main macports Installs MacPorts and packages from packages-macports.txt
Main homebrew Installs Homebrew formulae and casks
Main nvm Installs nvm, Node.js, and global npm packages
Main docker Checks Docker Desktop status
Main openjdk Configures Java symlink/PATH for Homebrew OpenJDK
Main shell Deploys unified ~/.zshrc with PATH entries for all tools
Main pip_global Installs global pip packages

All roles are idempotent — re-running only installs what is missing. Each role is controlled by settings.toml and only runs when enabled.

Fresh Mac note

On a brand-new Mac, macOS intercepts /usr/bin/python3 and shows an installation dialog instead of running Python. macstrap handles this automatically over SSH: the bootstrap phase uses raw SSH commands (no Python needed) to install Xcode CLT first, then hands off to the normal Ansible playbook.


Credential storage

Platform Storage location
macOS Keychain (security command) — visible in Keychain Access
Linux ~/.config/macstrap/ directory (chmod 600)

Key naming convention:

macstrap-target          → current default host
macstrap-hosts           → comma-separated index of all registered hosts
macstrap-pass-{host}     → sudo password for a specific host
macstrap-user-{host}     → SSH username for a specific host

Requirements

  • Local commands: Python 3.9+ (included with Xcode / Xcode CLT)
  • Remote commands: Python 3.10+ and pip install macstrap[remote] (adds ansible-core)
  • SSH access to the target Mac (key-based auth is set up automatically by ssh-auth)
  • macOS 13+ on the target machine

License

MIT © changyy

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

macstrap-1.5.0.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

macstrap-1.5.0-py3-none-any.whl (56.1 kB view details)

Uploaded Python 3

File details

Details for the file macstrap-1.5.0.tar.gz.

File metadata

  • Download URL: macstrap-1.5.0.tar.gz
  • Upload date:
  • Size: 34.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for macstrap-1.5.0.tar.gz
Algorithm Hash digest
SHA256 225e36b3fc7457fe88cf32e4c531670d3352149ddef2e038153a113f2f1d5943
MD5 95985e53a45ad51ecbb4c25fb9338ef7
BLAKE2b-256 7d5d7cef7163fed900603938ad0351b9e235cfa998df79a835da1bb405f34a7b

See more details on using hashes here.

Provenance

The following attestation bundles were made for macstrap-1.5.0.tar.gz:

Publisher: python-publish.yml on changyy/py-macstrap

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macstrap-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: macstrap-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 56.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for macstrap-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ba2944a4af0de71a48864bca7f3f29b3a052df339614922695bb8798d7b5e5fe
MD5 0f7201174e2a9d798e5efe4884378ff3
BLAKE2b-256 2c323d2d1100315fca45b134cf33c3c10e0d0a0c9f895947940ed5c1e753d614

See more details on using hashes here.

Provenance

The following attestation bundles were made for macstrap-1.5.0-py3-none-any.whl:

Publisher: python-publish.yml on changyy/py-macstrap

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page