mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 03:31:36 +00:00
feat: bundle agent executables + web-driven claude login
Two changes so an operator can stand up and authenticate Handler entirely from the browser, with a self-contained control image. Bundle executables in the control image (Dockerfile.control) - Node.js (NodeSource) + the Claude Code CLI, mise (official apt repo), and forge (git-pkgs/forge, built in a Go stage) join the existing git/tmux/ssh. No more bring-your-own binaries: live agent spawning, the verification gate, CI resolution, and the login flow all work out of the box. Installed under /usr so the /var/lib/handler VOLUME never masks them; mise apt source pinned to $TARGETARCH for the multi-arch (amd64/arm64) build. Claude login from the web UI - New login_start / login_submit command types (migration 0005) drive the interactive `claude /login` through the same enqueue→worker handoff every other control action uses — the API container has no claude binary. - control/login.py opens `claude` in a dedicated tmux session, sends /login, selects the subscription account, and scrapes the claude.com authorization URL (tmux.capture_pane, -pJ so a wrapped URL rejoins); a second command feeds back the pasted code. Fully mockable via the tmux seam. - API: POST /login/start, POST /login/submit (admin-gated). - Dashboard: a "Claude Login" pane — a button that starts the flow, embeds the URL in an iframe (with a new-tab fallback, since claude.com may refuse framing), and takes the code to finish. Also un-ignores frontend/lib/ (a broad Python `lib/` rule was swallowing the UI's own api client + formatters, breaking rebuilds from a fresh clone) and reconstructs those two source files; rebuilt static export committed. Tests: control/login unit tests (tmux faked), worker dispatch, and API route tests. Full suite green (195 tests), ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YKVyBmKvWDVgrFC9WER2f2
This commit is contained in:
@@ -155,9 +155,10 @@ the `/var/lib/handler` data volume:
|
||||
| `ghcr.io/0xwheatyz/handler` | [`Dockerfile`](Dockerfile) | the API (`uvicorn`) — also applies migrations on start | [`docker.yml`](.github/workflows/docker.yml) |
|
||||
| `ghcr.io/0xwheatyz/handler/control` | [`Dockerfile.control`](Dockerfile.control) | the control worker (`handler worker`) | [`docker-control.yml`](.github/workflows/docker-control.yml) |
|
||||
|
||||
The control image bakes in `git` + `tmux`; the `claude` and `forge` binaries are
|
||||
bring-your-own (layer or mount them in for live agent spawning — the CI poller degrades
|
||||
gracefully without `forge`). The **worker** drains the control-command queue the API
|
||||
The control image bakes in **every executable the control layer shells out to** — `git`,
|
||||
`tmux`, `openssh-client`, `node` + the `claude` CLI, `mise`, and `forge` — so live agent
|
||||
spawning, the verification gate, CI resolution, and the [web login](#claude-login-from-the-web-ui)
|
||||
flow all work with zero bring-your-own binaries. The **worker** drains the control-command queue the API
|
||||
enqueues (spawn/kill/resume/approve/reject/forge-init/poll-ci) and sweeps CI on an interval
|
||||
(subsuming `poll-ci --watch`), so the whole system is drivable from the dashboard — see
|
||||
[Web management](#web-management).
|
||||
@@ -224,12 +225,38 @@ What the dashboard can now do (all state-changing actions require `ADMIN_TOKEN`)
|
||||
- **Activity** — every enqueued command with its status (queued → running → done/failed) —
|
||||
the audit log of what the dashboard triggered. The UI polls `GET /commands/{id}` for
|
||||
live status.
|
||||
- **Claude Login** — log Claude Code in on the host from the browser (see below), so agents
|
||||
spawn against a real authenticated `claude` with no shell access to the container.
|
||||
|
||||
The command queue is exposed over HTTP as `POST …/agents/spawn`, `POST …/agents/{n}/kill`,
|
||||
`POST …/approvals`, `POST …/forge-init`, `POST …/poll-ci`, `POST …/sync`, and
|
||||
`GET /commands[/{id}]`; hosts as `/hosts`; schedules as `/schedules` +
|
||||
`/projects/{id}/schedules`; project mutation as `PATCH`/`DELETE /projects/{id}`. Run the
|
||||
worker with `handler worker` (the control image's default command).
|
||||
`POST …/approvals`, `POST …/forge-init`, `POST …/poll-ci`, `POST …/sync`,
|
||||
`POST /login/start`, `POST /login/submit`, and `GET /commands[/{id}]`; hosts as `/hosts`;
|
||||
schedules as `/schedules` + `/projects/{id}/schedules`; project mutation as
|
||||
`PATCH`/`DELETE /projects/{id}`. Run the worker with `handler worker` (the control image's
|
||||
default command).
|
||||
|
||||
### Claude login from the web UI
|
||||
|
||||
Agents *are* `claude` processes, so the control container needs a logged-in Claude Code.
|
||||
Because that container has no interactive shell in normal operation, the **Claude Login**
|
||||
pane logs it in from the browser — the same command-queue handoff every other control
|
||||
action uses:
|
||||
|
||||
1. **Log in to Claude** enqueues a `login_start` command. The worker opens `claude` in a
|
||||
dedicated tmux session in the control container, sends `/login`, selects the **Claude
|
||||
account with subscription** option, and scrapes the pane for the `claude.com`
|
||||
authorization URL — returned in the command result.
|
||||
2. The UI opens that URL in an embedded frame (with a new-tab link as a fallback, since
|
||||
claude.com may refuse to be framed). You authorize and Claude gives you a code.
|
||||
3. **Finish login** enqueues a `login_submit` command carrying the code; the worker feeds
|
||||
it into the still-open session, waits for claude to exchange it, and reports success.
|
||||
|
||||
The login session lives in the control container, and Claude's credentials land under the
|
||||
`handler` user's home on the `/var/lib/handler` volume — so the login **persists** across
|
||||
restarts and is shared by every agent the worker spawns. The flow is admin-gated
|
||||
(`ADMIN_TOKEN`) and driven entirely through `POST /login/start` and `POST /login/submit`.
|
||||
The interactive claude TUI is timing-sensitive; the waits in `control.login` are generous
|
||||
and overridable if a slow host needs more.
|
||||
|
||||
## Control CLI
|
||||
|
||||
|
||||
Reference in New Issue
Block a user