⚠️ This software is provided "AS IS" without warranty. You are solely responsible for authorization, compliance, and security. See SECURITY.md for full disclaimer.
中文文档 | English
One command to fully control any Mac remotely. Zero-config networking, end-to-end encrypted.
Terminal commands · Browser automation · Mouse & keyboard · Screenshots · VNC remote desktop — all supported.
Before running any of the scripts in this repo, the following must be true on every Mac you plan to use (both masters and workers). This whole section is a one-time, ~5 minute setup per machine. After this, everything else is automated.
| Requirement | |
|---|---|
| Hardware | Any Mac (Intel or Apple Silicon) made in the last ~10 years |
| OS | macOS 12 Monterey or newer (older versions may work but aren't tested) |
| Disk | ~5 GB free (mostly for Xcode Command Line Tools + Homebrew + Playwright) |
| Network | Any internet connection — even mobile hotspots, behind NAT, or different WiFi networks. Tailscale handles NAT traversal automatically. |
| Account | A free Tailscale account (sign up at https://tailscale.com — uses your Google/Apple/Microsoft/GitHub login). All Macs in the fleet must share the same Tailscale account. |
| Admin password | You'll need your macOS login password a few times during setup (for sudo, enabling Remote Login, granting permissions). |
If you've never used Terminal: press ⌘ + Space → type Terminal → press Enter.
A black/white window opens — this is where you'll paste commands. Right-click in the window to paste, then press Enter to run.
Brand-new Mac note: The very first time you run a developer command (like
git), macOS will pop up a dialog asking to install Xcode Command Line Tools. You can click Install now to get it out of the way, but you don't have to —worker-setup.shwill trigger it for you in the background later.
Tailscale is the secure network layer that connects all your Macs. Install on each machine (master + every worker). Pick one of the two methods below — the scripts detect both automatically.
Best for long-running fleet machines. Native auto-start, auto-update, and system permission dialogs.
-
Open the App Store → search Tailscale → Install (Direct link: https://apps.apple.com/app/tailscale/id1475387142)
-
Open Tailscale (it appears as a small icon in the menu bar, top-right of your screen)
-
Click the menu bar icon → Log in → choose your login provider (Google / Apple / Microsoft / GitHub / email)
-
Critical: every Mac must log in to the same Tailscale account, otherwise they can't see each other.
-
Verify it's connected: the menu bar icon should not have a slash through it. To double-check, open Terminal and run:
/Applications/Tailscale.app/Contents/MacOS/Tailscale ip -4
You should see an IP starting with
100.x.x.x(Tailscale assigns these).
Best if you already live in the terminal or don't want a menu-bar app. Requires a bit more setup (manually start the daemon, run tailscale up to log in via browser).
-
Install Homebrew first (if not present):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -
Install Tailscale via Homebrew. Two flavors:
- CLI daemon only (headless machines / servers):
brew install tailscale sudo brew services start tailscale
- Cask with menu bar app (identical to App Store version):
brew install --cask tailscale-app
- CLI daemon only (headless machines / servers):
-
Log in:
tailscale up
This prints an authentication URL — open it in your browser and log in. Use the same Tailscale account on every Mac.
-
Verify:
tailscale ip -4
You should see a
100.x.x.xIP.
App Store vs. Homebrew — which should I pick? The App Store GUI version auto-starts on boot, auto-updates, and handles system permission dialogs natively — ideal for unattended fleet workers. The Homebrew CLI version is lighter and scriptable, but you'll manage the daemon via
brew servicesand re-runtailscale upafter re-auth events. Both work equally well with this project — the scripts detect either install location (/Applications/Tailscale.app/...or/opt/homebrew/bin/tailscale). Pick whichever fits your workflow.
Step C — Sign in once at https://login.tailscale.com (recommended)
Visit https://login.tailscale.com/admin/machines after you've logged in on a couple of Macs. You should see them listed there. This is where you can later remove machines, share with others, or check status. Bookmark it.
The scripts handle all of this for you — listed here so you know what's coming:
- ❌ Homebrew — auto-installed by
worker-setup.sh. Already installed butbrewsays "command not found"? The script auto-fixes the PATH. - ❌ Node.js / npm — auto-installed via Homebrew.
- ❌ Playwright + browser — auto-installed (uses
chromium-headless-shell, ~70 MB). - ❌ cliclick (mouse/keyboard automation) — auto-installed via Homebrew.
- ❌ Xcode Command Line Tools —
worker-setup.shtriggers a non-interactive install in the background at Step 0, so it overlaps with everything else and saves ~10–20 minutes of perceived wait time. If a system dialog appears anyway, just click Install. - ❌ SSH keys — auto-generated and exchanged.
git clone https://github.com/willau95/mac-fleet-control.git ~/mac-fleet-control \
&& cd ~/mac-fleet-control \
&& bash master-setup.shfleet-ssh masterYou'll get a complete, paste-ready block like this:
git clone https://github.com/willau95/mac-fleet-control.git ~/mac-fleet-control \
&& cd ~/mac-fleet-control \
&& bash worker-setup.sh --master seacapital@100.107.142.39Paste the command from Step 2 into the worker's terminal. It will:
- ✅ Auto-install Homebrew (and fix PATH if already installed)
- ✅ Auto-install Node.js + Playwright (chromium-headless-shell, ~70 MB)
- ✅ Trigger Xcode Command Line Tools install in the background (parallel, saves ~15 min)
- ✅ Set up bidirectional SSH key auth — asks master password once
- ✅ Auto-register to the master's fleet
- ✅ Verify everything works
That's it. Run fleet-ssh list on the master and your new worker is online.
Optional 4th step — make workers always-on:
bash worker-harden.sh(disables sleep, enables auto-login, installs self-healing watchdog). See Hardening below.
Master A ──┐ ┌── Worker 1
Master B ──┤── Tailscale (WireGuard E2EE) ──┼── Worker 2
Master C ──┘ Works on any network └── Worker N
- ✅ Multi-master support
- ✅ One machine can be both Master and Worker
- ✅ WireGuard end-to-end encryption
- ✅ No public ports exposed
- ✅ Works across WiFi changes / locations / mobile hotspots
- ✅ Self-healing watchdog (auto-fixes every 5 min)
You can register a worker to several masters at once:
bash worker-setup.sh --master john@100.x.x.x --master jane@100.y.y.y| # | Step | Notes |
|---|---|---|
| 0 | Pre-trigger Xcode CLT install in background | Saves ~10–20 min vs. brew triggering it serially |
| 1 | Verify Tailscale is connected | Aborts with instructions if not |
| 2 | Enable macOS Remote Login (SSH) | Via systemsetup |
| 3 | Generate SSH key + config | Sets IdentitiesOnly to avoid auth-flood errors |
| 4 | Install Homebrew + auto-fix PATH + install cliclick + Node + Playwright | Auto-fixes the common "brew installed but not in PATH" trap |
| 5 | Create fleet-tools (screenshot-url.js, browser-action.js, capture-screen.sh) |
In ~/fleet-tools/ |
| 6 | Bidirectional SSH key exchange + register to master(s) | Asks master password once |
| 7 | Print the 3 manual permissions you still need to grant (see below) | One-time, survives reboots |
On the Worker Mac, open System Settings:
System Settings → General → Sharing → Screen Sharing → ON
System Settings → Privacy & Security → Screen & System Audio Recording → Click + → add these 2 entries:
/usr/libexec/sshd-keygen-wrapper— press Cmd+Shift+G to pasteTailscale.app— navigate to/Applications/, select the app (do not go inside it)(If you installed Tailscale via Homebrew instead of the App Store, add
/opt/homebrew/opt/tailscale/bin/tailscaledinstead ofTailscale.app.)
System Settings → Privacy & Security → Accessibility → Click + → add the same 2 entries as above:
/usr/libexec/sshd-keygen-wrapperTailscale.app(ortailscaledif Homebrew install)
These 3 permissions survive reboots — truly one-time.
bash worker-harden.shWill ask for macOS password once (for auto-login setup), everything else is automatic.
After hardening:
| Setting | Effect |
|---|---|
| Disable sleep/hibernation | Never sleeps |
| Wake on LAN | Can be woken via network |
| Auto-restart on power failure | Powers on when electricity returns |
| Tailscale auto-start | Reconnects after reboot |
| Auto-login | Goes straight to desktop after reboot |
| Disable auto-updates | Won't restart unexpectedly |
| Disable screen lock | No password prompt on wake |
| Self-healing watchdog | Checks & fixes every 5 minutes |
# List all machines
fleet-ssh list
# Print the paste-able worker setup command (run on a master)
fleet-ssh master
# Ping all
fleet-ssh ping
# Run command by number
fleet-ssh 1 "hostname && uptime"
# Run by name (partial match)
fleet-ssh my-imac "hostname"
# Run on ALL machines
fleet-ssh all "uptime"
# Interactive SSH session
fleet-ssh shell 1
# Add/remove machines manually
fleet-ssh add "name" "user" "ip"
fleet-ssh remove "name"If a machine is already set up but not in your fleet (e.g. adding a master to another master's fleet), do these 2 steps:
Step 1: Register it
fleet-ssh add "Office-iMac" "john" "100.x.x.x"Step 2: Set up passwordless SSH (one-time, will ask for password once)
ssh-copy-id john@100.x.x.xDone. fleet-ssh list should now show it as online.
Note:
worker-setup.shdoes both steps automatically. Manual adding is only needed when you skip the setup script (e.g. adding an existing master as a worker to another master).
fleet-ssh 1 "cliclick m:500,500" # Move mouse
fleet-ssh 1 "cliclick c:500,500" # Click
fleet-ssh 1 "cliclick dc:500,500" # Double click
fleet-ssh 1 "cliclick rc:500,500" # Right click
fleet-ssh 1 "cliclick t:'Hello World'" # Type text
fleet-ssh 1 "cliclick kp:command-a" # Shortcut Cmd+A
fleet-ssh 1 "cliclick kp:command-c" # Cmd+C
fleet-ssh 1 "cliclick kp:command-v" # Cmd+V
fleet-ssh 1 "cliclick kp:return" # Enter# Screen capture
fleet-ssh 1 "bash ~/fleet-tools/capture-screen.sh /tmp/screen.png"
# Web page screenshot
fleet-ssh 1 "node ~/fleet-tools/screenshot-url.js https://google.com /tmp/google.png"
# Pull screenshot to local machine
scp user@ip:/tmp/screen.png ~/Desktop/fleet-ssh 1 "node ~/fleet-tools/browser-action.js '{
\"url\": \"https://google.com\",
\"actions\": [
{\"type\": \"click\", \"selector\": \"textarea[name=q]\"},
{\"type\": \"type\", \"selector\": \"textarea[name=q]\", \"text\": \"hello\"},
{\"type\": \"screenshot\", \"path\": \"/tmp/result.png\"}
]
}'"open vnc://user@<worker-tailscale-ip>Fully supported. Run both:
bash master-setup.sh # As master
bash worker-setup.sh --master other@100.x.x.x # As worker
bash worker-harden.sh # HardenOn any master that's out of date:
cd ~/mac-fleet-control && git pull origin main && sudo cp fleet-ssh /usr/local/bin/fleet-ssh && fleet-ssh masterThe trailing fleet-ssh master verifies the new version took effect — if it prints the paste-ready worker-install block, you're on the latest fleet-ssh. If the output looks like the old "Usage: fleet-ssh ..." error, see the shadow-PATH trap callout further down.
When the repo has updates, follow these steps based on each machine's role:
fleet-ssh all "cd ~/mac-fleet-control && git fetch origin && git reset --hard origin/main"This updates every worker in your fleet with one command.
On every machine that acts as a master (including machines that are both master and worker), run locally:
cd ~/mac-fleet-control && git fetch origin && git reset --hard origin/main
sudo cp fleet-ssh /usr/local/bin/fleet-sshThe sudo cp step is required because fleet-ssh is installed to /usr/local/bin/ — git pull alone won't update it.
⚠️ Common trap: "I updated butfleet-sshstill acts like the old version."Some setups have multiple
fleet-sshfiles in PATH (~/bin/,~/.local/bin/,/usr/local/bin/). The shell uses whichever appears first in PATH, which may shadow the one you just updated. Check with:type -a fleet-sshIf you see more than one path, make the others symlinks to the canonical copy so future updates flow through automatically:
rm ~/bin/fleet-ssh ~/.local/bin/fleet-ssh 2>/dev/null ln -s /usr/local/bin/fleet-ssh ~/bin/fleet-ssh ln -s /usr/local/bin/fleet-ssh ~/.local/bin/fleet-sshThen your update workflow becomes one command:
cd ~/mac-fleet-control && git pull && sudo cp fleet-ssh /usr/local/bin/fleet-ssh— every PATH entry sees the new version.
| Role | Command | Where to run |
|---|---|---|
| All workers | fleet-ssh all "cd ~/mac-fleet-control && git fetch origin && git reset --hard origin/main" |
Any master |
| Each master | cd ~/mac-fleet-control && git fetch origin && git reset --hard origin/main && sudo cp fleet-ssh /usr/local/bin/fleet-ssh |
Locally on that master |
Safe to run again — all scripts are idempotent:
bash worker-setup.sh --master user@ip
bash worker-harden.sh
bash master-setup.sh# Master: list all machines
fleet-ssh list
# Master: batch check
fleet-ssh all "hostname && tailscale ip -4 && uptime"
# Worker: check Tailscale
tailscale status
# Worker: check SSH
sudo systemsetup -getremotelogin
# Worker: check sleep
pmset -g | grep sleep
# Worker: check watchdog
launchctl list | grep fleet.watchdog
tail -20 ~/fleet-tools/watchdog.log| Problem | Cause | Solution |
|---|---|---|
fleet-ssh list shows timeout |
SSH key not set up | ssh-copy-id user@worker-ip on master |
fleet-ssh list shows offline |
Tailscale disconnected / machine asleep | Open Tailscale app; run worker-harden.sh |
| Screenshot fails | Missing Screen Recording permission | Add sshd-keygen-wrapper to Screen Recording |
| Mouse doesn't move | Missing Accessibility permission | Add sshd-keygen-wrapper to Accessibility |
| Too many auth failures | SSH trying too many keys | Script auto-fixes; or add IdentitiesOnly yes to ~/.ssh/config |
| Tailscale not auto-starting | Not in Login Items | Run worker-harden.sh |
| Stuck at login screen after reboot | Auto-login not set | Run worker-harden.sh |
command not found (node/cliclick) |
PATH not loaded in SSH | fleet-ssh handles this; or export PATH=/opt/homebrew/bin:$PATH |
| Playwright fails | Not installed properly | cd ~/fleet-tools && npm install playwright && npx playwright install chromium |
| VNC won't connect | Screen Sharing off | System Settings → Sharing → Screen Sharing → ON |
| Worker on different network unreachable | Extreme firewall blocking Tailscale | Try mobile hotspot; watchdog retries every 5 min |
This repo includes an AI agent skill in skills/mac-control/ for automated cross-machine operations. The skill provides a 4-level decision tree:
| Level | Tool | When to use | Token cost |
|---|---|---|---|
| 1 | fleet-exec.sh |
CLI commands (95% of tasks) | Zero |
| 2 | fleet-browse.sh |
Headless browser automation | Zero |
| 3 | fleet-look.sh |
Screenshot + vision analysis | Medium |
| 4 | fleet-act.sh |
Mouse/keyboard simulation (last resort) | Low |
Rule: Always use the lowest level that can solve the task.
By default, all machines use the same Tailscale account. If a worker must use a different account, use Tailscale Node Sharing:
- Worker logs in with their own Tailscale account
- Master opens https://login.tailscale.com/admin/machines
- Find the worker device → "..." → Share
- Enter the master's Tailscale account email
- Master accepts the share invitation
- Networks are now connected — run
worker-setup.shas normal
Note: If the other party logs out or revokes sharing, you lose access. For 100% reliable control, use a single shared account.
Docs: https://tailscale.com/kb/1084/sharing
| File | Purpose |
|---|---|
master-setup.sh |
Master setup (env check + fleet-ssh + registry) |
worker-setup.sh |
Worker setup (tools + SSH keys + auto-register) |
worker-harden.sh |
Worker hardening (always-on + self-healing watchdog) |
fleet-ssh |
Fleet control tool (list/ping/shell/run) |
skills/mac-control/ |
AI agent skill for cross-machine ops |
SOP.md |
Detailed operations manual (Chinese) |
SECURITY.md |
Security disclaimer & legal notice |
- All traffic encrypted via Tailscale WireGuard — no public ports
- SSH uses ED25519 keys — no password brute-force risk
- Tailscale ACLs can restrict which machines can communicate
worker-harden.shdisables screen lock and auto-updates — suitable for managed environments only
See SECURITY.md for full disclaimer.
MIT — See LICENSE