Files
agent-helm/doc/hub-plan.md
Bjorn Blomberg 77682e91b5
All checks were successful
helmd-image / build (push) Successful in 3m22s
helmd-release / build-release (push) Successful in 4m28s
helmd hub: central server på Pi5 + agent-uplink + helmd ctl + web-login
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
2026-08-06 23:19:18 +02:00

11 KiB
Raw Blame History

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):

  1. En server som agent-klienter ansluter till; styrklienter ansluter till samma server och styr alla agenters sessioner.
  2. Agent-klienten ska ha en configfil där man ställer in vilken server den ansluter till.
  3. Servern godkänner agent-anslutningar utan inloggning.
  4. 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.
  5. 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 23 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_key vid 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 admin med lösenord från HELMD_HUB_ADMIN_PASSWORD (för Docker) eller ett slumpat som skrivs ut en gång i loggen. Byts med helmd 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

  1. 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 (subkommandon hub, ctl), config.go (hub-sektion), /api/mode i api.go.
  2. Webb (helmd/web/): mode-detektering, login, agentväljare.
  3. 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).
  4. Docs: helmd/README.md (API-tabeller), README, status.md.
  5. Push → CI bygger helmd-latest + arm64-image (serialiserat — max 2 tunga byggen på Pi5-runnern).
  6. Deploy: stack helm-hub på Pi5 (port 8790, volym /srv/docker/helm-hub → /data, HELMD_HUB_ADMIN_PASSWORD sätts inte — slumpat lösenord läses ur loggen och byts). Agent på brasse-linux01 får hub-config. Verifiera med helmd ctl mot Pi5
    • webbklienten. Ev. koppla även Pi5:s befintliga helmd-container som andra agent (hub.url mot hub-containern).
  7. 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).