remote-mac

agent
Security Audit
Fail
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 7 GitHub stars
Code Fail
  • rm -rf — Recursive force deletion command in tests/smoke.sh
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Control a remote Mac from Linux/VPS over SSH with two bash scripts: run commands, take screenshots, open/quit apps, scp files, and check channel status as JSON. Built as a remote-Mac layer for AI agents (OpenClaw skill). 从 Linux/VPS 通过 SSH 远程控制 Mac。

README.md

remote-mac

remote-mac lets you control a remote Mac from a Linux server or VPS over SSH — run commands, take screenshots, open/quit apps and transfer files — with two plain bash scripts (only ssh, scp and python3 needed locally). Built for AI agents (e.g. OpenClaw) and automation that need hands on a real macOS machine.

从 Linux / VPS 通过 SSH 远程控制 Mac:执行命令、截屏、控制 App、传文件,适合 AI agent 使用。

License: MIT
Shell
Platform


What it does

Script Purpose
mac_exec.sh Run commands, take screenshots, control apps, transfer files
channel_check.sh Check SSH (and an optional bridge URL) availability, output JSON

Works well paired with OpenClaw as a remote Mac control layer for AI agents.


Requirements

On the machine running the scripts (Linux / VPS):

  • bash, ssh, scp
  • python3 — used by channel_check.sh and for JSON escaping
  • curl — only if you set MAC_BRIDGE_URL

On the Mac:

  • Remote Login enabled (System Settings → General → Sharing → Remote Login).
  • SSH key login configured (your public key in ~/.ssh/authorized_keys on the Mac). The scripts use BatchMode=yes, so password login does not work.
  • For screenshots and app control: the Mac must be logged in to a desktop session with the screen unlocked.

Quick start

# 1. Clone
git clone https://github.com/ythx-101/remote-mac.git
cd remote-mac

# 2. Configure (copy example, fill in your SSH host/user)
cp remote-mac.conf.example ~/.remote-mac.conf
$EDITOR ~/.remote-mac.conf

# 3. Run
bash scripts/mac_exec.sh --run "sw_vers"

Not sure the connection works? Run bash scripts/channel_check.sh first (see Channel status).

Or configure via environment variables (no config file needed):

export MAC_SSH_HOST="your-mac-ip-or-hostname"
export MAC_SSH_USER="your-username"
export MAC_SSH_PORT="22"  # default: 22

Usage

bash scripts/mac_exec.sh --help    # list all commands

Run a remote command

bash scripts/mac_exec.sh --run "ls -la ~"
bash scripts/mac_exec.sh --run "brew list"
bash scripts/mac_exec.sh --run "some-long-script.sh" 120   # custom timeout (seconds)

Screenshot

bash scripts/mac_exec.sh --screenshot                      # saves to /tmp/mac_screenshot.png
bash scripts/mac_exec.sh --screenshot /tmp/myscreen.png

Requires an active desktop session. Won't work if the screen is locked.

App control

bash scripts/mac_exec.sh --app "open:Safari"
bash scripts/mac_exec.sh --app "activate:Finder"
bash scripts/mac_exec.sh --app "quit:Safari"
bash scripts/mac_exec.sh --app "kill:AppName"    # force kill via pkill -x

File transfer

# Mac → local
bash scripts/mac_exec.sh --file "get:~/Desktop/report.pdf:/tmp/report.pdf"

# local → Mac
bash scripts/mac_exec.sh --file "put:/tmp/script.sh:~/Desktop/"

The argument format is get|put:src:dst. Remote paths are passed to scp as-is (no extra quoting), so ~/... works. For paths containing spaces, escape them with a backslash when downloading (e.g. get:~/My\ Files/a\ b.txt:/tmp/); how scp treats spaces in upload destinations depends on the scp protocol in use (legacy vs SFTP), so prefer space-free destination paths for put.

Channel status (JSON)

bash scripts/channel_check.sh
{
  "timestamp": "2026-03-03T07:42:00Z",
  "ssh": { "available": true, "latency_ms": 42, "target": "[email protected]:22" },
  "ag_bridge": { "available": false, "latency_ms": 0, "status": "" },
  "preferred": "ssh"
}

Configuration

All config via ~/.remote-mac.conf or environment variables:

Variable Default Description
MAC_SSH_HOST mac.local SSH hostname or IP (Tailscale, LAN, etc.)
MAC_SSH_USER current user SSH username on the Mac
MAC_SSH_PORT 22 SSH port
MAC_BRIDGE_URL (empty) Optional: Antigravity bridge health URL
REMOTE_MAC_CONF ~/.remote-mac.conf Custom config file path
REMOTE_MAC_CONTROL_DIR auto Override SSH ControlPath directory

See remote-mac.conf.example for a commented template.


When to use (and when not)

Option Good fit Notes
Plain ssh You only need a shell on the Mac remote-mac builds on ssh/scp and adds one command set for screenshots, app control and file transfer, plus JSON channel status
VNC / macOS Screen Sharing You need to watch the live screen or use the mouse and keyboard remote-mac does not stream the screen or simulate mouse input; --screenshot takes a single still image
OpenClaw Node You already run an OpenClaw Node on the Mac Can serve as a second channel next to SSH (see Dual-channel)
remote-mac A Linux server, VPS or AI agent needs to run commands, grab screenshots, open/quit apps or move files on a Mac Two bash scripts, no extra software on the Mac beyond Remote Login

FAQ

How do I control a Mac from Linux / a VPS? / 怎么从 Linux 远程控制 Mac?
Enable Remote Login and SSH key login on the Mac, set MAC_SSH_HOST / MAC_SSH_USER, then run bash scripts/mac_exec.sh --run "sw_vers". See Requirements and Quick start.

Why do I get "Mac SSH unreachable" or "permission denied"?
Typical causes: Remote Login is off, your SSH key is not authorized on the Mac, or the host / port is wrong. Run bash scripts/channel_check.sh — if ssh.available is false, fix the SSH connection first (try the same ssh -p <port> user@host by hand).

Why is my screenshot failing or blank?
screencapture needs an active desktop session, so it will not work with a locked screen or without a graphical session. It may also be worth checking the Mac's screen-recording permissions for the SSH session.

Can I use it over Tailscale?
Yes — set MAC_SSH_HOST to the Mac's Tailscale IP or name.

Is it safe?
See Security: host keys use accept-new, app names are shell-escaped and passed via argv, and no secrets are hard-coded.

Does it work with password login?
No. The scripts run ssh with BatchMode=yes, which disables password prompts. Use SSH keys.

How do AI agents use it?
As a skill or shell tool: the agent calls scripts/mac_exec.sh with --run, --screenshot, --app or --file, and can parse the JSON from scripts/channel_check.sh to see whether the Mac is reachable. See SKILL.md.


Security

  • No hardcoded secrets — all config via env vars or ~/.remote-mac.conf (never committed)
  • StrictHostKeyChecking=accept-new — non-interactive but won't silently accept changed host keys
  • Shell injection protection — app names are single-quoted via shell_escape(); AppleScript uses argv passing
  • JSON injection protection — all dynamic values in channel_check.sh output are encoded via python3 json.dumps
  • Portable ControlPath — uses XDG_RUNTIME_DIR → TMPDIR → ~/.cache/remote-mac (works on both Linux and macOS)

Dual-channel architecture

When used with OpenClaw, you can keep two independent control channels to the same Mac — SSH and OpenClaw Node — as mutual fallbacks:

                    ┌─────────────┐
                    │    VPS      │
                    └──────┬──────┘
                           │
              ┌────────────┴────────────┐
              │                         │
         SSH (mac_exec.sh)        OpenClaw Node
         any shell command        persistent WebSocket
                                  via OpenClaw
              │                         │
              └────────────┬────────────┘
                           │
                    ┌──────▼──────┐
                    │    Mac      │
                    └─────────────┘
Channel How Best for
SSH mac_exec.sh via ssh Any shell command, file transfer, screenshots
OpenClaw Node nodes.run tool built into OpenClaw AI agent workflows when a Node is installed on the Mac

channel_check.sh checks the SSH channel and, if MAC_BRIDGE_URL is set, that optional bridge health URL (reported as ag_bridge). It does not check the OpenClaw Node channel — call that yourself from OpenClaw with nodes.run. The preferred field is ssh when SSH is reachable and none otherwise:

bash scripts/channel_check.sh
# → { "ssh": { "available": true }, "ag_bridge": { ... }, "preferred": "ssh" }

If preferred is none, try the Node channel from OpenClaw.


Tips

Keep Mac awake:

bash scripts/mac_exec.sh --run "caffeinate -d &"

SSH multiplexing is enabled by default (ControlMaster=auto, ControlPersist=10m). First connection is slow; subsequent calls reuse the connection.

Tailscale: set MAC_SSH_HOST to your Mac's Tailscale IP to reach it outside your local network.


License

MIT

Reviews (0)

No results found