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
11 KiB
agent-helm — hub-plan (v2 steg 4)
Skriven 2026-08-06, innan implementation. Status hålls i status.md; v2-planen i övrigt i plan.md.
Mål
En central hub på Pi5 som alla agent-klienter ansluter utåt till, och som web- och CLI-klienter loggar in på för att styra alla agenters sessioner från ett ställe:
brasse-linux01: helmd serve ──utåt──►┐
Pi5-container: helmd serve ──utåt──►├─ helmd hub :8790 (Pi5)
(fler maskiner senare) ──────utåt──►┘ ▲ ▲
│ login │ login
webbklient helmd ctl (CLI)
Krav (från Björn 2026-08-06):
- En server som agent-klienter ansluter till; styrklienter ansluter till samma server och styr alla agenters sessioner.
- Agent-klienten ska ha en configfil där man ställer in vilken server den ansluter till.
- Servern godkänner agent-anslutningar utan inloggning.
- Styrklienter (web/CLI) måste logga in — i första steget ett admin-konto, som längre fram byts mot LDAP eller annan lösning.
- Servern körs på Pi5, en webbklient kopplad till den, en agent-klient på brasse-linux01, och en CLI-klient att testa hela flödet med.
Arkitektur
Allt byggs in i samma helmd-binär (fortsatt ren stdlib, en fil):
| Del | Kommando | Roll |
|---|---|---|
| Hub | helmd hub |
Central server på Pi5. Agentregister, proxy, login, merged SSE, webbklient. |
| Agent | helmd serve |
Som idag (tmux-ägare, lokalt REST+SSE) plus en utåtgående hub-uplink om hub är satt i config.json. |
| CLI | helmd ctl <cmd> |
Styrklient mot hubben: login, agents, sessions, prompt, screen, answer, events … |
| Web | inbäddad SPA | Samma SPA som idag, utökad: login med användarnamn/lösenord i hub-läge + agentväljare. |
Transport: long-poll över HTTP, inte WebSocket
Agenten ansluter utåt med vanlig HTTP (long-poll): den håller
2–3 väntande GET /hub/work mot hubben; när en styrklient anropar
/api/agents/{namn}/… skickas anropet som ett jobb i svaret på en
väntande poll, agenten kör jobbet mot sin egen lokala mux (samma
handlers och auth som idag) och POST:ar resultatet tillbaka.
Motivering (samma beslut som v2 i övrigt): ren stdlib (inget WS-beroende), curl-testbart, överlever proxies, och all API-logik återanvänds — hubben är bara en växel.
Hub-API
Agentsidan (ingen inloggning/konto — men en delad agent-nyckel, beslut 2026-08-06, se Auth-modell):
| Metod & väg | Gör |
|---|---|
POST /hub/register |
{name, host, version, key:<agent_key>} → {key:<pollnyckel>}. Registrerar/ersätter agenten; 401 vid fel agent-nyckel. |
GET /hub/work?name&key |
long-poll (~25 s) → ett jobb {id, method, path, content_type, body} eller 204. |
POST /hub/result?name&key |
{id, status, headers, body} — svar på ett jobb. |
POST /hub/events?name&key |
{events:[…]} — agenten vidarebefordrar sina SSE-events (screen/question/session/share). |
Klientsidan (login krävs på allt utom login/mode):
| Metod & väg | Gör |
|---|---|
POST /api/login |
{username, password} → {token, user}. |
POST /api/logout / GET /api/me |
logga ut / vem är jag |
GET /api/mode |
{"mode":"hub"} (oautentiserad — webbklienten avgör läge; agentens serve svarar "local"). |
GET /api/agents |
alla registrerade agenter: namn, host, version, online, last_seen. |
GET /api/agentkey |
agent-nyckeln (för att koppla in nya agenter — kräver login). |
ANY /api/agents/{namn}/… |
proxas till agentens lokala /api/… — hela befintliga API-ytan (sessions, screen, prompt, question, answer, keys, mode, shares, config). |
GET /api/events |
merged SSE från alla agenter; varje event får fältet agent. Även agent-events (online/offline). |
/api/agents/{namn}/events proxas inte (SSE genom jobbkanalen
vore en evighetsförfrågan) — hubbens egna /api/events ersätter den.
Vidarebefordrade svar behåller Content-Type och
Content-Security-Policy (delad HTML måste förbli sandboxad även via
hubben), Content-Disposition och Cache-Control. Övriga headers
släpps.
Auth-modell
-
Agenter: delad agent-nyckel, inga konton. (Beslut 2026-08-06 efter säkerhetsdiskussion — helt öppen registrering lät vem som helst på LAN:et ersätta en agent och ta emot prompts.) Hubben genererar en
agent_keyvid första start; agenten anger samma nyckel i sin configfil och skickar den vid registrering — stämmer den litar hubben på agenten. Nyckeln hämtas ur hub-konfigen (helmd hub agentkey) eller via API/webb/CLI efter inloggning (GET /api/agentkey). -
Agent-isolering:
/hub/*-ytan exponerar ingenting om andra agenter — en agent kan bara polla sina egna jobb, posta sina egna resultat/events (via sin per-registrering-pollnyckel) och får aldrig listor eller data om övriga agenter. Agent→agent-kommunikation finns inte. (Obs: alla maskiner med agent-nyckeln kan registrera valfritt namn — nyckelinnehavarna är egna maskiner, så namnkapning skyddas inte mellan dem.) -
Styrklienter: obligatorisk inloggning.
Authenticator-interface:type Authenticator interface { Authenticate(username, password string) bool }Första implementation: lokala konton i hub-konfigen (pbkdf2-sha256, salt, 210k iterationer).
auth: "local"i konfigen; LDAP blir en ny implementation +auth: "ldap"längre fram — inget annat i hubben behöver ändras. -
Admin-seeding: första starten utan användare skapar
adminmed lösenord frånHELMD_HUB_ADMIN_PASSWORD(för Docker) eller ett slumpat som skrivs ut en gång i loggen. Byts medhelmd hub setpass admin. -
Sessionstokens (Bearer) i minnet, TTL 7 dagar (konfigurerbar). Hub-omstart loggar ut alla — acceptabelt.
Konfigfiler
Agenten — ~/.config/helmd/config.json får en ny sektion (kravet
"configfil där man ställer in vilken server man ansluter till"):
"hub": {
"url": "http://192.168.0.19:8790",
"name": "brasse-linux01",
"key": "<agent_key från hubben>",
"enabled": true
}
Tom url/enabled:false (default) = dagens beteende, ingen uplink.
name default = hostname. Lokala API:t fortsätter fungera parallellt
(graceful degradation — hubben nere ⇒ styr lokalt som idag).
Hubben — ~/.config/helmd/hub.json (HELMD_HUB_CONFIG/--config
överstyr; i containern /data/hub.json):
{
"listen": "127.0.0.1:8790",
"auth": "local",
"users": [{"username": "admin", "salt": "…", "hash": "…"}],
"agent_key": "<genereras vid första start>",
"session_ttl_hours": 168
}
CLI:n — ~/.config/helmd/ctl.json: {server, user, token},
skrivs av helmd ctl login.
CLI-klienten (helmd ctl)
Testar hela flödet utan webbläsare:
helmd ctl login [--server URL] [--user admin] [--password PW]
helmd ctl logout | agents | events [--agent X]
helmd ctl sessions <agent>
helmd ctl new <agent> <namn> [--cmd K] [--cwd D]
helmd ctl screen <agent> <session> [--ansi] [--history N]
helmd ctl prompt <agent> <session> <text …>
helmd ctl question|answer|keys|kill <agent> <session> […]
helmd ctl shares <agent>
helmd ctl agentkey # visar hubbens agent-nyckel (kräver login)
Lösenord: --password, annars läses en rad från stdin (funkar både
interaktivt och i pipe för test).
Webbklienten
Samma inbäddade SPA i båda lägena; GET /api/mode avgör vid load:
- hub: loginform användarnamn + lösenord →
/api/login; agentväljare ovanför sessionslistan; alla API-anrop prefixas/api/agents/{vald agent}; SSE filtreras påagent-fältet; agent-events uppdaterar väljaren. - local: exakt dagens beteende (token-inklistring).
Uppfyllnad av kraven
| Krav | Lösning |
|---|---|
| Server som agenter ansluter till, klienter styr alla sessioner | helmd hub + proxy /api/agents/{namn}/… + merged SSE |
| Configfil på agenten för val av server | hub-sektionen i config.json |
| Agent-anslutning utan inloggning, hubben litar på delad nyckel | POST /hub/register med agent_key — inga konton; nyckeln hämtas ur hub-config eller efter login (beslut 2026-08-06) |
| Agenter isolerade från varandra | /hub/* exponerar inget om andra agenter; ingen agent→agent-väg |
| Klientlogin: admin-konto nu, LDAP sen | lokala konton + Authenticator-interface, auth-nyckel i hub-konfig |
| Server på Pi5 + webbklient | ny Docker-stack helm-hub (:8790), samma image localhost:5000/helmd, SPA inbäddad |
| Agent-klient på denna dator | helmd serve på brasse-linux01 med hub.enabled:true |
| CLI-klient för att testa flödet | helmd ctl |
Implementationsordning
- Kod (
helmd/):hubauth.go(konton/sessioner/Authenticator),hub.go(register/work/result/events + login + proxy + SSE),uplink.go(agentens utåtgående anslutning),ctl.go(CLI),main.go(subkommandonhub,ctl),config.go(hub-sektion),/api/modeiapi.go. - Webb (
helmd/web/): mode-detektering, login, agentväljare. - Tester: Go-enhetstester (auth, register/work/result-roundtrip via riktiga uplink-koden mot stub-mux) + lokal e2e-smoke (hub + serve + ctl på brasse-linux01, bash-agent).
- Docs: helmd/README.md (API-tabeller), README, status.md.
- Push → CI bygger
helmd-latest+ arm64-image (serialiserat — max 2 tunga byggen på Pi5-runnern). - Deploy: stack
helm-hubpå Pi5 (port 8790, volym/srv/docker/helm-hub → /data,HELMD_HUB_ADMIN_PASSWORDsätts inte — slumpat lösenord läses ur loggen och byts). Agent på brasse-linux01 fårhub-config. Verifiera medhelmd ctlmot Pi5- webbklienten. Ev. koppla även Pi5:s befintliga helmd-container
som andra agent (
hub.urlmot hub-containern).
- webbklienten. Ev. koppla även Pi5:s befintliga helmd-container
som andra agent (
- infra-Doc: uppdatera
services/agent-helm.md+hosts/pi5.md(ny stack/port).
Beslut
- Samma binär, nya subkommandon — inte ett nytt repo/paket; följer "hela v2 är EN fil".
- Long-poll i stället för WebSocket — noll beroenden, curl-testbart.
- Delad agent-nyckel i stället för öppen registrering eller godkännande-flöde (Björn 2026-08-06) — inga konton på agenterna, men LAN-grannar utan nyckeln kan varken registrera eller kapa agenter. Nyckeln distribueras manuellt via configfilen.
- Hubben är en växel, inte en kopia av API:t — agentens handlers återanvänds oförändrade via jobb-exekvering mot lokala muxen; nya endpoints i agenten dyker automatiskt upp genom hubben.
- Port 8790 för hubben (8788 = lokal helmd, som idag).
- Delningar bor kvar hos agenten — hubben proxar även rå-filer; ingen lagring på hubben.
- Sessionstokens i minnet — enkelt; persistens kan läggas till om utloggning-vid-omstart stör.
Öppna punkter (tas efter första deployen)
- LDAP/Authentik-implementation av
Authenticator(identity-sso finns redan i infra:n). - Ska Pi5:s helmd-container alltid vara agent mot hubben? (Lutar åt ja när hubben visat sig stabil — då kan LAN-porten 8788 stängas.)
- Roller/rättigheter per användare (admin vs läsklient) — allt är admin i första versionen.
- ntfy: hubben kunde ta över frågenotiserna så att inte varje agent pushar själv (dubbelnotiser undviks idag genom att bara agenten pushar).