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
6 changes: 4 additions & 2 deletions .github/workflows/images-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,7 @@ jobs:
- name: Build router publisher
run: go build -o "$RUNNER_TEMP/image-publish" ./cmd/image-publish

- name: Check the boot smoke script
run: bash -n images/smoke.sh
- name: Check the boot smoke scripts
run: |
bash -n images/smoke.sh
python3 -m py_compile images/runner/smoke.py images/ubuntu-24.04-desktop/smoke.py images/publish.py images/catalog-pr.py
87 changes: 76 additions & 11 deletions images/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# images

Lab-built Incus images for agentcompute: the Alpine `router` system container
and two minimal Ubuntu 24.04 runner VMs. `runner` has no sudo grant;
`runner-publisher` permits only the root-owned image-build wrapper.
Lab-built Incus images for agentcompute: the Alpine `router` system container,
two minimal Ubuntu 24.04 runner VMs, and an Ubuntu 24.04 Xorg desktop VM.
`runner` has no sudo grant; `runner-publisher` permits only the root-owned
image-build wrapper.

| Path | Role |
| --- | --- |
| `pins.yaml` | Checksummed build tools, Alpine package closure, Ubuntu base and CA bootstrap package, fixed Ubuntu snapshot, Actions Runner, and upstream guest files. |
| `pins.yaml` | Checksummed build tools, Alpine package closure, Ubuntu base and CA bootstrap package, fixed Ubuntu snapshot, Actions Runner, Cua Driver, and upstream guest files. |
| `router/distrobuilder.yaml` | The recipe. Installs only the pinned APKs from an offline seed (`--no-network`, empty `/etc/apk/repositories`), enables OpenRC `lxc` mode, and emits a unified tarball. |
| `runner/distrobuilder.yaml` | Split VM recipe with `runner` and `publisher` variants, signed shim/GRUB, growroot, incus-agent generator, and `ttyS0` output. |
| `ubuntu-24.04-desktop/distrobuilder.yaml` | Split desktop VM recipe with Xorg, GDM automatic login, AT-SPI, Cua Driver, X0tigervnc, systemd-networkd, growroot, and signed shim/GRUB. |
| `ubuntu-24.04-desktop/smoke.py` | Candidate qualification for X11, the Driver session socket and user unit, native `list_apps`, disabled nesting, and owned-resource cleanup. |
| `build.py` | `validate` (schema and pin checks, no credentials) and `build` (download-verify, compile distrobuilder from vendored source, assemble). PEP 723 script with `build.py.lock`. |
| `catalog.yaml` | Startup catalog: image name → digest-pinned GHCR reference or upstream Incus `remote:alias`, kind, OS, defaults. |
| `smoke.sh` | Shared six-tool router boot smoke used by image CI. |
Expand Down Expand Up @@ -36,12 +39,13 @@ deletes anything it did not create.
distrobuilder needs root and loop devices, not KVM. macOS cannot run it;
`sandbox01` can.

For a runner VM, add `--image runner` or `--image runner-publisher`. Install
the VM assembly tools (`btrfs`, `qemu-img`, `sgdisk`, `mkfs.vfat`, `mkfs.ext4`,
`resize2fs`, `losetup`, `mount`, `rsync`, `blkid`, and `dpkg-deb`) first.
Distrobuilder requires `btrfs` even for an ext4 image; the publisher variant
bakes `btrfs-progs` alongside its other assembly tools. Output is `incus.tar.xz`,
`disk.qcow2`, and `metrics.json`; no nested virtualization is used.
For a VM, add `--image runner`, `--image runner-publisher`, or
`--image ubuntu-24.04-desktop`. Install the VM assembly tools (`btrfs`,
`qemu-img`, `sgdisk`, `mkfs.vfat`, `mkfs.ext4`, `resize2fs`, `losetup`,
`mount`, `rsync`, `blkid`, and `dpkg-deb`) first. Distrobuilder requires
`btrfs` even for an ext4 image; the publisher variant bakes `btrfs-progs`
alongside its other assembly tools. Output is `incus.tar.xz`, `disk.qcow2`,
and `metrics.json`; no nested virtualization is used.

The guest files are copied byte-for-byte from the pinned incus-gh-runner
v2.0.0 release and checked during validation. General runners inherit
Expand All @@ -52,11 +56,38 @@ sudo command can run:
actions-runner ALL=(root) NOPASSWD: /usr/local/sbin/agentcompute-build
```

The wrapper accepts only a full lowercase commit SHA and one of the three
The wrapper accepts only a full lowercase commit SHA and one of the four
image names. It discards job-supplied environment variables, uses a root-owned
checkout, and refuses source outside public `origin/master` ancestry. It
does not grant direct sudo access to shells, mount tools, or `qemu-img`.

### Desktop image

The desktop recipe starts from the pinned Ubuntu base and snapshot
`20260911T000000Z`. It builds a 16 GiB amd64 VM with Xorg, automatic login as
`automation`, `gnome-text-editor`, AT-SPI, and X0tigervnc on guest TCP port
5900. Netplan explicitly selects `renderer: networkd`, and the recipe enables
`systemd-networkd`; this is the correction found after the first guest needed
the same setting applied in place.

Cua Driver 0.28.1 is pinned to the full
`cua-driver-rs-0.28.1-linux-x86_64.tar.gz` archive with SHA-256
`a068b6e477893b77ced74bceccf7db7483cf140e8d54150ce5849b6252b90bcf`.
The GitHub release is a prerelease accepted under the project-specific pin
exception.

The graphical session runs a persistent user daemon at
`/run/user/1000/cua-driver.sock`. Host calls use a new one-shot
`cua-driver call --socket /run/user/1000/cua-driver.sock` process for each
native tool invocation. They run as UID 1000 from `/home/automation` with
`HOME=/home/automation` and `XDG_RUNTIME_DIR=/run/user/1000`. Native tool names
and JSON arguments pass through unchanged; the host does not add typed
wrappers for individual Driver tools. Screenshots travel through the binary
guest-file API and return as URLs rather than embedded image data.

See the [Phase 6 desktop spike report](../spikes/desktop/README.md) for direct
Driver results, timing, screenshot scaling, VNC, and reboot evidence.

## Publication and import

The public `images-publish.yml` validates on a GitHub-hosted runner and
Expand Down Expand Up @@ -184,6 +215,40 @@ with the router set (59 APKs, 13.2 MB downloaded).

Scratch is sampled every 100 ms, so short peaks between samples are missed.

### Desktop Phase 6

Two local desktop builds used fresh work and output directories:

| Build evidence | Download | Compile | Assemble | Peak RSS | Scratch high-water | `incus.tar.xz` | `disk.qcow2` |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| First image | 5.849 s | 31.670 s | 842.017 s | 507,180 KiB | 7,625,043,968 B | 656 B | 744,611,840 B |
| Corrected networkd rebuild | 5.824 s | 31.834 s | 392.512 s | 517,712 KiB | 7,621,808,128 B | 640 B | 745,013,248 B |

Download includes verified retrieval of Go, vendored distrobuilder source,
Ubuntu base, the snapshot CA package, and the full Cua Driver archive. Compile
excludes download and assembly. Scratch was sampled every 100 ms.

The direct token and reboot results in the spike report came from the first
image after repairing that guest's netplan in place. The corrected rebuild
subsequently passed `images/ubuntu-24.04-desktop/smoke.py` in `image-build`:
X11, the graphical user's active Driver service, and 234 native `list_apps`
entries. The smoke removed its own VM and imported image.

Protected bootstrap PR #23 and private bake run 34854323245 completed
successfully. The desktop-aware publisher must be deployed before the
four-image bake. Desktop publication, verified fetch-back, and the public
catalog update remain pending; no desktop GHCR digest is claimed.

The corrected image passed the complete production-stdio MCP acceptance run
after OVN recovery: private-only client, desktop readiness, native `list_apps`,
PNG URL fetch/decode, one foreground token click changing one editor tab to
two, reboot recovery, and the VNC endpoint reported by `desktop.info`.
The representative program took 23.624 s; the full run took 158.45 s.
The running reaper returned 404 for the original screenshot 27.120 s after
the shortened sandbox expiry. See the spike report for the native background
delivery limitation and [fleet PR #20](https://github.com/GilmanLab/fleet/pull/20)
for the separately recovered stale-CA reconnect storm and active log limits.

### Reproducibility

The three hand-driven tarballs decode to identical member sets, contents,
Expand Down
Loading