The web UI shipped as "experimental" and was never finished — no nav
off the dashboard, no live updates, no settled design, never a
supported surface. It was opt-in by default already; leaving the code
in the tree for v0.1.0 only invited "does this work?" questions and
kept HostSummary/BangerSummary/SudoStatus types on the public RPC
surface that nothing else uses.
Removed:
internal/webui/ (all Go + templates + assets)
internal/daemon/web.go (server start / Layout / Config / ListVMs / ListImages)
internal/daemon/dashboard.go (DashboardSummary aggregator)
Simplified:
internal/api/types.go drop WebURL on PingResult, drop
HostSummary / SudoStatus / BangerSummary /
DashboardSummary / DashboardSummaryResult
internal/model/types.go drop DaemonConfig.WebListenAddr
internal/config/config.go drop web_listen_addr from fileConfig + Load
internal/daemon/daemon.go drop webListener / webServer / webURL fields +
startWebServer() call + ping WebURL population
internal/cli/banger.go `daemon status` output no longer branches on web
internal/daemon/{doc.go,ARCHITECTURE.md} drop web UI sections
README.md drop web_listen_addr config bullet + security paragraph
Tests updated to reflect the new shape. Coverage 57.3 -> 58.9% (the
webui package was largely untested; its removal lifts the ratio
without moving the numerator). `banger daemon status` output and
--help are web-free. Lint + full suite green.
4.7 KiB
internal/daemon architecture
This document describes the current daemon package layout: the Daemon
composition root, the subpackages that own stateless helpers and shared
primitives, and the lock ordering every caller must respect.
Composition
Daemon is the composition root. Subsystem state and locks live on their
owning types:
- Layout, config, store, runner, logger, pid — infrastructure handles.
vmLocks vmLockSet— per-VM*sync.Mutex, one per VM ID. Held only across short, synchronous state validation and DB mutations so slow guest I/O does not block lifecycle ops on the same VM.workspaceLocks vmLockSet— per-VM mutex scoped toworkspace.prepare/workspace.export. Serialises concurrent workspace operations on a single VM (two simultaneous tar imports would clobber each other) without touchingvmLocks, sovm stop/delete/restartnever queue behind a slow import.handles *handleCache— in-memory map of per-VM transient kernel/ process handles (PID, tap device, loop devices, DM target). The cache is rebuildable: each VM directory holds a smallhandles.jsonscratch file that the daemon reads at startup to reconstruct the cache and verify processes against/procvia pgrep. Nothing in the durablevmsSQLite row describes transient kernel state. Seeinternal/daemon/vm_handles.go.createVMMu sync.Mutex— serialisesCreateVM(guards name uniqueness- guest IP allocation window).
imageOpsMu sync.Mutex— serialises image-registry mutations (PullImage,RegisterImage,PromoteImage,DeleteImage).createOps opstate.Registry[*vmCreateOperationState]— in-flight VM create operations; owns its own lock.tapPool tapPool— TAP interface pool; owns its own lock.sessions sessionRegistry— active guest session controllers; owns its own lock.listener,vmDNS— networking.vmCaps— registered VM capability hooks.pullAndFlatten,finalizePulledRootfs,bundleFetch,requestHandler,guestWaitForSSH,guestDial,waitForGuestSessionReady— injectable seams used by tests.
Subpackages
Pure helpers have moved into subpackages so the daemon package itself stays
focused on orchestration. Each subpackage takes explicit dependencies
(typically a system.Runner-compatible interface) and holds no global
state beyond small test seams.
| Subpackage | Purpose |
|---|---|
internal/daemon/opstate |
Generic Registry[T AsyncOp] for async-operation bookkeeping. |
internal/daemon/dmsnap |
Device-mapper COW snapshot create/cleanup/remove. |
internal/daemon/fcproc |
Firecracker process primitives (bridge, tap, binary, PID, kill, wait). |
internal/daemon/imagemgr |
Image subsystem pure helpers: validators, staging, build script gen. |
internal/daemon/session |
Guest-session helpers: state paths, scripts, parsing, utilities. |
internal/daemon/workspace |
Workspace helpers: git inspection, copy prep, guest import script. |
workspace imports session for ShellQuote and FormatStepError; all
other subpackages are leaves (no other intra-daemon subpackage imports).
Lock ordering
Acquire in this order, release in reverse. Never acquire in the opposite direction.
vmLocks[id] → workspaceLocks[id] → {createVMMu, imageOpsMu} → subsystem-local locks
vmLocks[id] and workspaceLocks[id] are NEVER held at the same
time. workspace.prepare acquires vmLocks[id] just long enough to
validate VM state, releases it, then acquires workspaceLocks[id]
for the guest I/O phase.
Subsystem-local locks (tapPool.mu, sessionRegistry.mu,
opstate.Registry mu, guestSessionController.attachMu /
writeMu) are leaves. They do not contend with each other.
Notes:
vmLocks[id]is the outer lock for any operation scoped to a single VM. Acquired viawithVMLockByID/withVMLockByRef.createVMMuandimageOpsMuare narrow: each guards one family of mutations and is released before any blocking guest I/O.- Holding a subsystem-local lock while calling into guest SSH is discouraged; copy needed state out under the lock and release before blocking I/O.
External API
Only internal/cli imports this package. The surface is:
daemon.Open(ctx) (*Daemon, error)(*Daemon).Serve(ctx) error(*Daemon).Close() errordaemon.Doctor(...)— host diagnostics (no receiver).
All other *Daemon methods are reached only through the RPC dispatch
switch in daemon.go and are free to move/rename during refactoring.