Skip to content

Release 2.1.60 (2026-06-23)

Bug fixes

  • enumerate off-platform VMs in update --all and session listing (#1806) Operations that fan out over 'all VMs' via list_vms() saw only Lima instances and silently skipped off-platform boxes (which live in OpenTofu state, not Lima). A running off-platform box got no tooling update on 'vrg-vm update --all' and its live sessions were invisible to 'vrg-vm list --sessions', both with no warning.

Add a shared cross-backend enumerator, _off_platform_vms(), that recovers each off-platform box (identity/org/repo, provider, status) from local tofu state, and route the fan-out call sites through it:

  • update --all now enumerates and reports off-platform boxes instead of silently skipping them, naming the per-box 'vrg-vm update ' command that rebuilds one. It never bulk-rebuilds (a rebuild is destructive, ~8 min, and quota-bound), and reporting is not a failure, so it never affects the exit code.
  • list --sessions queries each running off-platform box's session roster over the IAP transport, the same way Lima boxes are queried over limactl; a query failure degrades to a warning rather than erroring the listing.
  • _cloud_list_rows() (vrg-vm list) is reimplemented on the shared enumerator, with identical output.

A new vm_cloud.off_platform_transport() builds an IAP transport for an already-applied box from purely local state, so the enumerator can reach a running box without composing its full spec.

  • roll back a failed VM apply so an orphan firewall doesn't 409 every retry (#1807) A VM apply creates the global google_compute_firewall.ssh before the zonal instance. When the instance create fails — most often a capacity stockout (#1797) — tofu persists the already-created firewall to vm.tfstate while the apply errors out. The next create then tries to re-create the global firewall and fails with a 409 'already exists' that blocks every retry until the firewall is deleted by hand (#1804).

apply_vm now wraps the apply: on a CalledProcessError it runs a best-effort 'tofu destroy' against the VM state to tear the partial apply (the orphan firewall) down, then re-raises the original error so the operator still sees the real cause. The VM state holds only the firewall and instance — the persistent volume lives in its own state — so the rollback never touches the reusable disk, and a failed create becomes cleanly retryable.

The rollback is best-effort: a destroy failure is suppressed so it can never mask the original apply error.

  • shell-quote provision.env values so multi-token lists source cleanly (#1808) render_provision_env wrote 'KEY=value' unquoted. The provision scripts source the file (. /etc/vergil/provision.env), so a value with shell-significant characters was mis-parsed: EXTRA_PACKAGES='bridge-utils dnsmasq-base ...' was read as the assignment 'EXTRA_PACKAGES=bridge-utils' prefixing the command 'dnsmasq-base ...' (-> command not found, and the var never got the full list); PORT_FORWARDS' '|'/';' and APT_REPOS likewise broke. cloud-init errored.

The vergil-audit box never hit it (minimal spec -> all values empty). The first full lab spec (multi-package, apt repos, port forwards, vagrant plugins) breaks every such line. shlex.quote each value (and VERGIL_USER/HOME). Lima already double-quotes its template params; this brings the cloud writer to parity.

Regression test sources the rendered body under 'set -eu' and asserts each var round-trips to its exact value.