Commit graph

228 commits

Author SHA1 Message Date
f0c1dc924c
kernel catalog: add void-6.12 2026-04-16 16:28:45 -03:00
f0668ee598
Phase 4: remote catalog + banger kernel pull
Introduces the headline feature of the kernel catalog: pulling a kernel
bundle over HTTP without any local build step.

Catalog format (internal/kernelcat/catalog.go):
 - Catalog { Version, Entries } + CatEntry { Name, Distro, Arch,
   KernelVersion, TarballURL, TarballSHA256, SizeBytes, Description }.
 - catalog.json is embedded via go:embed and ships with each banger
   binary. It starts empty (Phase 5's CI pipeline will populate it).
 - Lookup(name) returns the matching entry or os.ErrNotExist.

Fetch (internal/kernelcat/fetch.go):
 - HTTP GET with streaming SHA256 over the response body.
 - zstd-decode (github.com/klauspost/compress/zstd) -> tar extract into
   <kernelsDir>/<name>/.
 - Hardens against path-traversal tarball entries (members whose
   normalised path escapes the target dir, and unsafe symlink
   targets) and sha256-mismatch downloads; any failure removes the
   partially-populated target dir.
 - Regular files, directories, and safe symlinks are supported; other
   tar types (hardlinks, devices, fifos) are silently skipped.
 - After extraction, recomputes sha256 over the on-disk vmlinux and
   writes the manifest with Source="pull:<url>".

Daemon methods (internal/daemon/kernels.go):
 - KernelPull(ctx, {Name, Force}) - lookup in embedded catalog, refuse
   overwrite unless Force, delegate to kernelcat.Fetch.
 - KernelCatalog(ctx) - return the embedded catalog annotated per-entry
   with whether it has been pulled locally.

RPC: kernel.pull, kernel.catalog dispatch cases.

CLI:
 - `banger kernel pull <name> [--force]`.
 - `banger kernel list --available` prints the catalog with a
   pulled/available STATE column and a human-readable size.

Tests: fetch round-trip (extract + manifest + sha256), sha256 mismatch
rejection with cleanup, missing-vmlinux rejection, path-traversal
rejection, HTTP error propagation, catalog parsing, lookup,
pulled-status reconciliation. All 20 packages green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:05:42 -03:00
7192ba24ae
Phase 3: banger kernel import bridges make-*-kernel.sh output
`banger kernel import <name> --from <dir>` copies a staged kernel
bundle into the local catalog. <dir> is the output of
`make void-kernel` or `make alpine-kernel` (build/manual/void-kernel/
or build/manual/alpine-kernel/).

kernelcat.DiscoverPaths locates artifacts under <dir>:
 1. Prefers metadata.json (written by make-void-kernel.sh).
 2. Falls back to globbing: boot/vmlinux-* or vmlinuz-* (Alpine
    fallback), boot/initramfs-*, lib/modules/<latest>.

The daemon's KernelImport copies kernel + optional initrd via
system.CopyFilePreferClone and modules via system.CopyDirContents
(no-sudo mode — catalog lives under ~/.local/state), computes SHA256
over the kernel, and writes the manifest via kernelcat.WriteLocal.

While wiring this up, fixed a latent bug in system.CopyDirContents:
filepath.Join(sourceDir, ".") silently drops the trailing dot, so
`cp -a source source/contents target/` was copying the whole source
directory (including its basename) instead of just its contents.
Replaced the join with a manual "/." suffix. imagemgr.StageBootArtifacts
(the only existing caller) silently benefits.

scripts/register-void-image.sh and scripts/register-alpine-image.sh
are rewritten to use `banger kernel import … && banger image register
--kernel-ref …` instead of the find-and-pass-paths dance. Preserves
the same user-facing commands and env vars.

Tests cover: metadata.json preference, glob fallback, Alpine vmlinuz
fallback, kernel-missing error, round-trip copy into the catalog, and
the --from required flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:53:49 -03:00
48e3a938cf
Phase 2: image register --kernel-ref resolves through the catalog
`banger image register --kernel-ref <name>` now substitutes for the
--kernel/--initrd/--modules triple. The daemon looks the name up via
kernelcat.ReadLocal under d.layout.KernelsDir, populates the three
paths from the resolved entry, then continues through the existing
validate/persist flow unchanged.

Passing both --kernel-ref and any of --kernel/--initrd/--modules is
rejected — at the CLI layer (before starting the daemon) and
defensively at the RPC layer. A missing catalog entry produces a clear
"run 'banger kernel list'" message.

Once registered, the image stores the resolved absolute paths, so
deleting the catalog entry later does not invalidate already-registered
images — managed image build still copies the kernel into its artifact
dir per imagemgr.StageBootArtifacts.

Tests cover: resolution success (absolute KernelPath populated from
catalog), mutual-exclusion rejection, and missing-entry error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:25:50 -03:00
83cc3aee15
Phase 1: local kernel catalog scaffolding
Introduces a read/write kernel catalog on disk without any network
dependency, so later phases (image register --kernel-ref, import, pull)
can build on a working foundation.

Layout: adds KernelsDir to paths.Layout, ensured under
~/.local/state/banger/kernels/. Each cataloged kernel lives at
<KernelsDir>/<name>/ with a manifest.json alongside vmlinux and optional
initrd.img / modules/.

New internal/kernelcat package owns the disk format:
- Entry (Name, Distro, Arch, KernelVersion, SHA256, Source, ImportedAt)
- ValidateName (alphanumeric + dots/hyphens/underscores, no traversal)
- ReadLocal / ListLocal / WriteLocal / DeleteLocal
- SumFile helper

The daemon exposes three RPC methods dispatched in daemon.go:
kernel.list, kernel.show, kernel.delete. Implementations live in a new
internal/daemon/kernels.go and are thin wrappers over kernelcat using
d.layout.KernelsDir.

CLI: new top-level `banger kernel` with list / show / rm subcommands
mirroring the image-command pattern (ensureDaemon, RPC call, table or
JSON output). No sudo required — kernel ops are user-space only.

Users can now manually populate ~/.local/state/banger/kernels/<name>/
and see it via `banger kernel list`. Phase 2 wires --kernel-ref into
image register; Phase 3 adds `banger kernel import`; Phase 4 adds
remote pulls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:21:10 -03:00
ca4865447c
Refresh daemon docs and mark web UI experimental
internal/daemon/doc.go and ARCHITECTURE.md were written before the
subpackage extractions and still referenced old structure (in-progress
phrasing, missing opstate/dmsnap/fcproc/imagemgr/session/workspace,
mentions of opRegistry by its old name). Both now describe the current
shape: composition root + six leaf subpackages, lock ordering rooted
at vmLocks[id], and the one intra-package dependency (workspace →
session for ShellQuote + FormatStepError).

README.md and AGENTS.md mark the local web UI as experimental. It is
still enabled by default at 127.0.0.1:7777, but the docs now state
plainly that its surface is not stable or hardened and not intended for
anything beyond single-user localhost use. AGENTS.md also points at
ARCHITECTURE.md for the subpackage layout.

No code changes; tests still green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:44:11 -03:00
1d51370d26
Extract workspace subpackage with pure repo helpers
Moves the stateless parts of the workspace subsystem into
internal/daemon/workspace:

- RepoSpec struct + InspectRepo for host-side git inspection
- ImportRepoToGuest (taking a minimal GuestClient interface) with the
  full-copy and metadata-only / shallow-overlay paths
- FinalizeScript, PrepareRepoCopy, ResolveSourcePath
- ListSubmodules, ListOverlayPaths, ParsePrepareMode
- Git helpers (GitOutput, GitTrimmedOutput, GitResolvedConfigValue,
  ParseNullSeparatedOutput, RunHostCommand, GitFileURL) and the
  HostCommandOutputFunc test seam
- ShallowFetchDepth const

The subpackage imports internal/daemon/session for ShellQuote and
FormatStepError so both workspace and session pure helpers live in
their own subpackages with a clean session→workspace direction of use.

daemon/workspace.go shrinks from 481 → 156 LOC, keeping just the three
orchestrator methods (Export, Prepare, prepareLocked) that still touch
d.store, d.FindVM, d.dialGuest, d.waitForGuestSSH, and the VM lock set.
guestSessionHostCommandOutputFunc is removed from guest_sessions.go (its
only caller was workspace.go; the new package has its own copy).

All tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:37:19 -03:00
37e02b1576
Extract session subpackage with pure guest-session helpers
Moves the stateless parts of the guest-session subsystem into
internal/daemon/session:

- consts (BackendSSH, attach/transport kinds, StateRoot, LogTailLineDefault)
- StateSnapshot plus ParseState / InspectStateFromDir / ApplyStateSnapshot / StateChanged
- 10 on-guest path helpers (StateDir, StdoutLogPath, StdinPipePath, …)
- 3 bash script generators (Script, InspectScript, SignalScript)
- small utilities (ShellQuote, ExitCode, CloneStringMap, TailFileContent,
  ProcessAlive + syscallKill test seam, FormatStepError)
- launch helpers (DefaultName, DefaultCWD, FailLaunch,
  NormalizeRequiredCommands, CWDPreflightScript, CommandPreflightScript,
  AttachInputCommand, AttachTailCommand, EnvLines)

Callers inside the daemon package import the new package under the
alias "sess" to avoid colliding with the local `session model.GuestSession`
variables threaded through the orchestrator code. guest_sessions.go
shrinks from 616 → 156 LOC; session_stream.go, session_attach.go,
session_lifecycle.go, workspace.go, and guest_sessions_test.go rewire to
the exported names.

The orchestrator methods (StartGuestSession, BeginGuestSessionAttach,
SendToGuestSession, GuestSessionLogs, refresh/inspect, sessionRegistry,
guestSessionController) stay on *Daemon. Full Manager-style extraction
would need prerequisite phases (operation protocol, workdisk helpers),
mirroring Phase 4a's trade-off.

All tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:33:12 -03:00
c13c8b11af
Extract imagemgr subpackage with pure image helpers
Moves the stateless helpers of the image subsystem into
internal/daemon/imagemgr:

paths.go — path validators (ValidateRegisterPaths,
ValidatePromotePaths), artifact staging (StageBootArtifacts,
StageOptionalArtifactPath), metadata (BuildMetadataPackages,
WritePackagesMetadata).

build.go — ResizeRootfs, WriteBuildLog, and the full guest
provisioning script generator (BuildProvisionScript, BuildModulesCommand
and all private script-append helpers) along with the mise/tmux/opencode
version constants.

The orchestrator methods (BuildImage, RegisterImage, PromoteImage,
DeleteImage, runImageBuildNative) stay on *Daemon: they still touch
d.store, d.imageOpsMu, d.beginOperation, capability hooks, and
fcproc-wrapped Daemon helpers — extracting them needs prerequisite
phases (operation protocol, workdisk helpers, tap pool). This commit is
strictly the pure-helper extraction that can land cleanly today.

imagebuild.go shrinks from 453 -> 225 LOC (half gone). images.go shrinks
from 450 -> 374 LOC. imagebuild_test.go updated to call the exported
imagemgr.BuildProvisionScript. Zero behavior change; all tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:24:22 -03:00
6e989914dd
Extract fcproc subpackage for firecracker process helpers
Moves the host-side firecracker primitives — bridge setup, socket dir,
binary resolution, tap creation, socket chown, PID lookup, resolve,
ctrl-alt-del, wait-for-exit, SIGKILL — plus the shared
ErrWaitForExitTimeout sentinel and a small waitForPath helper into
internal/daemon/fcproc.

Manager is stateless beyond its runner + config + logger. The daemon
package keeps thin forwarders (d.ensureBridge, d.createTap, etc.) so no
call site or test changes. A d.fc() helper builds a Manager on demand
from Daemon state, which lets tests keep constructing &Daemon{...}
literals without wiring fcproc explicitly.

This unblocks Phase 4 (imagemgr extraction): imagebuild.go's dependence
on d.createTap/d.firecrackerBinary/etc. can now be satisfied by
importing fcproc instead of reaching back to *Daemon.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:11:39 -03:00
fdab4a7e68
Extract opstate and dmsnap into subpackages
Two leaves of the daemon package that carry no back-references to Daemon
move out:

- internal/daemon/opstate: generic Registry[T AsyncOp]. The AsyncOp
  interface methods are capitalised (ID, IsDone, UpdatedAt, Cancel);
  vmCreateOperationState and imageBuildOperationState implement it.
- internal/daemon/dmsnap: Create, Cleanup, Remove plus the Handles type
  for device-mapper snapshot lifecycle. Takes an explicit Runner
  interface. The daemon-package snapshot.go keeps thin forwarders and a
  type alias so existing call sites and tests are untouched.

Skipped on purpose: tap_pool has too many Daemon-scoped dependencies
(config, store, closing, createTap) for a clean extraction at this
stage; nat.go is already a thin facade over internal/hostnat;
dns_routing.go tests tightly couple to package internals, so extraction
would be more churn than payoff. Each can be revisited when a
subsystem-level refactor forces the boundary.

All tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:02:43 -03:00
59f2766139
Move subsystem state/locks off Daemon into owning types
Daemon no longer owns a coarse mu shared across unrelated concerns.
Each subsystem now carries its own state and lock:

- tapPool: entries, next, and mu move onto a new tapPool struct.
- sessionRegistry: sessionControllers + its mutex move off Daemon.
- opRegistry[T asyncOp]: generic registry collapses the two ad-hoc
  vm-create and image-build operation maps (and their mutexes) into one
  shared type; the Begin/Status/Cancel/Prune methods simplify.
- vmLockSet: the sync.Map of per-VM mutexes moves into its own type;
  lockVMID forwards.
- Daemon.mu splits into imageOpsMu (image-registry mutations) and
  createVMMu (CreateVM serialisation) so image ops and VM creates no
  longer block each other.

Lock ordering collapses to vmLocks[id] -> {createVMMu, imageOpsMu} ->
subsystem-local leaves. doc.go and ARCHITECTURE.md updated.

No behavior change; tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:58:33 -03:00
ea0db1e17e
Split internal/daemon vm.go and guest_sessions.go by concern
vm.go (1529 LOC) splits into vm_create, vm_lifecycle, vm_set, vm_stats,
vm_disk, vm_authsync; firecracker/DNS/helpers stay in vm.go.

guest_sessions.go (1266 LOC) splits into session_controller,
session_lifecycle, session_attach, session_stream; scripts and helpers
stay in guest_sessions.go.

Mechanical move only. No behavior change. Adds doc.go and
ARCHITECTURE.md capturing subsystem map and current lock ordering as
the baseline for the upcoming subsystem extraction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:47:08 -03:00
09590cbaa0
Fix Firecracker PID resolution and deprecated net.Error.Temporary
Use context.Background() for resolveFirecrackerPID so a cancelled
request context (client disconnect) doesn't prevent tracking the
spawned Firecracker process, leaving it orphaned on cleanup.

Drop ne.Temporary() check in accept loop; deprecated since Go 1.18
and unreliable. Retry on any net.Error instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 12:44:38 -03:00
0e764b0571
Fix two daemon bugs: Firecracker context and sessionControllers init
vm.go: Firecracker was launched with context.Background() instead of
the incoming request ctx. A cancelled or timed-out VM creation request
could not stop mid-flight Firecracker process spawning, leaving an
orphaned process and leaked resources. Replace the four firecrackerCtx
uses with ctx directly; the local variable is removed.

guest_sessions.go / daemon.go: sessionControllers map was lazily
initialized with a nil-check inside every mutating method. With d.mu
held this isn't a data race, but the pattern is fragile — any new
method that writes to the map without copying the guard can panic.
Initialize the map once in Open() alongside the other daemon maps and
channels, and remove the redundant nil-checks from setGuestSessionController
and claimGuestSessionController.
2026-04-14 19:53:26 -03:00
43dfda14f8
Fix TOCTOU race in lockVMID
The old pattern held vmLocksMu to get/create a *sync.Mutex, then
released vmLocksMu before calling lock.Lock(). In the gap between
the two operations a concurrent goroutine could observe the entry,
and any future cleanup path that deleted map entries could let a
third goroutine create a fresh *sync.Mutex for the same ID — leaving
two callers holding independent locks with no mutual exclusion.

Fix: replace the manual map + vmLocksMu pair with sync.Map and
LoadOrStore. LoadOrStore is atomic at the map level: exactly one
*sync.Mutex wins for each VM ID, with no release-then-reacquire
gap between the lookup and the insert. vmLocksMu is removed.
2026-04-14 19:50:04 -03:00
ff51b7ce21
workspace.export: add base_commit to capture worker git commits
Without base_commit, export diffs against the current guest HEAD.
If the worker ran git commit inside the VM, HEAD advanced and the
diff came back empty — committed work was silently lost.

With base_commit set to the head_commit from workspace.prepare,
the diff uses that fixed point instead. After git add -A the index
holds the full working state, so git diff --cached <base_commit>
captures everything: committed deltas (HEAD moved past base) and
any uncommitted changes on top, in one patch, applied with the
same git apply flow.

- WorkspaceExportParams gains base_commit
- WorkspaceExportResult echoes back the ref actually used
- CLI gains --base-commit flag
- Tests assert scripts use the caller-supplied ref and that
  omitting it falls back to HEAD
2026-04-14 16:13:05 -03:00
94c353f317
Add guest.session.send and vm.workspace.export RPCs
guest.session.send — write to a pipe-mode session's stdin without
holding the exclusive attach. The daemon dials a fresh SSH connection,
uploads the payload to a temp file, and cats it into the session's
named FIFO. Linux atomicity for writes ≤ PIPE_BUF covers all pi RPC
JSONL lines. Attach exclusivity is unchanged.

vm.workspace.export — pull changes from guest back to host. Runs
`git add -A && git diff --cached HEAD --binary` inside the guest via a
new RunScriptOutput helper on guest.Client (stdout-only capture,
distinct from RunScript which merges stderr). Returns a binary-safe
patch and a list of changed files. CLI writes the patch to stdout for
`| git apply` or to a file via --output.

RunScriptOutput is implemented as a direct SSH session (same pattern as
runSession) rather than going through StartCommand/StreamSession to
avoid closing the underlying Client, which is required since
ExportVMWorkspace calls it twice on the same connection.

New files: internal/daemon/workspace_test.go
2026-04-14 15:21:50 -03:00
797a9de1ce
Install claude and pi through mise
Provisioning was still installing `claude` and `pi` through a separate
npm-global prefix even after the guest images had switched to `mise` for
Node and opencode. That left two competing install paths and made the
runtime layout harder to reason about.

Switch the Debian and Void image setup flows to install `claude` and `pi`
as `mise` npm tools, assert their shims exist after `mise reshim`, and
symlink `node`, `npm`, `opencode`, `claude`, and `pi` directly from the
mise shim directory into `/usr/local/bin`.

Update the imagebuild test expectations and bump the Void rootfs default
size to 4G so the larger default toolset still fits reliably.
2026-04-13 18:29:02 -03:00
5e26fd7544
Fix guest session cwd preflight scripts
Guest session cwd and command preflight helpers were emitting literal
`\\n` separators, so the guest shell saw malformed one-line scripts and
could fail `preflight_cwd` even when `/root/repo` already existed.

Replace those builders with real newlines, and fix the nearby attach
helper commands that were making the same mistake.

Add a small daemon guest-SSH seam so workspace preparation and session
start can share a fake backend in tests, then cover the regression with
an end-to-end daemon test for `PrepareVMWorkspace` followed by
`StartGuestSession` on `/root/repo`.

Validation: `GOCACHE=/tmp/banger-gocache go test ./internal/daemon` and
`GOCACHE=/tmp/banger-gocache go test ./...`.
2026-04-13 18:26:19 -03:00
37c4c091ec
Add guest sessions and agent VM defaults
Add daemon-backed workspace and guest-session primitives so host
orchestrators can prepare /root/repo, launch long-lived guest commands,
and attach to pipe-mode sessions over the local stdio mux bridge.

Persist richer session metadata and launch diagnostics, preflight guest
cwd/command requirements, make pipe-mode attach rehydratable from guest
state after daemon restart, and allow submodules when workspace prepare
runs in full_copy mode.

At the same time, stop vm run from auto-attaching opencode, make it
print next-step commands instead, and make glibc guest images more
agent-ready by installing node, opencode, claude, and pi while syncing
opencode/claude/pi auth files into work disks on VM start.

Validation:
- GOCACHE=/tmp/banger-gocache go test ./...
- make build
- banger vm workspace prepare --help
- banger vm session --help
- banger vm session start --help
- banger vm session attach --help
2026-04-12 23:48:42 -03:00
497e6dca3d
Rename experimental Void image to void
Replace the old `void-exp` repository defaults with `void` so the Make targets,
registration helper, example config, verification messaging, and sample test
fixtures all line up with the new managed image name.

Keep the scope to repo-facing naming only: config overrides, helper output, and
test fixtures now expect `void`, while runtime compatibility for existing local
`void-exp` VMs remains an operational concern outside this commit.

Validation: go test ./..., make build, and a local `banger vm create --image void`
smoke boot with ssh and opencode ports up.
2026-04-01 20:15:28 -03:00
5f89c07fc0
Fix vm run guest repo path and add vm acp bridge
Normalize repo-backed guest checkouts to /root/repo so vm run, attach, and
follow-on guest tooling stop depending on the source repository name.

Add `banger vm acp [--cwd] <vm>` as an SSH stdio bridge to guest `opencode acp`,
defaulting to /root/repo when that checkout exists and falling back to /root.
Update the README and CLI coverage around the fixed guest path and ACP command.

Validation: go test ./internal/cli, go test ./..., make build.
2026-04-01 19:42:00 -03:00
dbc70643c3
Teach VM listing Docker-style aliases and filters
Make `banger ps` a true alias of `banger vm list` and add `banger vm ls`
and `banger vm ps` so the common listing entrypoints all share one path.

Default the shared list command to running VMs only, add `--all` to include
stopped entries, `--latest` to keep only the newest matching VM, and `--quiet`
to print full VM IDs without the table renderer.

Cover the alias wiring plus the running/latest/quiet helpers in CLI tests.
Validation: go test ./internal/cli; GOCACHE=/tmp/banger-gocache go test ./...;
make build; ./build/bin/banger ps --help; ./build/bin/banger vm ls --help.
2026-03-31 13:03:12 -03:00
4813e844e2
Bootstrap vm run tooling before attach
Speed up first use of repo backed VMs by bootstrapping obvious tools before
the best effort LLM harness runs.

Add a host side tooling plan for pinned Go, Node, Python, and Rust versions,
summarize that plan in the uploaded prompt, and run repo mise install plus
guest global mise use -g --pin steps before the bounded opencode inspection.

Keep the harness non fatal, prefer host opencode attach when the client
supports it, fall back to guest opencode over SSH for older clients, and
cover the new flow with CLI plus planner tests.

Validation:
- go test ./internal/cli ./internal/toolingplan
- GOCACHE=/tmp/banger-gocache go test ./...
- make build
2026-03-29 11:38:05 -03:00
1e967140c3
Speed up vm run repo import
Replace the post-boot full-history git bundle path with a shallow repo copy so vm run no longer spends its quiet time shipping and cloning every object in the source repository.

Stage a depth-10 no-checkout clone from the host repo, fetch the requested checkout commit only when it is outside the shallow window, rewrite origin back to the host repo's origin URL, and keep the existing guest checkout plus working-tree overlay behavior.

Add explicit [vm run] progress lines after [vm create] ready so the user can see the SSH wait, shallow repo prep, guest copy, overlay, and opencode attach phases instead of a silent pause.

Validated with GOCACHE=/tmp/banger-gocache go test ./..., make build, and a local payload comparison showing the banger repo dropping from a ~400 MB full bundle to a ~294 KB shallow metadata copy.
2026-03-22 19:45:26 -03:00
42b4a18c63
Sync Git identity into guest VMs
Populate guest /root/.gitconfig from host git config --global during work-disk preparation so plain VM shells can commit.

Resolve user.name and user.email from the source repo for vm run and write them only into the imported checkout, preserving repo-specific identity overrides.

Update mounted guest .gitconfig through a host temp file plus sudo install instead of direct git config --file writes, since the mounted root-owned work disk blocks Git lockfile creation.

Validated with GOCACHE=/tmp/banger-gocache go test ./..., make build, and a live alpine vm create smoke check for guest git config.
2026-03-22 19:30:36 -03:00
f798e1db33
Stamp shared build metadata into banger binaries
Treat `banger`, `bangerd`, and `banger-vsock-agent` as one release by
stamping the same version, commit SHA, and build timestamp into every
binary through a shared ldflag-backed `internal/buildinfo` package.

Add `banger version`, extend daemon ping/status to report the running
daemon's build tuple, and keep the guest helper linked to the same build
metadata without adding a new public version surface for it.

Validate with `GOCACHE=/tmp/banger-gocache go test ./...`, `make build`,
`./build/bin/banger version`, and `./build/bin/banger daemon status`
after the daemon restarts onto the new binary.
2026-03-22 17:14:06 -03:00
ea2db1e868
Configure direct SSH access for .vm hosts
Make daemon startup sync a managed `Host *.vm` block into `~/.ssh/config` so plain `ssh root@<vm>.vm` uses banger's managed key and the same publickey-only options as `banger vm ssh`.

Write the block directly instead of relying on a separate include file so it still applies when a user's SSH config ends inside another `Host` stanza, and remove the legacy managed include path. Add daemon tests that cover fresh config creation and managed-block replacement while preserving user entries.

Validate with `go test ./...`, `make build`, `ssh -G alp.vm`, and `ssh alp.vm true`.
2026-03-22 16:48:42 -03:00
b7f6d1fe1b
Route .vm DNS through systemd-resolved
Banger was already serving VM records on 127.0.0.1:42069, but hosts using systemd-resolved were not routing .vm queries there. That made direct lookups against the local server work while normal host resolution and commands like opencode attach <vm>.vm:4096 failed.\n\nSync resolvectl dns/domain/default-route settings onto the banger bridge when the daemon opens and whenever VM DNS records are published, and revert that bridge-scoped configuration on daemon shutdown. This uses sudo resolvectl because unprivileged resolved reconfiguration on this host requires interactive authentication.\n\nValidation: GOCACHE=/tmp/banger-gocache go test ./..., make build, daemon restart, resolvectl dns/domain br-fc, resolvectl query vrum.vm, and curl http://vrum.vm:4096.
2026-03-22 15:07:22 -03:00
0ad3dae502
Fix vm run guest checkout ownership
Extract the host worktree overlay with tar -o so the guest repo stays owned by root instead of inheriting host UID/GID metadata. That avoids Git's dubious ownership check on /root/<repo> after vm run.\n\nAlso register the guest checkout as a safe.directory during repo setup so opencode and manual git commands can read branch state reliably after attach.\n\nValidation: GOCACHE=/tmp/banger-gocache go test ./... and make build.
2026-03-22 00:58:51 -03:00
2ebc6f99c6
Add repo-backed vm run command
Create a CLI-only banger vm run [path] flow that resolves the enclosing git repository, creates a VM, imports a guest checkout, and launches opencode attach automatically from the host.

Build the guest checkout by bundling git history plus the resolved base and head commits, cloning that bundle in the guest, and overlaying tracked plus untracked non-ignored files over SSH so local working-tree changes carry over. Support guest-only branch creation with --branch and --from, reject bare repos and submodules, and add selective tar helpers plus CLI seams to keep the workflow testable.

Validate with go test ./..., make build, banger vm run --help, and the expected --from requires --branch error path.
2026-03-21 23:34:20 -03:00
8bcc767824
Sync host opencode auth into guest work disks
Refresh guest opencode auth from the host at VM start so guest opencode can reuse the local login without baking secrets into managed images.

Reuse the existing work-disk preparation path to copy ~/.local/share/opencode/auth.json into /root/.local/share/opencode/auth.json with mode 0600, and warn and skip when the host file is missing or unreadable so any existing guest auth stays in place.

Add daemon coverage for copy, replacement, and warn-and-skip cases, document the restart behavior in the README, and validate with go test ./... plus make build. Existing VMs pick the new auth up on their next restart.
2026-03-21 22:36:13 -03:00
786d235f7f
Show image names in vm list and raise default memory
Make `banger vm list` easier to scan by resolving each VM image ID back to the registered image name when that mapping is available, while still falling back to a short ID for unknown images.

Raise the shared default VM memory from 1024 MiB to 2048 MiB so new VMs, CLI help, and daemon-side defaults all align on a 2 GiB baseline.

Add CLI coverage for the image-name rendering path and validate the change with go test ./..., make build, `banger vm list`, and `banger vm create --help`.
2026-03-21 22:21:47 -03:00
3b7e77a2de
Show rootfs sizes in image list
Replace the noisy rootfs path column in `banger image list` with the current rootfs file size so the table is easier to scan.

Render a ROOTFS SIZE column from the on-disk image size, fall back to `-` when the artifact cannot be statted, and keep the existing image summary output unchanged.

Add CLI coverage for both the formatted size case and the missing-file fallback, then rebuild and check the live command output.
2026-03-21 21:56:07 -03:00
14d8563f3c
Stop using kernel IP autoconfig for runtime VMs
Avoid the Alpine boot stall caused by kernel ip= autoconfig running before
virtio_net is available.

Split runtime and image-build boot args so managed VMs boot without kernel
network autoconfig, inject a static guest network config plus bootstrap
script into the runtime overlay, and keep image builds on the old path for
compatibility with existing base images.

Preserve executable bits when patching guest files into ext4 images and add
coverage for the new boot-arg split and guest network config generation.

Validated with go test ./..., a rebuilt Alpine image, and a fresh alp-fast
create/ssh check that brought vm.start down to about 2.7s.
2026-03-21 21:54:18 -03:00
092d848620
Wait for real guest vsock health before opencode
Make vm create wait for the guest-side vsock /healthz endpoint instead of only waiting for the host socket path, so the wait_vsock_agent stage reflects actual guest readiness.

Start banger-vsock-agent earlier in the Alpine OpenRC graph and report later /ports failures as guest-service waits rather than vsock-agent waits, which makes the progress output match what the guest is really doing.

Validate with go test ./..., a rebuilt managed alpine image, and a fresh vm create --image alpine --name alp --nat that now progresses through wait_vsock_agent -> wait_guest_ready -> wait_opencode -> ready.
2026-03-21 21:14:22 -03:00
a166068fab
Add an experimental Alpine image flow
Stage a complete Alpine x86_64 image stack so \	--image alpineworks like the existing manual Void path instead of relying on Debian-oriented image builds.\n\nAdd make targets plus kernel/rootfs/register helpers that download pinned Alpine artifacts, extract a Firecracker-compatible vmlinux, build a matching mkinitfs initramfs, seed OpenRC services, and register/promote a managed image named alpine.\n\nFold in the bring-up fixes discovered during boot validation: use rootfstype=ext4 in shared boot args, install libgcc/libstdc++ for the opencode binary, and give opencode more time to become ready on cold boots.\n\nValidate with go test ./..., the Alpine helper builds, image promotion, and banger vm create --image alpine --name alp --nat plus guest service and port checks.
2026-03-21 20:25:55 -03:00
572bf32424
Remove runtime-bundle image dependencies
Hard-cut banger away from source-checkout runtime bundles as an implicit source of\nimage and host defaults. Managed images now own their full boot set,\nimage build starts from an existing registered image, and daemon startup\nno longer synthesizes a default image from host paths.\n\nResolve Firecracker from PATH or firecracker_bin, make SSH keys config-owned\nwith an auto-managed XDG default, replace the external name generator and\npackage manifests with Go code, and keep the vsock helper as a companion\nbinary instead of a user-managed runtime asset.\n\nUpdate the manual scripts, web/CLI forms, config surface, and docs around\nthe new build/manual flow and explicit image registration semantics.\n\nValidation: GOCACHE=/tmp/banger-gocache go test ./..., bash -n scripts/*.sh,\nand make build.
2026-03-21 18:34:53 -03:00
01c7cb5e65
Reorganize the source checkout layout
Separate tracked source from generated artifacts so the repo root stops accumulating helper scripts, manifests, and local runtime outputs.

Move manual shell entrypoints under scripts/, manifests under config/, and the Firecracker API reference under docs/reference/. Make build and runtimebundle now target build/bin, build/runtime, and build/dist as the canonical source-checkout paths.

Update runtime discovery, helper scripts, tests, and docs to follow the new layout while keeping legacy source-checkout runtime fallbacks for existing local bundles during migration.

Validated with bash -n on the moved scripts, make build, and GOCACHE=/tmp/banger-gocache go test ./....
2026-03-21 17:22:57 -03:00
2362d0ae39
Serve a local web UI from bangerd
Add a localhost-only web console so VM and image management no longer depends on the CLI for every inspection and lifecycle action.

Wire bangerd up to a configurable web listener, expose dashboard and async image-build state through the daemon, and serve CSRF-protected HTML pages with host-path picking, VM/image detail views, logs, ports, and progress polling for long-running operations.

Keep the browser path aligned with the existing sudo and host-owned artifact model: surface sudo readiness, print the web URL in daemon status, and document the new workflow. Polish the UI with resource usage cards, clearer clickable affordances, cancel paths, confirmation prompts, image-name links, and HTTP port links.

Validation: GOCACHE=/tmp/banger-gocache go test ./...
2026-03-21 16:47:47 -03:00
30f0c0b54a
Manage image artifacts and show VM create progress
Stop relying on ad hoc rootfs handling by adding image promotion, managed work-seed fingerprint metadata, and lazy self-healing for older managed images after the first create.

Rebuild guest images with baked SSH access, a guest NIC bootstrap, and default opencode services, and add the staged Void kernel/initramfs/modules workflow so void-exp uses a matching Void boot stack.

Replace the opaque blocking vm.create RPC with a begin/status flow that prints live stages in the CLI while still waiting for vsock health and opencode on guest port 4096.

Validate with GOCACHE=/tmp/banger-gocache go test ./... and live void-exp create/delete smoke runs.
2026-03-21 14:48:01 -03:00
9f09b0d25c
Show vm create defaults in CLI help
Expose the static vm create CPU, memory, system overlay, and work disk defaults at the Cobra flag declaration layer so banger vm create --help shows the values users get by default.

Keep the daemon as the fallback source of truth by only sending those fields in VMCreateParams when the user actually changes the flags. This preserves existing RPC behavior for omitted values while improving the CLI UX.

Add CLI coverage for displayed defaults and for unchanged versus changed flag propagation. Verified with GOCACHE=/tmp/banger-gocache go test ./... and go run ./cmd/banger vm create --help.
2026-03-20 17:40:52 -03:00
0c80d03081
Remove the banger TUI
Hard cut the terminal UI so the supported management surface is the daemon-backed CLI only.

Drop the tui subcommand, delete the Bubble Tea implementation and its tests, and keep a regression check that the legacy command is rejected.
Prune the Charmbracelet dependencies with go mod tidy and remove the stale README and AGENTS references.

Validated with go test ./... and GOCACHE=/tmp/banger-gocache go test ./internal/cli.
2026-03-19 22:56:16 -03:00
3096de0a7f
Refine vm ports output
Make banger vm ports single-target and collapse the old VM/WEB table shape into a simpler PROTO ENDPOINT PROCESS COMMAND view. Web listeners now surface directly as http or https, with clickable endpoints in the main endpoint column instead of a separate URL field.

Classify TCP listeners with HTTPS-first probing so TLS services are not mislabeled as plain HTTP just because they answer bad cleartext requests with an HTTP error, then dedupe rows by rendered PROTO+ENDPOINT so dual-stack binds like 0.0.0.0 and :: only show once.

Update the CLI/daemon regressions and README to match the new contract. Verified with GOCACHE=/tmp/banger-gocache go test ./..., make build, git diff --check, and ./banger vm ports --help.
2026-03-19 18:21:04 -03:00
c298ed2fc1
Add vsock-backed VM port inspection
Let the host ask the guest vsock agent to run ss so open ports can be surfaced without SSHing in manually.

Add a narrow /ports agent endpoint, a daemon vm.ports RPC that enriches listeners with <hostname>.vm endpoints and best-effort HTTP links, and a concurrent 'banger vm ports' CLI table for one or more VMs.

Update the guest package contract to include ss for rebuilt Debian images, allow the guest agent package in the shell-out policy, and cover the new parsing/RPC/CLI flow in tests.

Verified with GOCACHE=/tmp/banger-gocache go test ./... outside the sandbox, make build, bash -n customize.sh make-rootfs-void.sh verify.sh, and ./banger vm ports --help.
2026-03-19 15:52:11 -03:00
3ed78fdcfc
Add experimental Void guest workflow and vsock agent
Make iterating on a Firecracker-friendly Void guest practical without replacing the Debian default image path.

Add local Void rootfs build/register/verify plumbing, a language-agnostic dev package baseline, and guest SSH/work-disk hardening so new images use the runtime bundle key, keep a normal root bash environment, and repair stale nested /root layouts on restart.

Replace the guest PING/PONG responder with an HTTP /healthz agent over vsock, rename the runtime bundle and config surface from ping helper to agent while still accepting the legacy keys, and route the post-SSH reminder through the new vm.health path.

Validated with GOCACHE=/tmp/banger-gocache go test ./..., make build, bash -n customize.sh make-rootfs-void.sh, and git diff --check.
2026-03-19 14:51:25 -03:00
c8d9a122f9
Speed up VM create with work seeds
Beat VM create wall time without changing VM semantics.

Generate a work-seed ext4 sidecar during image builds and rootfs rebuilds, then clone and resize that seed for each new VM instead of rebuilding /root from scratch. Plumb the new seed artifact through config, runtime metadata, store state, runtime-bundle defaults, doctor checks, and default-image reconciliation so older images still fall back cleanly.

Add a daemon TAP pool to keep idle bridge-attached devices warm, expose stage timing in lifecycle logs, add a create/SSH benchmark script plus Make target, and teach verify.sh that tap-pool-* devices are reusable capacity rather than cleanup leaks.

Validated with go test ./..., make build, ./verify.sh, and make bench-create ARGS="--runs 2".
2026-03-18 21:22:12 -03:00
a14a80fd6b
Harden VM delete cleanup and SQLite settings
Multi-VM delete exposed two separate regressions: NAT teardown was still running after stopped VMs had already dropped their tap metadata, and the store was relying on one-off SQLite pragmas instead of configuring every pooled connection.

Skip NAT cleanup when the runtime no longer has the network handles needed to identify rules, and move the SQLite profile into the DSN so WAL, busy timeouts, foreign keys, and the other connection-scoped settings apply consistently across the pool. Keep the write mutex in place for concurrent mutations, and update the daemon/store tests to use valid image fixtures now that foreign key enforcement is real.

Validated with go test ./... and make build.
2026-03-18 20:39:31 -03:00
08ef706e3f
Add vsock-backed SSH session reminders
Remind users when a VM is still running after 	hanger vm ssh exits instead of silently dropping them back to the host shell.\n\nAttach a Firecracker vsock device to each VM, persist the host vsock path/CID,\nadd a new guest-side banger-vsock-pingd responder to the runtime bundle and both\nimage-build paths, and expose a vm.ping RPC that the CLI and TUI call after SSH\nreturns. Doctor and start/build preflight now validate the helper plus\n/dev/vhost-vsock so the feature fails early and clearly.\n\nValidated with go mod tidy, bash -n customize.sh, git diff --check, make build,\nand GOCACHE=/tmp/banger-gocache go test ./... outside the sandbox because the\ndaemon tests need real Unix/UDP sockets. Rebuild the image/rootfs used for new\nVMs so the guest ping service is present.
2026-03-18 20:14:51 -03:00