Hub-läge i samma binär (doc/hub-plan.md): agenter ansluter utåt med delad agent-nyckel (hub-sektion i config.json), web-/CLI-klienter loggar in (lokala konton, pbkdf2; Authenticator-interface för LDAP senare) och styr alla agenters sessioner via proxy + merged SSE. Webbklienten autodetekterar hub/local via /api/mode. Nya kommandon: helmd hub [setpass|users|agentkey], helmd ctl (login/agents/sessions/ prompt/screen/answer/events …). Enhetstester + scripts/run-hub-smoke.sh (15-stegs e2e: hub + agent + ctl). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011KikHkfCiC3yELbsMN8fT9
214 lines
9.7 KiB
Markdown
214 lines
9.7 KiB
Markdown
# helmd — agent-helm v2-servern
|
|
|
|
En **enskild statisk Go-binär** som äger kommunikationen med en
|
|
tmux-session där en CLI-agent (agy, claude, …) kör, och exponerar ett
|
|
**REST-API + SSE** som webb- och mobilklienterna pratar med.
|
|
|
|
Designen bygger på insikten från Gemini-OAuth-döden: att integrera mot
|
|
en specifik agents hook-mekanism är skört. `helmd` behandlar i stället
|
|
**tmux som sanningskälla** — den läser skärmen, känner igen frågor och
|
|
skickar tangenter. Fungerar därför med vilken TUI-agent som helst, och
|
|
överlever att helmd själv startas om (sessioner adopteras tillbaka).
|
|
|
|
```
|
|
webb-PWA / Android ──(LAN eller SSH-tunnel)──► helmd :8788 ──tmux──► agy
|
|
│
|
|
└──► ntfy.brasse-pc.eu (pushar till mobilen)
|
|
```
|
|
|
|
## Säkerhetsmodell
|
|
|
|
- **Ingen publik exponering.** Servern binder `127.0.0.1:8788` som
|
|
default. På det privata nätet kan man binda `0.0.0.0:8788`
|
|
(`helmd serve --listen 0.0.0.0:8788`). Utifrån: SSH-tunnel —
|
|
`ssh -L 8788:127.0.0.1:8788 <host>` — eller Android-klientens
|
|
inbyggda tunnel (steg 3 i planen).
|
|
- **Bearer-token** på alla endpoints. Genereras vid första start,
|
|
lagras `0600` i `~/.config/helmd/config.json`, visas med
|
|
`helmd token`. Skickas som `Authorization: Bearer <t>`,
|
|
`X-Api-Token: <t>` eller `?token=<t>` (för SSE/EventSource och
|
|
`<img>`-länkar).
|
|
- Delad HTML serveras med `Content-Security-Policy: sandbox` så att
|
|
ett delat dokument aldrig kan anropa API:et med klientens token.
|
|
|
|
## Kommandon
|
|
|
|
```
|
|
helmd serve [--listen host:port] [--config fil] starta agent-servern
|
|
helmd share [--note text] [--session namn] <fil> dela en fil till klienterna
|
|
helmd token skriv ut API-token
|
|
helmd hub [--listen host:port] [--config fil] starta hubben (Pi5)
|
|
helmd hub setpass <användare> [--password PW] sätt/skapa hub-konto
|
|
helmd hub users lista hub-konton
|
|
helmd hub agentkey skriv ut agent-nyckeln
|
|
helmd ctl <kommando> CLI-styrklient mot hubben
|
|
helmd version
|
|
```
|
|
|
|
## Hubben (`helmd hub`)
|
|
|
|
Central server (Pi5) som **agenter ansluter utåt till** och där web-
|
|
och CLI-klienter **loggar in** och styr alla agenters sessioner. Full
|
|
design och beslut: [../doc/hub-plan.md](../doc/hub-plan.md).
|
|
|
|
- **Agent-anslutning:** ingen inloggning/konto — agenten skickar den
|
|
**delade agent-nyckeln** (`agent_key` i hubbens config) vid
|
|
registrering. Hämta den med `helmd hub agentkey` på hubben, eller
|
|
efter inloggning med `helmd ctl agentkey` / Inställningar-fliken i
|
|
webben. Transporten är long-poll över HTTP (`/hub/register`,
|
|
`/hub/work`, `/hub/result`, `/hub/events`) — agenten kör jobben mot
|
|
sin egen lokala mux, så hela API-tabellen ovan funkar oförändrad
|
|
genom hubben. Agenter ser aldrig något om varandra.
|
|
- **Klient-login:** `POST /api/login` med användarnamn + lösenord →
|
|
Bearer-token. Konton ligger lokalt i hub-konfigen (pbkdf2-sha256);
|
|
`Authenticator`-interfacet i `hubauth.go` är utbytespunkten för
|
|
LDAP/SSO längre fram. Första starten seedas `admin` (lösenord från
|
|
`HELMD_HUB_ADMIN_PASSWORD` eller slumpat — skrivs ut i loggen).
|
|
- **Klient-API:** `GET /api/agents` (lista + online-status),
|
|
`ANY /api/agents/{namn}/…` (proxas till agentens `/api/…`),
|
|
`GET /api/events` (merged SSE, fältet `agent` på varje event),
|
|
`GET /api/agentkey`, `GET/POST /api/login|logout|me`.
|
|
`GET /api/mode` (oautentiserad) svarar `hub`/`local` så att den
|
|
inbäddade webbklienten kan välja login-form — i hub-läge får den
|
|
användarnamn/lösenord och en agentväljare.
|
|
- **Agentens config** (`config.json`) pekar ut hubben:
|
|
|
|
```json
|
|
"hub": {
|
|
"url": "http://192.168.0.19:8790",
|
|
"name": "brasse-linux01",
|
|
"key": "<agent_key>",
|
|
"enabled": true
|
|
}
|
|
```
|
|
|
|
Det lokala API:t på `:8788` fortsätter fungera parallellt — hubben
|
|
nere betyder bara att uplinken backar och försöker igen.
|
|
- **Hubbens config** (`~/.config/helmd/hub.json`, `HELMD_HUB_CONFIG`
|
|
överstyr): `listen` (default `127.0.0.1:8790`), `auth` (`local`),
|
|
`users`, `agent_key`, `session_ttl_hours` (default 168).
|
|
- **CLI-klienten:** `helmd ctl login --server http://192.168.0.19:8790`
|
|
→ `ctl agents`, `ctl sessions <agent>`, `ctl new <agent> <namn>`,
|
|
`ctl screen|prompt|question|answer|keys|kill <agent> <session> …`,
|
|
`ctl shares <agent>`, `ctl events`. Sparar server + token i
|
|
`~/.config/helmd/ctl.json`.
|
|
- **Smoke-test av hela flödet:** `bash scripts/run-hub-smoke.sh`
|
|
(hub + agent + ctl lokalt, 15 kontroller).
|
|
|
|
`helmd share` är **agentens verktyg för att dela filer** (html, md,
|
|
png, jpg, …) till klienterna: agenten kör kommandot i sin shell, filen
|
|
laddas upp till servern och dyker upp i klientens delningsflik.
|
|
Tillåtna filtyper styrs av `share_exts` i konfigen.
|
|
|
|
## API
|
|
|
|
Alla svar är JSON om inget annat sägs. Auth krävs överallt.
|
|
|
|
| Metod & väg | Gör |
|
|
|---|---|
|
|
| `GET /api/health` | `{ok, version, sessions}` |
|
|
| `GET /api/sessions` | lista sessioner (namn, alive, seq, pending question) |
|
|
| `POST /api/sessions` | `{name, cmd?, cwd?}` startar agent i ny tmux-session `helm-<name>`; `{name, adopt:true, tmux?}` adopterar befintlig |
|
|
| `GET /api/sessions/{n}` | en sessions tillstånd |
|
|
| `DELETE /api/sessions/{n}` | döda sessionen |
|
|
| `GET /api/sessions/{n}/screen` | pane-text; `?ansi=1` behåller färger, `?history=N` tar med N rader scrollback |
|
|
| `POST /api/sessions/{n}/prompt` | `{text, submit?:bool}` klistrar in text (radbrytningar ok) och trycker Enter |
|
|
| `GET /api/sessions/{n}/question` | `{question}` — detekterad väntande fråga eller `null` |
|
|
| `POST /api/sessions/{n}/answer` | `{option:N}` väljer alternativ N (1-baserat). Numrerade listor väljs med siffertangent, onumrerade navigeras med pil+Enter |
|
|
| `POST /api/sessions/{n}/keys` | `{keys:["Escape","Down","Enter"]}` råa tmux-tangenter |
|
|
| `POST /api/sessions/{n}/mode` | `{action:"cycle"}` skickar Shift+Tab (lägesbyte i agy/claude) |
|
|
| `GET /api/config` / `PUT /api/config` | läs/ändra agentkommando, args, workdir, poll-intervall, ntfy-inställningar (token ändras aldrig via API) |
|
|
| `GET /api/shares` / `POST /api/shares` | lista / ladda upp (multipart, fält `file`, `note`, `session`) |
|
|
| `GET /api/shares/{id}` / `…/raw` | metadata / själva filen |
|
|
| `DELETE /api/shares/{id}` | ta bort delning |
|
|
| `POST /api/notify` | `{topic?, title?, message, priority?}` → ntfy; topic måste finnas i `allowed_topics` (`agent-helm`, `claude`) |
|
|
| `GET /api/events` | SSE-ström: `screen` (seq), `question`, `session`, `share` |
|
|
|
|
### Frågedetektering
|
|
|
|
Pollern läser panen (default var 500:e ms), och `DetectQuestion`
|
|
känner igen agy:s frågeformer: godkännande-listor (`1. Yes … 4. No`),
|
|
trust-/val-listor med `>`-markör, och gamla Gemini-CLI:s inramade
|
|
pickers. Varje fråga får en stabil hash — servern ntfy:ar **en gång
|
|
per ny fråga** till topic `agent-helm` (kan stängas av med
|
|
`notify_on_question:false`).
|
|
|
|
Verifierat mot riktiga agy 1.1.9-sessioner (trust-prompt,
|
|
kommandogodkännande) och mot mock i integrationstestet.
|
|
|
|
## Konfiguration
|
|
|
|
`~/.config/helmd/config.json` (skapas vid första start; override med
|
|
`HELMD_CONFIG` eller `--config`):
|
|
|
|
```json
|
|
{
|
|
"listen": "127.0.0.1:8788",
|
|
"token": "<genereras>",
|
|
"agent_cmd": "agy",
|
|
"agent_args": [],
|
|
"workdir": "/home/brasse",
|
|
"share_dir": "~/.local/share/helmd/shares",
|
|
"share_exts": [".html", ".htm", ".md", ".txt", ".json", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".pdf"],
|
|
"poll_ms": 500,
|
|
"cols": 200, "rows": 50,
|
|
"ntfy": {
|
|
"server": "https://ntfy.brasse-pc.eu",
|
|
"topic": "agent-helm",
|
|
"allowed_topics": ["agent-helm", "claude"],
|
|
"enabled": true,
|
|
"notify_on_question": true
|
|
}
|
|
}
|
|
```
|
|
|
|
## Webbklienten
|
|
|
|
Inbäddad i binären (`web/` + `go:embed`) och serveras på `/` — surfa
|
|
till servern (t.ex. `http://127.0.0.1:8788` genom en SSH-tunnel) och
|
|
klistra in din token. Ramverksfri (ren ES-moduler): sessionslista,
|
|
ANSI-färgad skärmvy (SSE + poll), promptfält, frågekort med knappar,
|
|
tangent-verktygsrad, delningsflik (bilder, sandboxad HTML, markdown)
|
|
och inställningar. Statiska filer kräver ingen auth (koden är publik);
|
|
allt data går genom det token-skyddade API:et.
|
|
|
|
## Docker på Pi5 (LAN-only)
|
|
|
|
`Dockerfile` bygger en arm64-image med tmux+bash via
|
|
`.gitea/workflows/helmd-image.yaml` → `localhost:5000/helmd:latest`.
|
|
Stack: `/srv/dockge-staks/helmd/` med host-port **8788** och volym
|
|
`/srv/docker/helmd → /data`. **Ingen NPM-vhost** — nås bara på LAN
|
|
(`http://192.168.0.19:8788`) eller via SSH-tunnel utifrån. Sessionerna
|
|
kör i containern (bash tills en agent installeras där).
|
|
|
|
## Bygga & testa
|
|
|
|
```bash
|
|
cd helmd
|
|
go test ./... # enhetstester (frågedetektorn m.m.)
|
|
CGO_ENABLED=0 go build -o build/helmd .
|
|
bash scripts/run-integration.sh # 26 API-tester i isolerad Docker-container
|
|
```
|
|
|
|
Integrationstestet startar helmd i en `debian:stable-slim`-container
|
|
med en mock-agent (`testdata/mock-agent.sh`) som härmar agy:s båda
|
|
frågetyper, och kör hela API-ytan med curl.
|
|
|
|
## CI / release
|
|
|
|
`.gitea/workflows/helmd-release.yaml` kör på push till master som rör
|
|
`helmd/`: `go vet` + `go test`, bygger **linux x64 + arm64** statiskt
|
|
och publicerar på den rullande releasen **`helmd-latest`**
|
|
(`helmd-linux-x64`, `helmd-linux-arm64`, `checksums.txt`).
|
|
|
|
## Kända begränsningar (v1)
|
|
|
|
- `mode cycle` skickar Shift+Tab (BTab) — verifierat tangentnamn i
|
|
tmux men inte bekräftat att agy byter läge på det; justera via
|
|
`/keys` om agy använder annan tangent.
|
|
- Ingen WebSocket-terminal ännu — klienterna pollar `screen` +
|
|
lyssnar på SSE. Räcker för kort-UI:t; rå terminal-vy kommer med
|
|
webbklienten (steg 2).
|
|
- En helmd-instans per maskin/användare; flera agenter = flera
|
|
sessioner i samma helmd.
|