Skip to content

Instantly share code, notes, and snippets.

@fxstein
Last active February 18, 2026 21:01
Show Gist options
  • Select an option

  • Save fxstein/10693ecc34c80fb7c4cf056d850bddf1 to your computer and use it in GitHub Desktop.

Select an option

Save fxstein/10693ecc34c80fb7c4cf056d850bddf1 to your computer and use it in GitHub Desktop.
Pi Mac Mini bootstrap — fresh machine to running Pi instance
#!/usr/bin/env bash
# ============================================================
# PI BOOTSTRAP — Set up a fresh Mac Mini as a Pi instance
# Usage: curl -fsSL <gist-url> | bash
#
# Prerequisites: macOS, admin user, internet
# Everything else is installed by this script.
# ============================================================
set -euo pipefail
echo "🥧 Pi Mac Mini Bootstrap"
echo "========================"
echo ""
# =============================================================
# 1. HOMEBREW
# =============================================================
if command -v brew &>/dev/null; then
echo "✅ Homebrew installed"
else
echo "📦 Installing Homebrew..."
# shellcheck disable=SC2312 # curl piped install is standard for Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Configure shell environment for Homebrew (Apple Silicon or Intel)
BREW_BIN=""
if [[ -f /opt/homebrew/bin/brew ]]; then
BREW_BIN="/opt/homebrew/bin/brew"
elif [[ -f /usr/local/bin/brew ]]; then
BREW_BIN="/usr/local/bin/brew"
fi
if [[ -n "${BREW_BIN}" ]]; then
# shellcheck disable=SC2312 # Command substitution in eval is expected
eval "$("${BREW_BIN}" shellenv)"
if ! grep -q 'brew shellenv' "${HOME}/.zprofile" 2>/dev/null; then
# shellcheck disable=SC2016 # Single quotes intentional - write literal string to file
echo "eval \"\$(${BREW_BIN} shellenv)\"" >> "${HOME}/.zprofile"
fi
fi
fi
echo ""
# =============================================================
# 2. CORE TOOLS
# =============================================================
echo "📦 Installing tools..."
TOOLS=(git gh cloudflared 1password-cli chezmoi)
# OrbStack for Apple Silicon, Docker Desktop for Intel
# shellcheck disable=SC2312 # uname in command substitution is standard
if [[ "$(uname -m)" == "arm64" ]]; then
CASKS=(orbstack)
else
CASKS=(docker)
fi
for tool in "${TOOLS[@]}"; do
if command -v "${tool}" &>/dev/null; then
echo " ✅ ${tool}"
else
echo " 📦 ${tool}..."
brew install "${tool}"
fi
done
for cask in "${CASKS[@]}"; do
# Check both brew and whether the app is already installed manually
if brew list --cask "${cask}" &>/dev/null 2>&1; then
echo " ✅ ${cask} (brew)"
elif [[ "${cask}" == "docker" ]] && [[ -d "/Applications/Docker.app" ]]; then
echo " ✅ ${cask} (manual install)"
elif [[ "${cask}" == "orbstack" ]] && [[ -d "/Applications/OrbStack.app" ]]; then
echo " ✅ ${cask} (manual install)"
else
echo " 📦 ${cask}..."
brew install --cask "${cask}"
fi
done
echo ""
# =============================================================
# 3. ENABLE SSH (Remote Login)
# =============================================================
echo "🔐 Enabling Remote Login (SSH)..."
if sudo systemsetup -getremotelogin 2>/dev/null | grep -q "On"; then
echo " ✅ Remote Login already enabled"
else
if sudo systemsetup -setremotelogin on 2>/dev/null; then
echo " ✅ Remote Login enabled"
else
echo " ⚠️ Could not enable Remote Login (needs Full Disk Access)."
echo " Enable manually: System Settings → General → Sharing → Remote Login"
fi
fi
echo ""
# =============================================================
# 4. GITHUB AUTH
# =============================================================
echo "🔑 GitHub authentication..."
if gh auth status &>/dev/null 2>&1; then
# shellcheck disable=SC2312 # Command substitution expected here
echo " ✅ Already authenticated as $(gh api user --jq .login)"
else
echo " Please authenticate with GitHub (need access to pionizer org):"
gh auth login
fi
echo ""
# =============================================================
# 5. CLONE REPOS
# =============================================================
echo "📂 Cloning repositories..."
OPENCLAW_DIR="${HOME}/openclaw"
PIINFRA_DIR="${HOME}/pi-infra"
if [[ -d "${OPENCLAW_DIR}/.git" ]]; then
echo " ✅ ~/openclaw exists"
else
echo " 📦 Cloning OpenClaw..."
gh repo clone openclaw/openclaw "${OPENCLAW_DIR}"
fi
if [[ -d "${PIINFRA_DIR}/.git" ]]; then
echo " ✅ ~/pi-infra exists"
cd "${PIINFRA_DIR}" && git pull --quiet
else
echo " 📦 Cloning pi-infra..."
gh repo clone pionizer/pi-infra "${PIINFRA_DIR}"
fi
PICORE_DIR="${HOME}/pi-core"
if [[ -d "${PICORE_DIR}/.git" ]]; then
echo " ✅ ~/pi-core exists"
cd "${PICORE_DIR}" && git pull --quiet
else
echo " 📦 Cloning pi-core..."
gh repo clone pionizer/pi-core "${PICORE_DIR}"
fi
NUCLEUS_DIR="${HOME}/nucleus"
if [[ -d "${NUCLEUS_DIR}/.git" ]]; then
echo " ✅ ~/nucleus exists"
cd "${NUCLEUS_DIR}" && git pull --quiet
else
echo " 📦 Cloning nucleus..."
gh repo clone pionizer/nucleus "${NUCLEUS_DIR}"
fi
echo ""
# =============================================================
# 6. INSTALL OPENCLAW CLI
# =============================================================
echo "🔧 Installing openclaw CLI..."
mkdir -p "${HOME}/.local/bin"
cp "${PIINFRA_DIR}/pion-wrapper.sh" "${HOME}/.local/bin/pion"
chmod +x "${HOME}/.local/bin/pion"
ln -sf "${HOME}/.local/bin/pion" "${HOME}/.local/bin/openclaw"
# Ensure ~/.local/bin is on PATH for this session and future shells
export PATH="${HOME}/.local/bin:${PATH}"
if ! grep -q '\.local/bin' "${HOME}/.zshenv" 2>/dev/null; then
echo 'export PATH="${HOME}/.local/bin:${PATH}"' >> "${HOME}/.zshenv"
echo " ✅ Added ~/.local/bin to PATH in ~/.zshenv"
fi
echo " ✅ ~/.local/bin/pion + openclaw"
echo ""
# =============================================================
# 7. SECRETS DIRECTORY
# =============================================================
echo "📁 Setting up directories..."
mkdir -p "${HOME}/.openclaw/secrets"
mkdir -p "${HOME}/.openclaw/workspace"
echo " ✅ ~/.openclaw/"
echo ""
# =============================================================
# 8. 1PASSWORD SERVICE ACCOUNT
# =============================================================
echo "🔐 1Password setup..."
if [[ -n "${OP_SERVICE_ACCOUNT_TOKEN:-}" ]]; then
echo " ✅ OP_SERVICE_ACCOUNT_TOKEN set"
else
echo ""
echo " ⚠️ OP_SERVICE_ACCOUNT_TOKEN not set."
echo " Get it from 1Password → Settings → Service Accounts → pi-infra"
echo ""
read -r -p " Paste token (or Enter to skip): " OP_TOKEN
if [[ -n "${OP_TOKEN}" ]]; then
export OP_SERVICE_ACCOUNT_TOKEN="${OP_TOKEN}"
# Add to shell profile
# Write to .zshenv (loaded by ALL shells, including non-interactive SSH)
if ! grep -q "OP_SERVICE_ACCOUNT_TOKEN" "${HOME}/.zshenv" 2>/dev/null; then
echo "export OP_SERVICE_ACCOUNT_TOKEN=\"${OP_TOKEN}\"" >> "${HOME}/.zshenv"
echo " ✅ Added to ~/.zshenv"
fi
else
echo " ⏭️ Skipped — run 'openclaw setup' after setting it"
fi
fi
echo ""
# =============================================================
# 9. INSTANCE CONFIGURATION (must run before chezmoi)
# =============================================================
echo "🏷️ Instance setup..."
echo ""
echo " Each Pi instance needs an ID (single letter):"
echo " o = Oliver"
echo " m = Markus"
echo " s = Susanne"
echo ""
read -r -p " Instance ID [o]: " INSTANCE_ID
INSTANCE_ID="${INSTANCE_ID:-o}"
echo ""
# Map single letter to full instance name for chezmoi
# Note: no associative arrays — macOS ships with Bash 3.2
case "${INSTANCE_ID}" in
o) PION_INSTANCE="oliver" ;;
m) PION_INSTANCE="markus" ;;
s) PION_INSTANCE="susanne" ;;
*) PION_INSTANCE="${INSTANCE_ID}" ;;
esac
export PION_INSTANCE
# Store instance identity
if ! grep -q "^export PI_INSTANCE_ID=" "${HOME}/.zshrc" 2>/dev/null; then
echo "export PI_INSTANCE_ID=\"${INSTANCE_ID}\"" >> "${HOME}/.zshrc"
fi
# PION_INSTANCE in .zshenv (needed for SSH + chezmoi)
if ! grep -q "^export PION_INSTANCE=" ~/.zshenv 2>/dev/null; then
echo "export PION_INSTANCE=\"${PION_INSTANCE}\"" >> ~/.zshenv
echo " ✅ PION_INSTANCE=${PION_INSTANCE} added to ~/.zshenv"
fi
if ! grep -q "${INSTANCE_ID}-gluon" "${HOME}/.zshrc" 2>/dev/null; then
echo "PROMPT=\"🥧 ${INSTANCE_ID}-gluon %1~ \$ \"" >> "${HOME}/.zshrc"
echo " ✅ Shell prompt set: 🥧 ${INSTANCE_ID}-gluon"
fi
# =============================================================
# 10. WORKSPACE — Clone instance repo
# =============================================================
WORKSPACE_DIR="${HOME}/openclaw/workspace"
INSTANCE_REPO="pionizer/pi-${PION_INSTANCE}"
echo "📂 Setting up workspace..."
if [[ -d "${WORKSPACE_DIR}/.git" ]]; then
echo " ✅ ${WORKSPACE_DIR} exists"
cd "${WORKSPACE_DIR}" && git pull --quiet
else
echo " 📦 Cloning ${INSTANCE_REPO}..."
rm -rf "${WORKSPACE_DIR}"
gh repo clone "${INSTANCE_REPO}" "${WORKSPACE_DIR}"
fi
echo ""
# =============================================================
# 11. CHEZMOI — Generate secrets
# =============================================================
if [[ -n "${OP_SERVICE_ACCOUNT_TOKEN:-}" ]]; then
echo "🔑 Generating secrets via chezmoi..."
rm -rf "${HOME}/.config/chezmoi" "${HOME}/.local/share/chezmoi"
cp -r "${PIINFRA_DIR}/chezmoi" "${HOME}/.local/share/chezmoi"
chezmoi init --force
chezmoi apply --force
echo " ✅ ~/.openclaw/.env generated"
else
echo "⏭️ Skipping chezmoi (no 1Password token)"
fi
# =============================================================
# 11. BUILD & START
# =============================================================
echo "🏗️ Building Pi..."
echo ""
# OrbStack needs to be running
if ! docker info &>/dev/null 2>&1; then
# shellcheck disable=SC2312 # uname in command substitution is standard
DOCKER_APP=$([[ "$(uname -m)" == "arm64" ]] && echo "OrbStack" || echo "Docker Desktop")
echo " ⚠️ Docker not running. Start ${DOCKER_APP} first, then run:"
echo " openclaw build"
echo " openclaw tunnel setup --instance ${INSTANCE_ID}"
echo ""
else
openclaw build
echo ""
# =============================================================
# 12. TUNNEL
# =============================================================
echo "🔐 Setting up Cloudflare tunnel..."
echo ""
echo " ⚠️ This needs a tunnel credential file."
echo " If this is a NEW instance, create a tunnel first:"
echo " cloudflared tunnel create pi-${INSTANCE_ID}"
echo ""
read -r -p " Run tunnel setup now? [y/N]: " REPLY
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
openclaw tunnel setup --instance "${INSTANCE_ID}"
fi
fi
echo ""
echo "============================================"
echo "✅ Pi bootstrap complete!"
echo "============================================"
echo ""
echo " Instance: ${INSTANCE_ID}"
echo " CLI: openclaw help"
echo " Start: openclaw gateway start"
echo " Build: openclaw build"
echo " SSH: openclaw tunnel setup --instance ${INSTANCE_ID}"
echo ""
echo " If you skipped any steps, run: openclaw setup"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment