Skip to main content

max / mountaineer

4.9 KB · 117 lines History Blame Raw
1 # QEMU iteration loop
2
3 Wrappers around qemu for the build-and-test cycle. Mac host with qemu 8+
4 and the x86_64 OVMF firmware. Apple Silicon hosts run x86_64 guests via
5 TCG emulation (no HVF for cross-arch) — first boot takes ~5-10 minutes
6 while cloud-init installs the build deps; subsequent boots are fast.
7
8 ## Scripts
9
10 | Script | Job |
11 |--------|-----|
12 | `vm-up.sh` | Start the builder VM. First run: builds a cloud-init seed and lets the VM provision itself; subsequent runs: just boots. |
13 | `vm-up.sh --stop` | Graceful shutdown via the qemu monitor socket. |
14 | `vm-up.sh --reprovision` | Wipe state and re-run cloud-init from scratch. |
15 | `vm-build.sh` | rsync the repo into the builder, run `./build.sh`, copy artifacts back to `./out/`. |
16 | `vm-test-install.sh` | Boot the most recent ISO from `./out/` in a fresh disposable target VM with the install config attached. |
17 | `common.sh` | Shared paths, OVMF location, qemu invocation pattern. Sourced by the others. |
18
19 All scripts write VM state under `../../.vms/` (gitignored). Disk images,
20 the cloud qcow2, per-VM NVRAM, the generated SSH key for the builder,
21 serial logs, and monitor sockets all live there.
22
23 ## First-time setup
24
25 The cloud image needs to be downloaded once. From the `build/` root:
26
27 ```sh
28 mkdir -p .vms/images
29 curl -fL -o .vms/images/alpine-cloud.qcow2 \
30 https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/cloud/generic_alpine-3.23.4-x86_64-uefi-cloudinit-r0.qcow2
31 qemu-img resize .vms/images/alpine-cloud.qcow2 +30G
32 ```
33
34 Then start the builder:
35
36 ```sh
37 ./tools/qemu/vm-up.sh
38 ```
39
40 On the first run this generates an ed25519 SSH keypair, writes
41 cloud-init user-data with that pubkey baked in, builds a NoCloud seed
42 ISO with `hdiutil`, and boots the VM with both the cloud image (as
43 overlay-on-base) and the seed attached. Cloud-init installs the build
44 deps and starts sshd. The script polls for sshd reachability every 10s
45 for up to 15 minutes and exits when the VM is ready.
46
47 After the first successful boot, a `.vms/.builder-provisioned` marker
48 file is dropped. On subsequent runs the VM boots without the seed
49 attached (cloud-init has already run).
50
51 ## The iteration cycle
52
53 ```sh
54 # Once per shell session — or whenever the VM has been stopped:
55 ./tools/qemu/vm-up.sh
56
57 # Edit files in apkovl-src/, installer/, mkimg/, etc., then:
58 ./tools/qemu/vm-build.sh # rsync + build inside builder
59 ./tools/qemu/vm-test-install.sh # boot the new ISO in a fresh target VM
60 ```
61
62 `vm-build.sh` runs in your terminal, blocks until the build finishes,
63 copies artifacts back. `vm-test-install.sh` boots the most recent ISO
64 in a fresh disposable target VM. Pass `--daemonize` to background it
65 for scripted ssh-driven installs. If you have `dosfstools` + `mtools`
66 (`brew install dosfstools mtools`), it also stages
67 `tools/qemu/test-install.yaml` onto a FAT image labeled `MTN-CFG` on a
68 second virtio disk; the installer auto-detects that path. Without those
69 tools, copy the config into the live env over ssh (`ssh ... 'cat > /tmp/install.yaml' < tools/qemu/test-install.yaml`)
70 and pass `--config /tmp/install.yaml` to `mountaineer-install`.
71
72 ## When the loop breaks
73
74 Common failure modes:
75
76 - **`apk add` fails on a package.** A name in
77 `apkovl-src/common/etc/apk/world` or in
78 `mkimg/mkimg.mountaineer-server.sh` is wrong, in the wrong repo, or
79 not packaged for x86_64. Fix the name; re-run `vm-build.sh`.
80 - **mkimage profile rejected.** The `boot_efi` token or feature names may
81 not match what current aports expects. Read the mkimage script's
82 error; edit the profile.
83 - **Cloud-init didn't finish.** Tail `.vms/logs/builder-serial.log` to
84 see what happened during the first boot. Common: network can't reach
85 Alpine repos (qemu user-mode NAT should always work — usually a DNS
86 or upstream-block issue on the host); a package name in user-data is
87 invalid (fix and `vm-up.sh --reprovision`).
88 - **Builder VM seems stuck.** `./tools/qemu/vm-up.sh --stop` then
89 `--reprovision`.
90 - **Target VM doesn't see the install config.** On macOS, install
91 `dosfstools` + `mtools` via Homebrew, or skip the FAT image and use
92 the ssh-cat workflow above. See `../../build_notes.md` for the full
93 iteration recipe.
94
95 ## Cleanup
96
97 Wipe all VM state and start fresh:
98
99 ```sh
100 ./tools/qemu/vm-up.sh --stop # if running
101 rm -rf .vms/
102 ```
103
104 The next `./tools/qemu/vm-up.sh` re-downloads the cloud image (200 MB)
105 and re-provisions from scratch.
106
107 ## Why a cloud image instead of the standard ISO
108
109 The original plan was the Alpine virt ISO + manual `setup-alpine`. The
110 cloud image with cloud-init lets us declare provisioning state in
111 `user-data` and have the VM configure itself on first boot — no manual
112 keyboard time, no per-developer divergence in builder setup. The
113 tradeoff is 200 MB instead of 67 MB on download, and a bigger dependency
114 chain (cloud-init itself). For a build host the bigger image is the
115 right call; for the *installed* Mountaineer system we still build from
116 scratch and cloud-init never touches it.
117