Files
lvmh/daemon/README.md
T

4.5 KiB

lvmh daemon

Go daemon binding the lvmh wire protocol (../PROTOCOL.md): agent websocket (/agent/ws), REST API (/api/*), web websocket (/ws), GitLab PAT management and worker-container spawning over the Docker API.

Layout

file role
main.go flags, env wiring, HTTP server lifecycle
hub.go agent WS hub (/agent/ws) + web WS hub (/ws), prompt/abort routing, online state
api.go REST handlers, bearer auth, recover middleware, embedded web UI
gitlab.go PAT connect/validate/list member projects
docker.go spawn pipeline: clone → ensure image → create/start container, job status
store.go SQLite (modernc, CGO-free): sessions, events, settings, containers
webdist/ embedded web UI placeholder (replace with web/dist at build time)

Build & run

cd daemon
go build ./...

LVMH_TOKEN=secret LVMH_DB=/tmp/lvmh.db ./lvmh-daemon --addr :8686

Flags: --addr (default :8686), --db (default $LVMH_DB or /data/lvmh.db), --webdist (default /app/web-dist when that directory exists, else the embedded webdist/).

Docker image (multi-stage, CGO_ENABLED=0):

docker build -f daemon/Dockerfile -t lvmh-daemon:latest .

To bake the real web UI into the binary, copy the built frontend over the placeholder before building: cp -r web/dist daemon/webdist/. Otherwise mount it at /app/web-dist (the default --webdist path) or pass --webdist.

Environment

var default meaning
LVMH_TOKEN — (required) bearer token for REST + WS auth
LVMH_DB /data/lvmh.db sqlite path (WAL mode)
GITLAB_BASE_URL https://git.westphal.fr GitLab instance
LVMH_WORKER_DOCKERFILE /app/build/docker/worker.Dockerfile image build recipe
LVMH_WORKER_CONTEXT parent of dockerfile dir docker build context
LVMH_REPO_DIR /data/repos host-side clone cache (<slug> subdirs)
LVMH_CONTAINER_LVMH_URL ws://lvmh:8686/agent/ws daemon URL handed to spawned containers
LVMH_CONTAINER_NETWORK lvmh-net network for spawned containers
LVMH_WORKER_MODELS /app/build/docker/worker-models.json mounted read-only as models.json when present
ZAI_RENAUD_API_KEY injected into spawned containers
DOCKER_HOST honored by the docker client

Spawn pipeline

POST /api/spawn {repo, branch?} returns {sessionId, containerId:""} immediately (async). The job walks cloning → building → creating → running (or error + message) in GET /api/spawn/status and on the web WS.

  1. Per-repo-slug mutex; git clone/git pull --ff-only into $LVMH_REPO_DIR/<slug>.
  2. Ensure image lvmh-worker:latest — built from LVMH_WORKER_DOCKERFILE when missing (502-style clear error from POST /api/spawn if neither image nor Dockerfile exist).
  3. Named volume lvmh-repo-<slug> (seeded from the clone on first use), lvmh-sessions volume, lvmh.session=<sessionId> label, network lvmh-net, env ZAI_RENAUD_API_KEY, LVMH_TOKEN, LVMH_URL, LVMH_SESSION_ID.
  4. DELETE /api/sessions/:id/container stops and removes the container.

Tests

go vet ./... && staticcheck ./... && go test ./... -count=1

Covered: store persistence + replay-after-seq, agent handshake (hello→welcome.lastSeq), reconnect replacement, live message_update fan-out (no persistence), prompt routing + 409 offline, bearer 401s, GitLab user/ projects mapping against an httptest upstream.

Protocol ambiguities (daemon-side resolutions)

  • Envelope payload shape: payload fields are flattened into the envelope object (as the hello example shows). The daemon persists the full frame verbatim; GET /api/sessions/:id/events replays it as-is.
  • events frame after: exclusive lower bound = seq of the first event in the batch minus 1, matching the REST ?after= semantics.
  • POST /api/spawn response: returns immediately with {sessionId, containerId: ""}containerId appears later via /api/spawn/status (async spawn per spec; the protocol table's synchronous shape is not achievable for clones that take minutes).
  • Events persistence: everything except message_update; both live and replayed message_update content is reconstructed from message_end.
  • Repo workspace: clone cache on the daemon filesystem under LVMH_REPO_DIR; the container mounts named volume lvmh-repo-<slug> seeded from that clone on first spawn (protocol: "clone into volume").