0.41.0
VM hibernation
The biggest change in this release: avocado vm now hibernates an idle VM instead of burning host CPU, and wakes it transparently on the next connection.
Wake-on-connect supervisor
A long-lived avocado vm supervise process is spawned alongside QEMU. It owns the user-facing SSH port and docker socket (QEMU's host-forward moves to a loopback-only internal port) and:
- Proxies inbound SSH/docker connections to QEMU, sending QMP
contto resume a paused guest on accept. - Tracks active connections and an idle timer; after
idle.hibernate_after_secswith no activity, sends QMPstop. Host CPU on QEMU drops to ~0% while RAM stays resident, and any subsequent SSH or docker call wakes it.
The default idle timeout is 60 seconds. Override it with avocado vm config set idle.hibernate_after_secs N or AVOCADO_VM_IDLE_HIBERNATE_SECS.
PSCI idle-states for lower idle CPU
QEMU's -machine virt doesn't emit cpu-idle-states device-tree bindings, so under HVF idle vCPUs fell back to bare WFI and bounced through vmexit/vmenter — roughly 80% host CPU per vCPU at guest idle. On arm64 launches the CLI now splices PSCI idle-states into the machine DTB (pure-Rust FDT parsing, no dtc dependency) and caches the patched copy under ~/.avocado/vm/dtb/. Measured at smp=8 idle: 670% → 275–344% host CPU. The DTB cache key also moved to the QEMU binary mtime, saving ~300–500 ms per VM start.
Status and telemetry
avocado vm statusreports hibernation — surfaces(hibernated — wakes on next ssh/docker call)when the guest is paused. The machine-readable status gains a tri-statepausedfield (true/false/ unknown).- Infrastructure lane for telemetry — new
--infra-portand--docker-socket-streamlanes are proxied like the user-facing ports but do not count toward the idle timer or wake a paused VM. This lets background telemetry (e.g. the desktop app's agent tunnel) observe the VM without pinning it awake or respawn-waking it, so hibernation actually sticks. - USB re-attach on wake — resuming a paused VM fires a
vm.notify.wokehint so the desktop can re-attach USB devices that were dropped while the guest was frozen.
Reproducible builds
Channel snapshot pinning
Each target is now pinned to an immutable, point-in-time snapshot of its feed channel, recorded in the lock file. A clean and rebuild reproduces exactly, even after the live channel head advances or evicts the package versions the lock referenced.
- Lock file v7 adds a per-target
repo-snapshot. It's additive — v6 lock files read as "track head" and keep working. - On first fetch a target auto-pins to the channel's latest snapshot; pins freeze all sysroots together via
AVOCADO_RELEASEVERwith no per-call-site plumbing. avocado updateadvances the snapshot pin to the newest and clears package/kernel pins so the next install re-resolves and re-locks (Cargo-style move-forward).- If a pinned snapshot has been garbage-collected, you get an actionable "run
avocado update" error instead of an opaque dnf failure.
Auto-pinning now also works for projects that rely on the baked-in default feed (no explicit distro.repo.url), and the default repo URL is single-sourced (Config::effective_repo_url()).
Top-level permissions: block
Users and groups are now declared in a top-level permissions: map and referenced by name from rootfs.<name>.permissions / initramfs.<name>.permissions (or inlined), instead of being buried inside a single extension:
permissions:
dev:
users: [...]
groups: [...]
rootfs:
default:
permissions: dev
This puts identity provisioning at the image layer, where one coherent passwd/shadow/group makes sense, and lets the same block be reused across rootfs and initramfs. When no permissions: is set on an image, no script section is emitted and the base packages' files are left untouched.
Deprecation: extensions that declare
users:/groups:still work but now emit a deprecation warning. That path will be removed in a future release — move identity definitions to a top-levelpermissions:block.
avocado init now scaffolds new projects with this layout (a dev permissions profile referenced by explicit rootfs: / initramfs: blocks) and stamps the generated cli_requirement to >=0.41.0.
Private package feeds: custom CA / TLS
distro.repo.ca and distro.repo.tls_verify (overridable via AVOCADO_REPO_CA / AVOCADO_REPO_INSECURE) let the SDK trust a self-signed or private-CA package endpoint. The setting is applied across every dnf phase — SDK bootstrap, SDK packages, extensions, runtime, rootfs, initramfs, and the per-module dnf subcommands — for both host and target repo configs.
Build caching: per-step input hashes
Build stamps previously shared one input hash across ext install/build/image and another across runtime install/build, so editing a build-only field (e.g. an extension's image: args or a runtime's post_build:) over-invalidated the install stamp and cascaded into unnecessary rebuilds. Each step now hashes exactly the inputs it consumes, the kernel: block is hashed narrowly so cosmetic edits don't invalidate unrelated stamps, and post_build / post_install hooks now hash script contents (not just the path). STAMP_VERSION bumped 1 → 2; existing stamps invalidate once on first run after upgrade, then the narrower hashing applies.
Connect
avocado deployon macOS — deploys now work on macOS. The TUF repo HTTP server runs inside the slirp-NAT'davocado-vmand was unreachable by the target device; the CLI now opens a per-deploy QMP host-forward and advertises the host's LAN IP so the device can fetch the repo. Validated end-to-end to a LAN Raspberry Pi 4.- Connect-signed deploy and runtime listing — adds Connect runtime listing and support for connect-signed deploys, with output tuned for desktop integration.
- Org selection in
connect auth login—--org <id>scopes a new token non-interactively; multi-org users get an interactive picker, single-org users skip it. Org-scoped commands also fall back toconnect.orginavocado.yaml, then the active profile'sorganization_id, before erroring. avocado connect ext(super-admin) — build-once publish of a packaged extension RPM to the feed, plusstatusandlistof published versions. The publish feed is now derived from your distro config (get_distro_release()/get_distro_channel()) rather than separate--target-release/--target-channelflags, so build and publish share one source of truth.- Target-aware extension packaging —
avocado ext packagestamps aProvides: avocado-target(<machine>)persupported_targetsentry (wildcard when unset). This lands in the feed metadata so target compatibility can be queried without downloading the RPM, andconnect ext publishderives its target list fromsupported_targetsinstead of a hardcoded default. - Nested extension layout — packaged extensions can nest content under
/<ext_name>/and self-describe viaProvides: avocado-ext-layout(nested), installing into a sharedincludesinstallroot so one rpmdb tracks every extension with no cross-extension file collisions. Legacy packages keep the per-extension installroot; final content lands atincludes/<ext_name>/either way, so consumers are unchanged.
Other changes
avocado initdrops the channel suffix from the generated SDK image tag — it now pins only the distro release (docker.io/avocadolinux/sdk:{release}).- Cross-platform groundwork — the hibernation supervisor, QMP/QGA modules, and deploy port-forwarding are now
cfg(unix)-gated so a Windows build compiles. On Windows the hibernation feature is unavailable (QEMU binds the user-facing port directly and the VM never auto-pauses).
Bug fixes
- Root login no longer silently breaks when a runtime has no explicit
rootfs:/initramfs:reference.runtime buildnow falls back to the default rootfs/initramfs for permissions resolution, so the root user's shadow entry is rewritten and the image's/etc/shadowcarries the configured (empty) password instead of the inheritedroot:*:…. - TUI install rendering fix — the unset-environment-variable warning for
{{ env.VAR }}interpolation now routes through the output module instead of a raweprintln!, fixing stranded/stacked spinner lines during installs that fetch remote extensions.