An open API service indexing awesome lists of open source software.

https://github.com/buildingopen/moto

๐Ÿ›ต One command to run 20+ Claude Code / Codex / opencode agents on a remote Linux server from your Mac. tmux sessions, authenticated Chrome, residential proxy, reboot-proof. Hetzner-tested.
https://github.com/buildingopen/moto

agent-farm agents ai-agents chrome-automation claude-code codex dev-server devops docker hetzner homelab iterm opencode remote-development residential-proxy reverse-tunnel self-hosted sshfs tmux vps

Last synced: 2 days ago
JSON representation

๐Ÿ›ต One command to run 20+ Claude Code / Codex / opencode agents on a remote Linux server from your Mac. tmux sessions, authenticated Chrome, residential proxy, reboot-proof. Hetzner-tested.

Awesome Lists containing this project

README

          

# moto ๐Ÿ›ต

> Your Mac is the remote control. A Linux box is the garage. **One command brings every agent window back.**

> **Status: v0.1 โ€” experimental.** This is extracted from a setup that runs 20+ concurrent Claude sessions on a Hetzner AX41 24/7. The code and docs are solid, but it has not yet been end-to-end installed on a clean box from this repo. Expect small paper cuts. Issues + PRs very welcome.

`moto` is an opinionated, reproducible setup for running an army of AI coding agents (Claude Code, Codex, opencode) on a big Linux box, controlled from your Mac, with:

- ๐ŸชŸ **One iTerm window, many tabs** โ€” every agent session as a tab, restored with one command after a reboot, laptop close, or Mac sleep
- ๐Ÿ” **Full-access Mac tunnel** โ€” the server can read/write your Mac's `~/.claude` live over SSHFS (reverse tunnel), so your local config is the source of truth
- ๐ŸŽ **`moto`** โ€” one command from the Mac: `moto up` reopens everything, `moto new` spawns a new session, `moto kill` cleans up
- โ™ป๏ธ **Reboot / OOM survival** โ€” `tmux` is OOM-immune, containers auto-restart, SSHFS re-mounts every 30s, stale processes are reaped nightly
- ๐ŸŒ **Residential-IP egress** โ€” drop-in proxy container, all agent traffic rewritten through your provider of choice
- ๐Ÿงฐ **Code-execution sandboxes** โ€” pre-wired containers for rendering, Node dev servers, and a logged-in headless Chrome with CDP exposed on the Docker bridge
- ๐Ÿงน **Auto-cleanup** โ€” orphan `next dev` / `tsx watch` / Chrome / old `node_modules` are garbage-collected so the box doesn't rot

Built by [@federicodeponte](https://github.com/federicodeponte) to run 20+ concurrent Claude sessions on a Hetzner AX41 without ever babysitting the box.

---

## Who is this for?

- **You run Claude Code (or Codex, or opencode) all day** and your laptop's fan is tired.
- **You already have a VPS / dedicated box** (Hetzner, OVH, Latitude, Fly machine, your own homelab) and want to stop SSH-ing in and re-creating tmux windows by hand.
- **You want agents that survive**: Mac sleep, laptop closed, server OOM, server reboot โ€” `moto up` restores the same 20 tabs in one iTerm window every time.
- **You need agents that look logged-in**: shared Chrome with CDP on the Docker bridge, so agents attach to an already-authenticated browser instead of logging into Google / GitHub / LinkedIn per session.
- **You don't want to maintain scattered dotfiles**: the whole setup is one repo, one `install.sh mac`, one `install.sh server-remote`.

Not for you if: you only run agents locally, you're on Windows, or you're looking for a hosted SaaS.

---

## 60-second quickstart

```bash
git clone https://github.com/buildingopen/moto.git
cd moto
cp .env.example .env
$EDITOR .env # fill in AX41_HOST, MAC_USER, etc.

./install.sh mac # on your Mac
./install.sh server # via SSH on your Linux box
```

Then from the Mac:

```bash
moto up # reopen every tmux session in ONE iTerm window
moto new myproj/feature # spawn a fresh Claude session
moto ls # list active sessions
moto kill myproj/feature # kill one
```

---

## Architecture

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ YOUR MAC โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚
โ”‚ iTerm (one window, N tabs) ~/.claude (source of truth) โ”‚
โ”‚ โ”‚ โ–ฒ โ”‚
โ”‚ โ”‚ tmux -CC (ControlCC) โ”‚ sshfs reverse mount โ”‚
โ”‚ โ”‚ over SSH ControlMaster โ”‚ โ”‚
โ”‚ โ–ผ โ”‚ โ”‚
โ”‚ moto CLI โ”€โ”€โ”€โ”€ ssh ax41 โ”€โ”€โ”€โ”€โ–บ ax41:22 โ”‚ โ—„โ”€โ”€ ssh -R 2222 โ”€โ” โ”‚
โ”‚ โ”‚ Mac sshd โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ YOUR LINUX BOX โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ–ผ โ”‚ โ”‚
โ”‚ /mnt/mac-claude (FUSE view of Mac ~/.claude) โ”‚ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ tmux-server.service (MemoryLow=16G, OOMScoreAdjust=-900) โ”‚ โ”‚
โ”‚ โ”œโ”€ tmux session myproj/feature โ†’ claude โ”‚ โ”‚
โ”‚ โ”œโ”€ tmux session other/main โ†’ codex โ”‚ โ”‚
โ”‚ โ””โ”€ ... (each a tab in your Mac iTerm) โ”‚ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ authenticated-chrome.service โ†’ Xvfb :98 + Chrome :9222 โ—„โ”€โ”€โ”ค โ”‚
โ”‚ chrome-bridge-keeper.service โ†’ CDP keepalive โ”‚ โ”‚
โ”‚ cdp-docker-proxy.service โ†’ 172.17.0.1:9222 for agents โ”‚ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ mac-mount-check.timer โ†’ every 30s: remount if stale โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ moto-cleanup.timer โ†’ every 10min: reap orphans โ”‚
โ”‚ node-modules-gc.timer โ†’ nightly: rm old node_modules โ”‚
โ”‚ โ”‚
โ”‚ docker compose: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ proxy (residential IP egress, all-in) โ”‚ โ”‚
โ”‚ โ”‚ runtime-api (node execution sandbox) โ”‚ โ”‚
โ”‚ โ”‚ dev-sandbox (sshd + node, port 2223) โ”‚ โ”‚
โ”‚ โ”‚ cloudflared (outbound public tunnels) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

See [`docs/architecture.md`](docs/architecture.md) for the full diagram.

---

## What `moto` does differently

| Problem | Typical solution | `moto` |
|---|---|---|
| Laptop sleeps, agents die | Run on laptop, restart | Agents live in `tmux` on server, Mac just attaches |
| Server OOM kills your session | `oom_score_adj` guesses | `tmux-server.service` reserves 16 GB + `OOMScoreAdjust=-900`; `earlyoom` picks someone else |
| Claude config drifts between machines | `rsync` / Dropbox / git | SSHFS reverse mount โ€” the Mac *is* the filesystem |
| Re-opening 20 windows is tedious | Shell for-loop | `moto up` โ€” kills iTerm, creates one window, opens every session as a tab, retries stubborn ones 5ร— |
| Agents get rate-limited by IP | Random VPN | Dedicated proxy container; only outbound agent traffic is rewritten |
| Agents need to use logged-in services | New login per session | Shared `authenticated-chrome` with CDP โ€” agents attach, don't log in |
| Reboot nukes everything | `docker-compose up` by hand | All services are systemd-managed with `Restart=on-failure`, containers are `--restart unless-stopped` |

---

## Requirements

**Mac**: macOS 13+, iTerm2, Homebrew, `ssh` (ControlMaster), OpenSSH server (for reverse tunnel).

**Server**: Debian 12 / Ubuntu 22.04+, root or sudo, โ‰ฅ16 GB RAM recommended, public IPv4. Tested on Hetzner AX41.

**Agent CLIs (install separately on the server after `./install.sh server`)** โ€” `moto` wraps these, it doesn't bundle them:

```bash
# Claude Code
npm i -g @anthropic-ai/claude-code && claude /login

# Codex (OpenAI)
npm i -g @openai/codex

# opencode (optional)
npm i -g opencode
```

`moto new foo/bar` needs `claude` in `PATH`. `moto newx` needs `codex`. `moto newo` needs `opencode`. Only install what you'll actually use.

---

## Commands

```
moto up # restore all sessions in one iTerm window (idempotent)
moto new NAME # create session NAME (format: project/task)
moto attach NAME # attach existing session as a new iTerm tab
moto ls # list server sessions
moto kill NAME # kill a session
moto status # server health: tmux count, mount status, chrome, containers
moto img PATH # scp an image to the server and print its remote path
moto logs # tail moto-cleanup + mac-mount-check logs
moto down # detach all clients (leaves sessions running)
moto doctor # diagnose the setup
```

Legacy aliases (`ax`, `axo`, `axn`, `axk`, `axl`, `axwin`, `aximg`, `axd`, `axq`) are preserved โ€” see [`mac/shell/20-sessions.zsh`](mac/shell/20-sessions.zsh).

---

## Documentation

- [Architecture](docs/architecture.md) โ€” how the pieces fit
- [Bootstrap a fresh server](docs/bootstrap.md) โ€” zero-to-moto on a new box
- [Browser login](docs/browser-login.md) โ€” how to log `authenticated-chrome` into Google/GitHub/LinkedIn
- [Residential proxy setup](docs/proxy.md) โ€” Bright Data, Smartproxy, or generic SOCKS5
- [Reboot recovery](docs/reboot-recovery.md) โ€” what happens when the box comes back up
- [Claude config sync (why SSHFS)](docs/claude-sync.md) โ€” why not rsync/git

---

## Known gaps (v0.1)

- **Cold install validated in a throwaway container** (`debian:12` on the author's Hetzner AX41 โ€” 50/50 checks pass, see [`server/test/`](server/test/)). Not yet validated on a bare-metal box from zero, but every piece the installer touches on a new box โ€” apt, Chrome, Docker CE, systemd unit syntax, script layout โ€” is exercised by that test.
- **Residential proxy sidecar end-to-end tested** with `server/test/proxy-smoke.sh`: URL parser handles http/https/socks5 with and without auth, direct egress works when `PROXY_URL` is empty, and chained traffic is proved to flow through the upstream (parent tinyproxy's own logs show the forwarded request). Tinyproxy under the hood โ€” we tried 3proxy first, parent directive was silently ignored across three versions.
- **Agent CLIs not bundled**: `claude` / `codex` / `opencode` are npm installs โ€” see [Requirements](#requirements).
- **`chrome-bridge-keeper`** ships as a simple bash CDP-ping loop. The author's real setup uses a ~300-line Python variant that also auto-patches the Claude Code browser extension; too specific to include here. Add `# MOTO_KEEP_LOCAL` to your own script and `server/install.sh` will preserve it.
- **x86_64 only** for now โ€” Chrome, Docker images, and the Hetzner target are all amd64. ARM support is untested.
- **IPv6**: reverse tunnel and SSHFS work over IPv4; IPv6 is not explicitly tested.
- **No automated e2e test** for the reverse-tunnel โ†’ SSHFS โ†’ `moto up` path. `moto doctor` covers static health.

If you hit something, please open an issue โ€” most gaps are 10-minute fixes once surfaced.

## Contributing

PRs welcome. Before submitting:

```bash
shellcheck -S warning server/bin/* mac/bin/moto server/test/*.sh server/docker/proxy/entrypoint.sh
(cd server/docker && docker compose config) >/dev/null
HOST=your.host ./server/test/run-container-test.sh # full isolated install test
HOST=your.host ./server/test/proxy-smoke.sh # proxy sidecar end-to-end
```

See [`server/test/README.md`](server/test/README.md) for what the tests cover.

---

## Related projects

`moto` pairs well with other [BuildingOpen](https://github.com/buildingopen) tools for Claude Code operators:

| Project | What it does |
|---------|--------------|
| **[claude-setup](https://github.com/buildingopen/claude-setup)** | The Claude Code config (`~/.claude`) that moto syncs to the server โ€” 60+ skills, 12 safety hooks, CLAUDE.md templates |
| **[openqueen](https://github.com/buildingopen/openqueen)** | Autonomous agent orchestrator โ€” spawn Claude / Codex workers on moto from WhatsApp or Telegram |
| **[bouncer](https://github.com/buildingopen/bouncer)** | Gemini-powered quality gate that audits Claude's output before it can stop |
| **[session-recall](https://github.com/buildingopen/session-recall)** | Recover Claude Code context after automatic compaction |
| **[browse](https://github.com/buildingopen/browse)** | Browser automation CLI โ€” the agents running inside moto use this against the shared Chrome CDP |
| **[openbrowser](https://github.com/buildingopen/openbrowser)** | MCP server that gives agents authenticated browser access |
| **[blast-radius](https://github.com/buildingopen/blast-radius)** | Find every file affected by a change โ€” one bash script |

## License

MIT ยฉ Federico de Ponte. See [LICENSE](LICENSE).