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

@ -25,6 +25,7 @@ type migration struct {
var migrations = []migration{
{id: 1, name: "baseline", up: migrateBaseline},
{id: 2, name: "drop_images_docker", up: migrateDropImagesDocker},
{id: 3, name: "add_vm_workspace", up: migrateAddVMWorkspace},
}
// runMigrations ensures schema_migrations exists, then applies every
@ -152,3 +153,14 @@ func migrateDropImagesDocker(tx *sql.Tx) error {
_, err := tx.Exec(`ALTER TABLE images DROP COLUMN docker;`)
return err
}
// migrateAddVMWorkspace adds the workspace_json column that records
// the last workspace.prepare result (guest path, host source path,
// HEAD commit, and timestamp) per VM. Default '{}' means no workspace
// has been prepared yet. The column is managed exclusively via
// Store.SetVMWorkspace; lifecycle UpsertVM calls never touch it so
// workspace state survives VM stop/start cycles.
func migrateAddVMWorkspace(tx *sql.Tx) error {
_, err := tx.Exec(`ALTER TABLE vms ADD COLUMN workspace_json TEXT NOT NULL DEFAULT '{}'`)
return err
}