---
name: "Mac Remote SSH"
description: "SSH-based Mac operations — launchd services, Homebrew, system defaults, scripting, and system info. For controlling Mac Minis and other macOS machines remotely."
version: "1.0.0"
author: "skynet"
category: "ops"
agents: ["claude-code", "codex", "gemini"]
tags: ["mac", "ssh", "launchd", "homebrew", "remote"]
tools_required: ["bash", "ssh"]
---

# Mac Remote SSH

# Mac Remote SSH

Control macOS machines remotely via SSH. Covers launchd services, Homebrew, system defaults, scripting, and diagnostics.

## Prerequisites

- SSH access configured (e.g., `ssh bots`, `ssh vault`, `ssh jarvis`)
- Target machine has SSH enabled: System Settings → General → Sharing → Remote Login

## IMPORTANT: PATH Setup

SSH non-login shells do NOT have Homebrew in PATH on Apple Silicon Macs. Always source brew at the start of commands that need it:

```bash
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew list'
```

Or for multi-line scripts, add this as the first line:
```bash
ssh bots bash <<'EOF'
eval "$(/opt/homebrew/bin/brew shellenv)"
# now brew, and brew-installed tools, are available
brew list
EOF
```

## Machine Fleet Reference

| Machine | SSH Alias | IP | Role | Notes |
|---------|-----------|------|------|-------|
| Mac Mini (Vault) | `ssh vault` | 192.168.86.27 | API key vault, LLM routing | Limited GUI automation (System Events hangs) |
| Mac Mini (Bots) | `ssh bots` | 192.168.86.50 | Bots platform runtime | Full GUI + SSH support |
| Mac Mini (Jarvis) | `ssh jarvis` | 192.168.86.51 | AI agent fleet node | No screencapture (no virtual display) |

## Running Commands

Single command:
```bash
ssh bots 'whoami && hostname && sw_vers'
```

Multi-line script:
```bash
ssh bots bash <<'EOF'
echo "Disk usage:"
df -h /
echo "Memory:"
vm_stat | head -5
echo "CPU:"
sysctl -n machdep.cpu.brand_string
EOF
```

## launchd Service Management

launchd is the macOS service manager (equivalent to systemd on Linux).

### List running services
```bash
ssh bots 'launchctl list | grep -v com.apple'
```

### Load/start a service
```bash
ssh bots 'launchctl load ~/Library/LaunchAgents/com.skynet.myservice.plist'
```

### Unload/stop a service
```bash
ssh bots 'launchctl unload ~/Library/LaunchAgents/com.skynet.myservice.plist'
```

### Check service status
```bash
ssh bots 'launchctl list | grep myservice'
```
Output: `PID  ExitCode  Label` — if PID is `-`, the service is not running.

### View service logs
```bash
ssh bots 'cat /tmp/myservice.stdout.log'
ssh bots 'cat /tmp/myservice.stderr.log'
```

### Create a launchd plist
```bash
ssh bots "cat > ~/Library/LaunchAgents/com.skynet.example.plist" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.skynet.example</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/python3</string>
        <string>/Users/james/myapp/server.py</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/example.stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/example.stderr.log</string>
    <key>WorkingDirectory</key>
    <string>/Users/james/myapp</string>
</dict>
</plist>
EOF
```

## Homebrew

Always prefix with brew shellenv:
```bash
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew install jq'
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew update && brew upgrade'
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew list'
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew search redis'

# Brew services (wraps launchd)
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew services list'
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew services start redis'
ssh bots 'eval "$(/opt/homebrew/bin/brew shellenv)" && brew services stop redis'
```

## System Defaults

macOS settings are stored in defaults (plist-based).

```bash
ssh bots 'defaults read com.apple.dock autohide'
ssh bots 'defaults write com.apple.dock autohide -bool true && killall Dock'
ssh bots 'defaults domains | tr "," "\n"'

# Useful defaults:
ssh bots 'defaults write NSGlobalDomain AppleShowAllExtensions -bool true'
ssh bots 'defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder'
```

Note: `defaults read` returns error if the key hasn't been explicitly set (uses system default). This is normal.

## System Diagnostics

```bash
ssh bots 'system_profiler SPHardwareDataType'
ssh bots 'sw_vers'
ssh bots 'uptime'
ssh bots 'df -h /'
ssh bots 'memory_pressure'
ssh bots 'ps aux | sort -k3 -rn | head -10'
ssh bots 'ifconfig | grep -A2 "inet "'
ssh bots 'lsof -iTCP -sTCP:LISTEN -P -n'
ssh bots 'pgrep -fl python'
```

## File Operations

```bash
scp ./config.json bots:~/myapp/config.json
scp bots:~/myapp/output.json ./output.json
scp -r ./project bots:~/project
rsync -avz ./project/ bots:~/project/
```

## Process Management

```bash
ssh bots 'pkill -f myserver'
ssh bots 'nohup python3 ~/myapp/server.py > /tmp/server.log 2>&1 &'
ssh bots 'tmux new-session -d -s myapp "python3 ~/myapp/server.py"'
```

## Docker (if OrbStack/Docker Desktop installed)

```bash
ssh bots 'docker ps'
ssh bots 'docker compose -f ~/myapp/docker-compose.yml up -d'
ssh bots 'docker logs --tail 50 mycontainer'
```
