Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/docs-changelog/june-2026/0.41.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: '0.41.0'
description: 'Avocado CLI 0.41.0 release notes.'
---

## 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 `cont` to resume a paused guest on accept.
- Tracks active connections and an idle timer; after `idle.hibernate_after_secs` with no activity, sends QMP `stop`. 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 status` reports hibernation** — surfaces `(hibernated — wakes on next ssh/docker call)` when the guest is paused. The machine-readable status gains a tri-state `paused` field (`true` / `false` / unknown).
- **Infrastructure lane for telemetry** — new `--infra-port` and `--docker-socket-stream` lanes 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.woke` hint 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_RELEASEVER` with no per-call-site plumbing.
- `avocado update` advances 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:

```yaml
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-level `permissions:` 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 deploy` on macOS** — deploys now work on macOS. The TUF repo HTTP server runs inside the slirp-NAT'd `avocado-vm` and 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 to `connect.org` in `avocado.yaml`, then the active profile's `organization_id`, before erroring.
- **`avocado connect ext` (super-admin)** — build-once publish of a packaged extension RPM to the feed, plus `status` and `list` of published versions. The publish feed is now derived from your distro config (`get_distro_release()` / `get_distro_channel()`) rather than separate `--target-release` / `--target-channel` flags, so build and publish share one source of truth.
- **Target-aware extension packaging** — `avocado ext package` stamps a `Provides: avocado-target(<machine>)` per `supported_targets` entry (wildcard when unset). This lands in the feed metadata so target compatibility can be queried without downloading the RPM, and `connect ext publish` derives its target list from `supported_targets` instead of a hardcoded default.
- **Nested extension layout** — packaged extensions can nest content under `/<ext_name>/` and self-describe via `Provides: avocado-ext-layout(nested)`, installing into a shared `includes` installroot so one rpmdb tracks every extension with no cross-extension file collisions. Legacy packages keep the per-extension installroot; final content lands at `includes/<ext_name>/` either way, so consumers are unchanged.

---

## Other changes

- **`avocado init`** drops 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 build` now falls back to the default rootfs/initramfs for permissions resolution, so the root user's shadow entry is rewritten and the image's `/etc/shadow` carries the configured (empty) password instead of the inherited `root:*:…`.
- **TUI install rendering fix** — the unset-environment-variable warning for `{{ env.VAR }}` interpolation now routes through the output module instead of a raw `eprintln!`, fixing stranded/stacked spinner lines during installs that fetch remote extensions.
4 changes: 4 additions & 0 deletions src/docs-changelog/latest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ title: 'Latest'
description: 'The latest Avocado CLI release.'
displayed_sidebar: changelog
---

import { Redirect } from '@docusaurus/router'

<Redirect to="/changelog/june-2026/0.41.0" />
Comment thread
nicksinas marked this conversation as resolved.
133 changes: 133 additions & 0 deletions src/docs-changelog/may-2026/0.39.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: '0.39.0'
description: 'Avocado CLI 0.39.0 release notes.'
---

## New commands

### `avocado vm`

On macOS and Windows, the CLI now drives a single user-level QEMU guest that hosts its own `dockerd` and a 9p mount of the user's workspace. Flows that need Docker (`provision`, `ext build`, `sdk run`) work without Docker Desktop installed and without the developer having to manage QEMU manually:

```bash
avocado vm start # launch / resume the helper VM
avocado vm stop # graceful shutdown
avocado vm status # show running state and SSH port
avocado vm shell # interactive shell inside the guest
avocado vm logs # tail guest logs
avocado vm rebuild # rebuild the var.btrfs from scratch
```

State lives under `~/.avocado/vm/` (pidfile, SSH port, QMP/QGA/control sockets, `var.btrfs`, generated SSH config and key). Subcommands that need Docker auto-flip `DOCKER_HOST` to the SSH-forwarded socket inside the guest, so `docker run` calls land in the VM transparently. Opt out with `--no-vm-auto-start`, `AVOCADO_VM_AUTO_START=0`, or `--runs-on`.

### `avocado vm update`

Polls the configured channel for a newer VM release, downloads any artifacts whose SHA differs from the installed copy, and atomic-swaps them into `~/.avocado/vm/install/`. Stops the VM during the swap and restarts it if it was running:

```bash
avocado vm update --check # check availability without downloading
avocado vm update -y # skip interactive confirmation
avocado vm update --channel beta
avocado vm update --output json
```

User state in the persistent `var.btrfs` survives version bumps. Artifacts marked `update_policy: seed_only` (currently `var`) are downloaded only on first install and never overwritten.

### `avocado vm reset`

Wipe the persistent `var.btrfs` and re-seed from the installed `var` artifact:

```bash
avocado vm reset
```

This drops Docker volumes, container caches, project work under `/data`, and `/etc/machine-id`. Interactive confirmation requires typing `reset` (not just `y`) because the operation is irreversible. `-y/--yes` for scripted use.

### `avocado completion`

Generates shell tab-completion scripts. Candidate sets for `--extension`, `--runtime`, `--target`, and signing-key positionals are computed dynamically at TAB time, reading the current `avocado.yaml` and the local signing-key registry — so completions stay live as the project changes:

```bash
avocado completion bash > /etc/bash_completion.d/avocado
avocado completion zsh > "${fpath[1]}/_avocado"
avocado completion fish > ~/.config/fish/completions/avocado.fish
```

`elvish` and `powershell` are also supported.

### `avocado provision --list`

Discovers the available provisioning profiles for the resolved target by reading the stone manifest inside the SDK volume:

```bash
avocado provision --list --output json
```

The JSON output names each profile, its script, the declared default, and per-profile field schemas (name, type, label, description, required flag, default) for desktop tooling that needs to render dynamic provisioning forms keyed off the manifest. Returns `available: false` with a reason when the SDK isn't installed yet or the target has no manifest — so callers can distinguish "no profiles yet" from "command failed" without parsing exit codes.

---

## New features

### `--output json` across the lifecycle

Every command the macOS desktop app shells out to now supports `--output json`, emitting newline-delimited JSON events instead of TUI prose. JSON mode also auto-enables `--force` semantics so DNF gets `-y` and Docker drops `-it`:

- `avocado init --output json` — `{event: info, …}` per step, terminal `{event: complete, …}` or `{event: error, …}`. With `--reference X --name myproj`, writes into `<directory>/<myproj>/` rather than `<directory>/`.
- `avocado config show --output json` — narrow projection of `avocado.yaml` (distro release/channel, default_target, supported_targets as `"*"` or list, runtimes, provision_profiles, src_dir, plus a `connect: { org, project }` mirror).
- `avocado install / build / provision --output json` — NDJSON `step` events per task lifecycle transition (`task_registered`, `step` with `running` / `success` / `failed`, `step_error`, `output`).
- `avocado connect auth login --output json` — `login_url`, `info`, `complete`, `error` events so a UI can drive the browser handshake. `status` and `logout` emit single objects.
- `avocado connect projects create --output json` — single-shot `{ org, id, name }` so callers can persist the new project ID immediately.

### `post_build` and `post_install` hooks

Extensions and runtimes can now declare a `post_build` script that runs as the final step of `avocado ext build` and `avocado runtime build`:

```yaml
extensions:
my-ext:
post_build: scripts/post-build.sh

runtimes:
dev:
post_build: scripts/post-build.sh
```

For extensions the hook fires after the sysroot is populated but **before** the sysext / confext `.raw` images are sealed, so the script can mutate `$AVOCADO_BUILD_EXT_SYSROOT` and have changes baked into the image (e.g. generating `/etc/ssl/certs/ca-certificates.crt` from the installed `ca-certificates` package). For runtimes the hook fires after the runtime image is built.

Extension scripts resolve relative to the extension's source directory (`/opt/src` for local, `$AVOCADO_PREFIX/includes/<ext>/` for remote), so remote extensions can ship their own hooks. The `post_build` field is hashed into the build stamp; adding, removing, or renaming the hook re-triggers a build. Script contents are not hashed — editing in place still needs `--no-stamps` to force a re-run.

`rootfs.post_install` and `initramfs.post_install` accept the same shape and run before the image (e.g. EROFS) is generated, so the script can mutate the sysroot before sealing.

### Parallel extension build for `avocado build -r <runtime>`

`avocado build` without `-r` already parallelized extension builds via the task scheduler. The `-r <runtime>` form was taking an early-return path that ran extensions through a serial for-loop. That early return is gone — extensions for the requested runtime now build concurrently, same as the no-flag path.

### Apache-2.0 license + Homebrew tap

The CLI is now Apache-2.0 licensed (was MIT) — aligning with the rest of the avocado-linux org and what Homebrew core requires. Releases now publish:

- Per-tarball `.sha256` sidecars alongside each asset.
- A consolidated `SHA256SUMS` file on each release.
- An automated dispatch to `avocado-linux/homebrew-tap` on stable tags.

`avocado upgrade` and the `[UPDATE]` notice now detect Homebrew-installed binaries and suggest `brew upgrade avocado-cli` instead of self-update, so a subsequent `brew upgrade` doesn't silently revert the binary.

### Container tool plumbing

`container_tool` is now threaded through the volume + image copy paths (`copy_volume_path_to_host`, `VolumeManager`, per-command `copy_rpm_to_host` helpers) instead of hardcoded `docker`. Podman, nerdctl, and other Docker-compatible runtimes work consistently across `ext` and `sdk` image + package flows.

### Variable `var` partition size

The build script now measures the freshly built `var.btrfs` and forwards the size to `stone bundle` via `--partition-size`. Manifests no longer need a hand-picked `size` for the last expandable partition — stone 2.2.0's omitted-size behavior takes over, rounded up to the partition's `size_alignment`.

### Overlay: `cp -a` replaces `rsync`

Overlay merge steps now use `cp -a` instead of `rsync`, removing the dependency on rsync being on `PATH` inside the SDK environment.

---

## Bug fixes

- `avocado clean --skip-volumes` no longer removes `.avocado-state`. The state file is the only pointer to the Docker volume's name; removing it on unlock-only / stamps-only paths orphaned the volume so the next `avocado clean --volumes` could not find it. The two are now paired.
- The 9p workspace share is now lazy-mounted from `share::ensure_mounted_in_guest` when the CLI routes through `avocado-vm`. When Avocado.app launches QEMU directly the SSH-mount step would be skipped, leaving `/run/workspace` as an empty tmpfs scaffold so every Docker bind under `$HOME` silently mounted nothing — surfacing as `Compile script not found` with an empty `ls /opt/src` inside the build container.
Loading
Loading