feat(vm): add vm exec command with workspace dirty detection

Introduces three interconnected features for persistent VM workflows:

1. `banger vm exec <vm> -- <cmd>`: runs a command in the prepared
   workspace, automatically cd-ing into the guest path and wrapping
   via `mise exec --` so mise-managed tools are on PATH. Falls back
   to a plain exec when mise isn't available. Exit code propagates
   verbatim.

2. Workspace persistence: workspace.prepare now stores the guest path,
   host source path, and HEAD commit into a new `workspace_json` column
   on the vms table (migration 3). This state survives daemon restarts
   and informs both dirty-checking and auto-prepare.

3. Dirty detection: `vm exec` compares the stored HEAD commit against
   the current host repo HEAD. When stale it warns and, with
   --auto-prepare, re-syncs the workspace before running.

Also:
- WORKSPACE column added to `banger ps` / `vm list`
- `banger vm` quick reference updated with `vm exec` entry
This commit is contained in:
Thales Maciel 2026-04-26 23:53:45 -03:00
parent c8637b0fe4
commit d59425adb9
No known key found for this signature in database
GPG key ID: 33112E6833C34679
8 changed files with 260 additions and 13 deletions

View file

@ -144,7 +144,8 @@ type VMRecord struct {
LastTouchedAt time.Time `json:"last_touched_at"`
Spec VMSpec `json:"spec"`
Runtime VMRuntime `json:"runtime"`
Stats VMStats `json:"stats"`
Stats VMStats `json:"stats"`
Workspace VMWorkspace `json:"workspace"`
}
type VMCreateRequest struct {
@ -166,6 +167,18 @@ type VMSetRequest struct {
NATEnabled *bool
}
// VMWorkspace records the last successful workspace.prepare result on
// a VM so callers can skip re-stating the source path on every exec
// and so banger can detect drift between the guest copy and the host
// repo. Stored as workspace_json in the vms table; zero value means
// no workspace has been prepared on this VM yet.
type VMWorkspace struct {
GuestPath string `json:"guest_path,omitempty"`
SourcePath string `json:"source_path,omitempty"`
HeadCommit string `json:"head_commit,omitempty"`
PreparedAt time.Time `json:"prepared_at,omitempty"`
}
type WorkspacePrepareMode string
const (