appliance/README.md
Rama main · réplica de lectura del repositorio original.
# Windows appliance image build
Builds the sealed Ubuntu 24.04 ProjectAchilles VM image. Packer, QEMU, OVMF and
libguestfs all run inside one builder container, so local and CI builds use
identical tools.
## Prerequisites
- Docker, able to run containers with `--device /dev/kvm`
- `/dev/kvm` on the host (hardware virtualisation)
- Go (host) for the installer binaries
- At least 40 GB free in `appliance/out/`
No qemu, packer or libguestfs is needed on the host.
## Targets
Run from the repo root with `make -C appliance <target>`.
| Target | What it does |
|--------|--------------|
| `builder` | Build the `achilles-appliance-builder:local` image (about 5 min first time) |
| `builder-smoke` | Check packer, qemu, OVMF, `/dev/kvm` and libguestfs in the container |
| `check-space` | Fail unless 40 GB are free in `appliance/out` |
| `base-image` | Download the Ubuntu 24.04 cloud image, verify against `SHA256SUMS` (about 600 MB) |
| `installer-bin` | Build `out/achilles-installer` (host Go) |
| `installer-testbin` | Build `out/achilles-installer-test` (`-tags appliancetest`) |
| `out-ownership-check` | Prove container targets leave `out/` owned by you (`DOCKER_RUN_OUT`) |
| `image` | Build `out/achilles-appliance-<ver>.qcow2` with Packer from the committed HEAD (refuses a dirty tree; about 6-10 min with KVM). Log: `out/packer.log`, VM console: `out/serial.log` |
| `repo-bundle` | Write `out/repo.bundle` (run by `image`): `HEAD` and only the `v<x>.<y>.<z>` tags merged into it. No branches, stash, `agent-v*` or local-only tags, so nothing private from the build host ships. Fails unless the bundle holds exactly those refs |
| `check-sealed` | Assert the image is sealed (`IMAGE=` defaults to this `VER`'s image) |
| `boot-test` | Boot `IMAGE` in two throwaway UEFI VMs (`test/boot-test.sh`). `MODE=quick` (about 10 min): first boot, distinct identities, clean checkout and image labels at the build commit, reboot. `MODE=full` (10-40 min) also installs on VM1, backs it up and restores the backup on VM2. Serial logs and install output: `out/boot-test/` |
| `vhdx` | Convert `IMAGE` to a dynamic VHDX (`qemu-img convert -O vhdx -o subformat=dynamic`, then `qemu-img check`), zip it as `out/ProjectAchilles-<ver>-hyperv.zip` (holds `ProjectAchilles-<ver>.vhdx`) and write `out/appliance-SHA256SUMS`. Verify with `cd out && sha256sum -c appliance-SHA256SUMS` |
| `clean` | Remove `appliance/out/` |
`VER` is the platform release the image is built from:
`git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]*' --always --dirty`, so
`v2.7.0` on a release tag and `v2.7.0-3-g<sha>` after it. Agent tags
(`agent-v*`) never count, even on a commit they share with a platform
release. `image` refuses a `VER` (environment or command line) that differs
from that describe of HEAD, because the image is named, versioned and stamped
(`-X main.version`, `ACHILLES_VERSION`) with it. To build one anyway, add
`ALLOW_VER_OVERRIDE=1`, for example
`make -C appliance image VER=v2.7.0-rc1 ALLOW_VER_OVERRIDE=1`. Other targets
(`check-sealed`, `boot-test`, `vhdx`) take `VER` only to find the default
`IMAGE`.
## Disk use
`out/` needs about 40 GB at peak: base image (about 600 MB), the 80 GB sparse
qcow2 (a few GB real), and the converted VHDX. Full build time is roughly
20-40 minutes with KVM.
## How `image` works
`appliance.pkr.hcl` boots the Ubuntu cloud image under KVM/OVMF with a
cloud-init seed that creates the `achilles` build user (per-build random
password, generated by `build/build-image.sh`, never written to a file).
The provisioners run in order:
| Script | Does |
|--------|------|
| `provision/10-base.sh` | apt upgrade, guest tools, console/keyboard packages, security-only unattended upgrades, UTC |
| `provision/20-docker.sh` | Docker CE + compose plugin from Docker's apt repo (key fingerprint checked) |
| `provision/30-repo.sh` | `/opt/projectachilles` from `out/repo.bundle`, `origin` = GitHub, detached at the build commit; the installer binary + `/usr/local/bin` symlink |
| `provision/40-images.sh` | builds `achilles-server-backend`/`-frontend` (label `io.projectachilles.commit`) and pulls the other compose images; never starts the stack |
| `provision/50-rootfs.sh` | copies `rootfs/` to `/`, hands the network to `50-achilles.yaml`, disables cloud-init, enables the achilles units, disables getty@tty1 and ssh |
| `seal.sh` | removes identity, credentials, logs and build leftovers; the steps that take sudo away run at shutdown from a transient unit |
The image holds the committed HEAD, so commit first: `image` refuses a
`-dirty` version. `build-image.sh` sparsifies the qcow2, refuses it if it
contains the build password or its hash, and runs `check-sealed` before the
qcow2 gets its final name; `make check-sealed` re-runs the gate.
## CI
`.github/workflows/appliance.yml` runs the same targets on `ubuntu-latest`
(KVM enabled via a udev rule, 150 minute timeout):
| Trigger | Mode | Artifact |
|---------|------|----------|
| `pull_request` touching `appliance/`, `installer/`, `scripts/deploy-server.sh`, the server compose file or the Dockerfiles | `quick` | none |
| `workflow_dispatch` (input `full`; pick the branch or tag with `--ref`) | `quick`, or `full` when `full` is set | in full mode |
| push of a `v*.*.*` tag | `full`, on the tag | yes |
The job only builds the ref it runs on. Checking out another ref (a
`workflow_run` trigger, a `ref` input) would let that code write Actions
caches that default-branch runs restore (CodeQL
`actions/cache-poisoning`). The tag-push run starts alongside "Release
Platform" rather than after it.
In full mode the job also runs `make vhdx` and uploads only
`ProjectAchilles-<ver>-hyperv.zip` and `appliance-SHA256SUMS` as the
`appliance-hyperv` artifact (7 days). When a run fails, `appliance-logs`
(1 day) holds `out/packer.log`, `out/serial.log` and the boot test's
`out/boot-test/**/*.log`: `build-image.sh` deletes a log that contains the
build password or its hash before it fails, and the boot-test VMs use only
the placeholder fixtures in `test/`. The qcow2, the VM disks, the backup and
`repo.bundle` are never uploaded.
The job frees runner disk first (removes preinstalled SDKs and toolcaches,
prunes Docker) and prints `df -h`; a build needs about 40 GB.
### First CI run
The workflow has not run on GitHub before this PR. Merge only after the PR's
`quick` run is green. Then run the full mode once, before the next `v*` tag:
```bash
gh workflow run appliance.yml -f full=true
```
It installs, backs up and restores, and uploads the Hyper-V zip; a failure
there would otherwise first show up on the release.
## Release check on Hyper-V
Before a release, run the manual check in [docs/hyperv-release-check.md](docs/hyperv-release-check.md):
import with `windows/Import-Achilles.ps1`, first boot on a real console, the wizard, an
agent, backup and restore, upgrade.