No description
  • TypeScript 84.6%
  • CSS 7.5%
  • Shell 5.8%
  • Dockerfile 1.4%
  • HTML 0.4%
  • Other 0.3%
Find a file
Luca Zanussi 18971fae10
All checks were successful
ci / nexus (push) Successful in 10m40s
ci / images (push) Successful in 6m26s
Fix CI images build step
2026-05-11 22:04:42 +02:00
.forgejo/workflows Fix CI images build step 2026-05-11 22:04:42 +02:00
nexus Add vitest test suite 2026-05-11 21:07:43 +02:00
worker Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00
.gitignore Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00
AGENTS.md Add vitest test suite 2026-05-11 21:07:43 +02:00
CLAUDE.md Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00
DEPLOYMENT.md Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00
docker-compose.yml Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00
README.md Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00

Agent Nexus

Self-hosted webapp for spawning and managing dockerized Claude Code agents.

Connects to one or more git providers (Forgejo today, GitHub planned), lists your repositories, and spawns Nexus Worker containers — each running Claude Code under tmux with cloud Remote Control so you can attach from the Claude mobile/web app or docker exec from the host.

Quick start

# 1. Build images
docker build -t nexus-worker:latest ./worker
docker build -t agent-nexus:latest ./nexus

# 2. One-time: bootstrap the shared Claude session.
#    Remote Control refuses inference-only setup-tokens, so workers share the
#    full-scope OAuth state produced by `claude auth login`. The bootstrap
#    script runs it interactively and saves the result to a docker volume.
docker run --rm -it -v claude-session:/session nexus-worker:latest \
  /bootstrap-claude-auth.sh

# 3. Start Nexus + the docker-socket proxy
docker compose up -d

Then open http://localhost:3001/ and:

  1. Set a master passphrase (first run only — it encrypts provider tokens at rest)
  2. Unlock with that passphrase
  3. If the bootstrap step ran, the UI advances past the Bootstrap Claude session screen automatically. If not, you'll see instructions to run step 2 above.
  4. Add a Forgejo connection (base URL + PAT with read:repository read:user read:organization)
  5. Pick a repo, name a session, hit Launch
  6. The session shows up at claude.ai/code and in the Claude Android/iOS app a few seconds later

Architecture

 ┌────────────────────────────────┐
 │ browser / Claude mobile app    │
 └──────────────┬─────────────────┘
                │ HTTPS (nginx, optional)
 ┌──────────────▼─────────────────┐         ┌─────────────────────────────┐
 │ agent-nexus  (Fastify + SQLite)│ via TCP │ docker-socket-proxy          │
 │  - master-passphrase vault     │────────►│  endpoint allowlist:         │
 │  - provider abstraction        │ to:2375 │  containers, exec, volumes,  │
 │  - spawns workers              │         │  images. Everything else 403.│
 └──────────────┬─────────────────┘         └────────────┬─────────────────┘
                │ creates / inspects                     │ mounts /var/run/docker.sock:ro
                │                                        ▼
                │                          ┌─────────────────────────────┐
                │                          │ host Docker engine          │
                ▼                          └────────────┬─────────────────┘
 ┌───────────────────────────────────┐                  │ spawns
 │ nexus-worker  (one per session)   │◄─────────────────┘
 │  - tmux PID 1, session "claude"   │
 │  - claude --remote-control NAME   │       ┌─────────────────────────┐
 │    --worktree NAME                │──────►│ Anthropic cloud relay   │
 │  - /session ← claude-session vol  │       └────────────┬────────────┘
 │  - /workspace ← per-worker volume │                    │
 └───────────────────────────────────┘                    │
                                                          ▼
                                          your Claude mobile / claude.ai/code
  • One worker container = one Claude session = one git worktree. Spawn multiple workers on the same repo for collaborating agents; they share the same /workspace clone but each runs --worktree <name> so changes land on isolated branches.
  • Shared OAuth state: the claude-session docker volume holds credentials.json + account.json produced by claude auth login. Workers mount it read-write so refresh-token rotations persist across the fleet.
  • Docker socket is fronted by a proxy (tecnativa/docker-socket-proxy) on a default-deny internal network. Even if Nexus is compromised, the attacker cannot reach docker run --privileged or any non-allowlisted endpoint.
  • Per-spawn secrets (Forgejo token, authed clone URL) are injected via tmpfs after the worker starts. They do NOT appear in docker inspect or /proc/1/environ.
  • Worker discovery: containers carry nexus-managed=true. The UI never lists unrelated host containers.

Repo layout

agent-nexus/
├── nexus/                  # the webapp (Fastify + Vite + Kysely)
├── worker/                 # the Claude runtime image
├── docker-compose.yml      # nexus + docker-socket-proxy
├── DEPLOYMENT.md           # threat model, nginx, backups
└── .forgejo/workflows/ci.yml

See DEPLOYMENT.md for production deployment, nginx reverse-proxy config, the threat model after the socket-proxy hardening, and backup snippets.