Skip to content

Instantly share code, notes, and snippets.

@timstew
Created March 16, 2026 19:02
Show Gist options
  • Select an option

  • Save timstew/f5e49d8133f45e6eae950b4fcb2d93d8 to your computer and use it in GitHub Desktop.

Select an option

Save timstew/f5e49d8133f45e6eae950b4fcb2d93d8 to your computer and use it in GitHub Desktop.
OpenClaw Mac Mini — Enable Remote Access setup script
#!/bin/bash
# ============================================================================
# OpenClaw Mac Mini — Enable Remote Access
# ============================================================================
# This script configures a Mac Mini for headless remote access.
# A non-technical person can run this by double-clicking the file or via curl.
#
# What it does:
# 1. Enables SSH (Remote Login)
# 2. Enables Screen Sharing (VNC/Remote Desktop)
# 3. Configures the firewall to allow remote connections
# 4. Enables automatic restart after power loss
# 5. Installs and launches Tailscale for secure remote access
# 6. Writes a verification report to ~/Desktop
#
# Designed for: Field technicians following GUIDE-1-field-tech-setup.md
# ============================================================================
# --- Error handling -----------------------------------------------------------
# We intentionally do NOT use set -euo pipefail. This script is run by
# non-technical users — one failure should not kill the entire process.
# Each step handles its own errors and reports them in the final summary.
ERRORS=()
WARNINGS=()
PASSES=()
CURRENT_USER=""
TAILSCALE_IP=""
REPORT_FILE="" # Set after we determine CURRENT_USER
# --- Helpers ----------------------------------------------------------------
log() {
echo "$(date '+%H:%M:%S') [OK] $1"
PASSES+=("$1")
}
warn() {
echo "$(date '+%H:%M:%S') [!!] $1"
WARNINGS+=("$1")
}
fail() {
echo "$(date '+%H:%M:%S') [ERR] $1"
ERRORS+=("$1")
}
dialog() {
osascript -e "display dialog \"$1\" with title \"OpenClaw Setup\" buttons {\"OK\"} default button \"OK\"" 2>/dev/null || true
}
dialog_info() {
osascript -e "display notification \"$1\" with title \"OpenClaw Setup\"" 2>/dev/null || true
}
ask_yes_no() {
local result
result=$(osascript -e "display dialog \"$1\" with title \"OpenClaw Setup\" buttons {\"No\", \"Yes\"} default button \"Yes\"" 2>/dev/null) || true
[[ "$result" == *"Yes"* ]]
}
# --- Welcome ----------------------------------------------------------------
dialog "Welcome to OpenClaw Mac Mini Setup!
This script will configure this Mac for secure remote access. It takes about 2 minutes.
What will happen:
1. Enable Remote Login (SSH)
2. Enable Screen Sharing
3. Configure the firewall
4. Set power options (auto-restart, no sleep)
5. Set up Tailscale
Click OK to begin."
echo ""
echo "=============================================="
echo " OpenClaw Mac Mini — Remote Access Setup"
echo "=============================================="
echo ""
# --- Pre-flight checks ------------------------------------------------------
# Check we're on macOS
if [[ "$(uname)" != "Darwin" ]]; then
dialog "This script only runs on macOS. Exiting."
exit 1
fi
# Check for admin privileges; request them if needed
if [[ $EUID -ne 0 ]]; then
echo "This script requires administrator privileges."
echo "You will be prompted for your password."
echo ""
exec sudo "$0" "$@"
fi
CURRENT_USER="${SUDO_USER:-$(logname 2>/dev/null || echo $USER)}"
REPORT_FILE="/Users/$CURRENT_USER/Desktop/remote-access-setup-report.txt"
log "Running as administrator (configuring for user: $CURRENT_USER)"
echo ""
# --- 1. Enable Remote Login (SSH) ------------------------------------------
echo "--- Step 1/5: Enabling Remote Login (SSH) ---"
dialog_info "Step 1/5: Enabling Remote Login (SSH)..."
if systemsetup -getremotelogin 2>/dev/null | grep -q "On"; then
log "Remote Login is already enabled"
else
if systemsetup -setremotelogin on 2>/dev/null; then
log "Remote Login enabled"
elif launchctl load -w /System/Library/LaunchDaemons/ssh.plist 2>/dev/null; then
log "Remote Login enabled via launchctl"
else
fail "Could not enable Remote Login — enable manually: System Settings > General > Sharing > Remote Login"
fi
fi
echo ""
# --- 2. Enable Screen Sharing (VNC / Remote Desktop) -----------------------
echo "--- Step 2/5: Enabling Screen Sharing ---"
dialog_info "Step 2/5: Enabling Screen Sharing..."
if launchctl load -w /System/Library/LaunchDaemons/com.apple.screensharing.plist 2>/dev/null; then
log "Screen Sharing enabled"
else
if defaults write /var/db/launchd.db/com.apple.launchd/overrides.plist com.apple.screensharing -dict Disabled -bool false 2>/dev/null && \
launchctl load /System/Library/LaunchDaemons/com.apple.screensharing.plist 2>/dev/null; then
log "Screen Sharing enabled"
else
fail "Could not enable Screen Sharing — enable manually: System Settings > General > Sharing > Screen Sharing"
fi
fi
echo ""
# --- 3. Configure Firewall -------------------------------------------------
echo "--- Step 3/5: Configuring Firewall ---"
dialog_info "Step 3/5: Configuring Firewall..."
# Enable the firewall
FIREWALL_STATE=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null || echo "unknown")
if echo "$FIREWALL_STATE" | grep -q "enabled"; then
log "Firewall is already enabled"
else
if /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on 2>/dev/null; then
log "Firewall enabled"
else
warn "Could not enable firewall — check System Settings > Network > Firewall"
fi
fi
# CRITICAL: Ensure "Block all incoming connections" is OFF
if /usr/libexec/ApplicationFirewall/socketfilterfw --setblockall off 2>/dev/null; then
log "\"Block all incoming connections\" is disabled (required for SSH)"
else
warn "Could not disable \"Block all incoming\" — check System Settings > Network > Firewall > Options"
fi
# Allow sshd through the firewall
SSHD_PATH="/usr/sbin/sshd"
if [[ -f "$SSHD_PATH" ]]; then
/usr/libexec/ApplicationFirewall/socketfilterfw --add "$SSHD_PATH" 2>/dev/null || true
if /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp "$SSHD_PATH" 2>/dev/null; then
log "Firewall: sshd allowed"
else
warn "Could not configure sshd firewall rule"
fi
fi
# Allow sshd-session (handles post-authentication; blocking it drops SSH mid-handshake)
for SSHD_SESSION_PATH in /usr/libexec/sshd-session /usr/sbin/sshd-session; do
if [[ -f "$SSHD_SESSION_PATH" ]]; then
/usr/libexec/ApplicationFirewall/socketfilterfw --add "$SSHD_SESSION_PATH" 2>/dev/null || true
if /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp "$SSHD_SESSION_PATH" 2>/dev/null; then
log "Firewall: sshd-session allowed ($SSHD_SESSION_PATH)"
else
warn "Could not configure sshd-session firewall rule"
fi
break
fi
done
# Enable stealth mode
if /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on 2>/dev/null; then
log "Stealth mode enabled"
else
warn "Could not enable stealth mode"
fi
echo ""
# --- 4. Enable Auto-Restart After Power Loss -------------------------------
echo "--- Step 4/5: Configuring Power Settings ---"
dialog_info "Step 4/5: Configuring Power Settings..."
if systemsetup -setrestartfreeze on 2>/dev/null; then
log "Auto-restart after freeze enabled"
else
warn "Could not enable auto-restart after freeze"
fi
if systemsetup -setrestartpowerfailure on 2>/dev/null; then
log "Auto-restart after power failure enabled"
else
warn "Could not enable auto-restart after power failure"
fi
# Prevent sleep (headless server should never sleep)
pmset -a displaysleep 0 2>/dev/null && log "Display sleep disabled" || warn "Could not disable display sleep"
pmset -a sleep 0 2>/dev/null && log "System sleep disabled" || warn "Could not disable system sleep"
pmset -a disksleep 0 2>/dev/null && log "Disk sleep disabled" || warn "Could not disable disk sleep"
# Wake on network access (Wake-on-LAN)
pmset -a womp 1 2>/dev/null && log "Wake on network access enabled" || warn "Could not enable Wake on LAN"
echo ""
# --- 5. Tailscale -----------------------------------------------------------
echo "--- Step 5/5: Tailscale (Secure Remote Access) ---"
dialog_info "Step 5/5: Setting up Tailscale..."
TAILSCALE_INSTALLED=false
if [[ -d "/Applications/Tailscale.app" ]] || command -v tailscale &>/dev/null; then
log "Tailscale is already installed"
TAILSCALE_INSTALLED=true
else
echo ""
echo "Tailscale provides the secure network tunnel to this Mac."
echo "Attempting to install from the Mac App Store..."
echo ""
# Try automated install via mas (Mac App Store CLI)
if command -v mas &>/dev/null; then
if mas install 1475387142 2>/dev/null; then
log "Tailscale installed from the Mac App Store"
TAILSCALE_INSTALLED=true
fi
fi
# If automated install didn't work, open the App Store page and guide the user
if [[ "$TAILSCALE_INSTALLED" == "false" ]]; then
echo "Automatic install not available. Opening the App Store..."
open "macappstore://apps.apple.com/app/tailscale/id1475387142" 2>/dev/null || \
open "https://apps.apple.com/app/tailscale/id1475387142" 2>/dev/null || true
dialog "Tailscale needs to be installed manually.
The App Store should have opened. Please:
1. Click \"Get\" then \"Install\"
2. Wait for it to finish
3. Click OK here when done"
# Check again
if [[ -d "/Applications/Tailscale.app" ]] || command -v tailscale &>/dev/null; then
log "Tailscale installed manually"
TAILSCALE_INSTALLED=true
else
fail "Tailscale not found after manual install attempt — install it from the App Store before continuing"
fi
fi
fi
# Launch Tailscale and prompt sign-in
if [[ "$TAILSCALE_INSTALLED" == "true" ]]; then
# Launch the app if not running
if ! pgrep -x "Tailscale" > /dev/null 2>&1; then
sudo -u "$CURRENT_USER" open -a Tailscale 2>/dev/null || true
sleep 3
fi
dialog "Tailscale is installed. Now you need to sign in.
1. Look for the Tailscale icon in the menu bar (top-right of screen)
2. Click it and choose \"Sign in...\" or \"Log in...\"
3. Sign in with the Tailscale credentials from your setup packet
4. When the sign-in page says \"Success\" or you see a connected status, click OK here"
# Try to get the Tailscale IP
sleep 2
if command -v tailscale &>/dev/null; then
TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "")
fi
# If CLI not available, try reading from the app's status
if [[ -z "$TAILSCALE_IP" ]]; then
# The App Store version may not have the CLI in PATH; try the bundled binary
TAILSCALE_CLI="/Applications/Tailscale.app/Contents/MacOS/Tailscale"
if [[ -x "$TAILSCALE_CLI" ]]; then
TAILSCALE_IP=$("$TAILSCALE_CLI" ip -4 2>/dev/null || echo "")
fi
fi
if [[ -n "$TAILSCALE_IP" ]]; then
log "Tailscale connected — IP: $TAILSCALE_IP"
else
warn "Could not detect Tailscale IP — make sure you signed in. Check the Tailscale icon in the menu bar for the IP (looks like 100.x.x.x)"
fi
fi
echo ""
# --- Verification -----------------------------------------------------------
echo "=============================================="
echo " Verifying Configuration..."
echo "=============================================="
echo ""
VERIFY_ERRORS=()
# Check SSH
if systemsetup -getremotelogin 2>/dev/null | grep -q "On"; then
echo " [PASS] Remote Login (SSH) is ON"
else
echo " [FAIL] Remote Login (SSH) is OFF"
VERIFY_ERRORS+=("Remote Login is not enabled")
fi
# Check Screen Sharing
if launchctl list 2>/dev/null | grep -q "com.apple.screensharing"; then
echo " [PASS] Screen Sharing is running"
else
echo " [FAIL] Screen Sharing is not running"
VERIFY_ERRORS+=("Screen Sharing is not running")
fi
# Check firewall
if /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null | grep -q "enabled"; then
echo " [PASS] Firewall is enabled"
else
echo " [FAIL] Firewall is not enabled"
VERIFY_ERRORS+=("Firewall is not enabled")
fi
# Check block-all is off
if /usr/libexec/ApplicationFirewall/socketfilterfw --getblockall 2>/dev/null | grep -q "DISABLED"; then
echo " [PASS] Block-all is disabled"
else
echo " [FAIL] Block-all is still enabled (SSH will be blocked!)"
VERIFY_ERRORS+=("Block-all is enabled — SSH will not work")
fi
# Check sleep
SLEEP_VAL=$(pmset -g 2>/dev/null | grep "^ sleep" | awk '{print $2}' || echo "unknown")
if [[ "$SLEEP_VAL" == "0" ]]; then
echo " [PASS] System sleep is disabled"
else
echo " [FAIL] System sleep is set to $SLEEP_VAL (should be 0)"
VERIFY_ERRORS+=("System sleep is not disabled (value: $SLEEP_VAL)")
fi
# Check auto-restart
if systemsetup -getrestartpowerfailure 2>/dev/null | grep -qi "on"; then
echo " [PASS] Auto-restart after power failure is ON"
else
echo " [FAIL] Auto-restart after power failure is OFF"
VERIFY_ERRORS+=("Auto-restart after power failure is not enabled")
fi
# Check Tailscale
if [[ -d "/Applications/Tailscale.app" ]] || command -v tailscale &>/dev/null; then
echo " [PASS] Tailscale is installed"
else
echo " [FAIL] Tailscale is not installed"
VERIFY_ERRORS+=("Tailscale is not installed")
fi
if [[ -n "$TAILSCALE_IP" ]]; then
echo " [PASS] Tailscale IP: $TAILSCALE_IP"
else
echo " [WARN] Tailscale IP not detected — check menu bar icon"
fi
echo ""
# --- Write Report to Desktop ------------------------------------------------
{
echo "=============================================="
echo " OpenClaw Remote Access Setup Report"
echo " $(date)"
echo " Mac User: $CURRENT_USER"
echo "=============================================="
echo ""
echo "PASSED:"
for item in "${PASSES[@]}"; do
echo " [OK] $item"
done
echo ""
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
echo "WARNINGS:"
for item in "${WARNINGS[@]}"; do
echo " [!!] $item"
done
echo ""
fi
if [[ ${#ERRORS[@]} -gt 0 ]]; then
echo "ERRORS:"
for item in "${ERRORS[@]}"; do
echo " [ERR] $item"
done
echo ""
fi
echo "VERIFICATION:"
if [[ ${#VERIFY_ERRORS[@]} -eq 0 ]]; then
echo " All checks passed."
else
for item in "${VERIFY_ERRORS[@]}"; do
echo " [FAIL] $item"
done
fi
echo ""
if [[ -n "$TAILSCALE_IP" ]]; then
echo "TAILSCALE IP: $TAILSCALE_IP"
else
echo "TAILSCALE IP: (not detected — check Tailscale menu bar icon)"
fi
echo ""
echo "NEXT STEP: Send the Tailscale IP to your remote administrator."
echo "=============================================="
} > "$REPORT_FILE" 2>/dev/null
if [[ -f "$REPORT_FILE" ]]; then
log "Report saved to Desktop: remote-access-setup-report.txt"
else
warn "Could not write report to Desktop"
fi
echo ""
# --- Summary Dialog ---------------------------------------------------------
SUMMARY_STATUS="Setup complete!"
if [[ ${#ERRORS[@]} -gt 0 ]]; then
SUMMARY_STATUS="Setup finished with ${#ERRORS[@]} error(s). Check the report on your Desktop."
elif [[ ${#WARNINGS[@]} -gt 0 ]]; then
SUMMARY_STATUS="Setup finished with ${#WARNINGS[@]} warning(s). Check the report on your Desktop."
fi
TAILSCALE_MSG=""
if [[ -n "$TAILSCALE_IP" ]]; then
TAILSCALE_MSG="
Tailscale IP: $TAILSCALE_IP
Send this IP to your remote administrator now."
else
TAILSCALE_MSG="
Tailscale IP: Check the Tailscale icon in the menu bar (top-right).
The IP looks like 100.x.x.x
Send that IP to your remote administrator."
fi
dialog "$SUMMARY_STATUS
$TAILSCALE_MSG
A report has been saved to your Desktop:
remote-access-setup-report.txt
DO NOT disconnect the monitor/keyboard until the admin confirms they can connect."
echo "=============================================="
echo " $SUMMARY_STATUS"
echo "=============================================="
echo ""
if [[ -n "$TAILSCALE_IP" ]]; then
echo " Tailscale IP: $TAILSCALE_IP"
echo ""
echo " >>> Send this IP to your remote administrator <<<"
else
echo " Tailscale IP: Check the Tailscale menu bar icon"
echo ""
echo " >>> Send the IP to your remote administrator <<<"
fi
echo ""
echo " Report saved to: $REPORT_FILE"
echo ""
echo " DO NOT disconnect monitor/keyboard until the"
echo " admin confirms they can connect remotely."
echo ""
echo "=============================================="
echo ""
echo "You can close this window now."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment