The `image build` flow spun up a transient Firecracker VM, SSHed in, and ran a large bash provisioning script to derive a new managed image from an existing one. It overlapped heavily with the golden- image Dockerfile flow (same mise/docker/tmux/opencode install logic duplicated in Go as `imagemgr.BuildProvisionScript`) and had far more machinery: async op state, RPC begin/status/cancel, webui form + operation page, preflight checks, API types, tests. For custom images, writing a Dockerfile is simpler and more reproducible. Removed end-to-end: - CLI `image build` subcommand + `absolutizeImageBuildPaths`. - Daemon: BuildImage method, imagebuild.go (transient-VM orchestration), image_build_ops.go (async begin/status/cancel), imagemgr/build.go (the 247-line provisioning script generator and all its append* helpers), validateImageBuildPrereqs + addImageBuildPrereqs. - RPC dispatches for image.build / .begin / .status / .cancel. - opstate registry `imageBuildOps`, daemon seam `imageBuild`, background pruner call. - API types: ImageBuildParams, ImageBuildOperation, ImageBuildBeginResult, ImageBuildStatusParams, ImageBuildStatusResult; model type ImageBuildRequest. - Web UI: Backend interface methods, handlers, form, routes, template branches (images.html build form, operation.html build branch, dashboard.html Build button). - Tests that directly exercised BuildImage. Doctor polish (task C): - Drop the "image build" preflight section entirely (its raison d'être is gone). - Default-image check now accepts "not local but in imagecat" as OK: vm create auto-pulls on first use. Only flag when the image is neither locally registered nor in the catalog. Net: 24 files touched, 1,373 lines deleted, 25 added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.9 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.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,webListener,webServer,webURL,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] → {createVMMu, imageOpsMu} → subsystem-local locks
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.
Web UI
The optional web UI served at web_listen_addr is experimental. It is
enabled by default for local observability but is not considered a stable
or supported interface. Set web_listen_addr = "" in config to disable.