Repository navigation
Expand file tree
/
Copy pathiso.justfile
More file actions
590 lines (534 loc) · 26.9 KB
/
Copy pathiso.justfile
File metadata and controls
590 lines (534 loc) · 26.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# Output directory for built ISOs and intermediate artifacts.
# Override with: just output_dir=/your/path iso-sd-boot tromso
output_dir := "output"
# Set to 1 to enable SSH in the live session for debugging.
# Example: just debug=1 output_dir=/tmp/out iso-sd-boot tromso
# Never use debug=1 for production/release ISOs.
debug := "0"
# Set to "dev" to pull the tuna-installer dev build (continuous-dev release).
# Example: just installer_channel=dev iso-sd-boot tromso
installer_channel := "stable"
# LUKS passphrase used by luks-install for testing.
luks-passphrase := "testpassphrase"
# Squashfs compression preset:
# fast (default) — zstd level 3, 128K blocks — quick local builds/CI
# release — zstd level 15, 1M blocks — ~20% smaller, ~5× slower
# Example: just compression=release iso-sd-boot tromso
compression := "fast"
# Build the ISO in the background, detached from the terminal session.
# Logs are written to {{output_dir}}/build.log and tailed live.
# Usage: just build-bg tromso
build-bg target:
#!/usr/bin/bash
set -euo pipefail
mkdir -p {{output_dir}}
LOG=$(realpath {{output_dir}})/build.log
echo "Starting background build → ${LOG}"
setsid sudo just \
debug={{debug}} \
installer_channel={{installer_channel}} \
output_dir={{output_dir}} \
compression={{compression}} \
iso-sd-boot {{target}} \
> "${LOG}" 2>&1 &
disown $!
echo "Build PID $! — tailing log (Ctrl-C is safe, build continues)"
tail -f "${LOG}"
container target:
#!/usr/bin/bash
set -euo pipefail
# Pre-squash base image to avoid disk explosion during multi-layer pulls
BASE_IMAGE=$(grep '^ARG BASE_IMAGE=' ./{{target}}/Containerfile | cut -d= -f2)
# Pre-flight: the payload base image must exist in its registry. A
# missing payload (e.g. nightly 'Build Tromso' failing) used to surface
# deep inside the pull as a generic 'manifest unknown' that read like an
# install failure — tromso#208. Fail loudly and clearly instead.
if command -v skopeo >/dev/null 2>&1; then
if ! sudo skopeo inspect "docker://${BASE_IMAGE}" >/dev/null 2>&1; then
echo "::error::Payload image ${BASE_IMAGE} is missing or not pullable."
echo "::error::It is published by the nightly 'Build Tromso (Multi-Runner)' workflow — if that has been failing, the payload must be republished before ISO builds can run (see tromso#208)."
exit 1
fi
echo "Payload image ${BASE_IMAGE} verified"
fi
echo "Squashing base image: ${BASE_IMAGE}"
SQUASH_CTR=$(sudo buildah from --pull-never "${BASE_IMAGE}" 2>/dev/null || sudo buildah from "${BASE_IMAGE}")
sudo buildah commit --squash "${SQUASH_CTR}" oci-archive:/tmp/squashed-base.oci:"${BASE_IMAGE}"
sudo podman load -i /tmp/squashed-base.oci
rm -f /tmp/squashed-base.oci
sudo podman build --cap-add sys_admin --security-opt label=disable \
--layers \
--build-arg BASE_IMAGE="${BASE_IMAGE}" \
--build-arg DEBUG={{debug}} \
--build-arg INSTALLER_CHANNEL={{installer_channel}} \
-t {{target}}-installer ./{{target}}
# Build the Debian-based ISO assembly container for the given target.
iso-builder target:
podman build --security-opt label=disable -t {{target}}-iso-builder \
-f ./{{target}}/Containerfile.builder ./{{target}}
# Build a systemd-boot UEFI live ISO for the given target.
#
# Uses a two-container approach:
# 1. localhost/<target>-installer — the live environment (3-stage Containerfile)
# 2. localhost/<target>-iso-builder — Debian ISO assembly tools (Containerfile.builder)
#
# Output: output/<target>-live.iso
iso-sd-boot target:
#!/usr/bin/bash
set -euo pipefail
# Read payload ref from file (defaults to localhost/<target>:latest if missing)
PAYLOAD_REF="$(cat '{{target}}/payload_ref' 2>/dev/null | tr -d '[:space:]' || echo "localhost/{{target}}:latest")"
just debug={{debug}} installer_channel={{installer_channel}} container {{target}}
mkdir -p {{output_dir}}
OUTPUT_DIR=$(realpath "{{output_dir}}")
if [[ $(id -u) -eq 0 ]]; then
_ns() { bash -c "$1"; }
_ns_rm() { rm -rf "$@"; }
else
_ns() { podman unshare bash -c "$1"; }
_ns_rm() { podman unshare rm -rf "$@"; }
fi
SQUASHFS="${OUTPUT_DIR}/{{target}}-rootfs.sfs"
BOOT_TAR="${OUTPUT_DIR}/{{target}}-boot-files.tar"
CS_STAGING="${OUTPUT_DIR}/{{target}}-cs-staging"
SQUASHFS_ROOT="${OUTPUT_DIR}/{{target}}-sfs-root"
trap "rm -f '${SQUASHFS}' '${BOOT_TAR}' '${OUTPUT_DIR}/{{target}}-payload.oci.tar'; _ns_rm '${CS_STAGING}' '${SQUASHFS_ROOT}' 2>/dev/null || true" EXIT
echo "Building squashfs and boot tar from localhost/{{target}}-installer..."
_ns "
set -euo pipefail
MOUNT=\$(podman image mount localhost/{{target}}-installer)
PATH=/usr/sbin:/usr/bin:/home/linuxbrew/.linuxbrew/bin:\$PATH
PAYLOAD_OCI='${OUTPUT_DIR}/{{target}}-payload.oci.tar'
CS_STAGING='${CS_STAGING}'
SQUASHFS_ROOT='${SQUASHFS_ROOT}'
SQUASHFS_STORAGE=\"\${CS_STAGING}/var/lib/containers/storage\"
STORAGE_CONF=\"\$(mktemp '${OUTPUT_DIR}'/live-storage-XXXXXX.conf)\"
mkdir -p \"\${SQUASHFS_STORAGE}\"
printf '[storage]\ndriver = \"vfs\"\nrunroot = \"/tmp/cs-runroot\"\ngraphroot = \"/vfs-storage\"\n' \
> \"\${STORAGE_CONF}\"
echo 'Squashing tromso image layers to reduce disk footprint...'
SQUASH_CTR=\$(buildah from --pull-never \"${PAYLOAD_REF}\")
buildah commit --squash \"\${SQUASH_CTR}\" oci-archive:\${PAYLOAD_OCI}:${PAYLOAD_REF}
echo 'Importing tromso OCI image into squashfs containers-storage...'
podman run --rm \
--privileged \
-v \"\${PAYLOAD_OCI}:/payload.oci.tar:ro\" \
-v \"\${SQUASHFS_STORAGE}:/vfs-storage\" \
-v \"\${STORAGE_CONF}:/tmp/st.conf:ro\" \
localhost/{{target}}-installer \
sh -c 'mkdir -p /tmp/cs-runroot /var/tmp && CONTAINERS_STORAGE_CONF=/tmp/st.conf skopeo copy oci-archive:/payload.oci.tar:${PAYLOAD_REF} containers-storage:${PAYLOAD_REF}'
rm -f \"\${PAYLOAD_OCI}\" \"\${STORAGE_CONF}\"
echo 'Building unified squashfs source tree...'
mkdir -p \"\${SQUASHFS_ROOT}\"
cp -a --reflink=auto \"\${MOUNT}/.\" \"\${SQUASHFS_ROOT}/\" 2>/dev/null || \
cp -a \"\${MOUNT}/.\" \"\${SQUASHFS_ROOT}/\"
mkdir -p \"\${SQUASHFS_ROOT}/var/lib/containers/storage\"
cp -a \"\${CS_STAGING}/var/lib/containers/storage/.\" \
\"\${SQUASHFS_ROOT}/var/lib/containers/storage/\"
rm -rf \"\${CS_STAGING}\"
SFS_LEVEL=3; SFS_BLOCK=131072
[[ '{{compression}}' == 'release' ]] && { SFS_LEVEL=15; SFS_BLOCK=1048576; }
mksquashfs \"\${SQUASHFS_ROOT}\" '${SQUASHFS}' \
-noappend -comp zstd -Xcompression-level \${SFS_LEVEL} -b \${SFS_BLOCK} \
-processors 4 \
-e proc -e sys -e dev -e run -e tmp
rm -rf \"\${SQUASHFS_ROOT}\"
tar -C \"\$MOUNT\" \
-cf '${BOOT_TAR}' \
./usr/lib/modules \
./usr/lib/systemd/boot/efi
podman image umount localhost/{{target}}-installer
"
TMPDIR="${OUTPUT_DIR}" \
PATH="/usr/sbin:/usr/bin:/home/linuxbrew/.linuxbrew/bin:${PATH}" \
bash "{{target}}/src/build-iso.sh" "${BOOT_TAR}" "${SQUASHFS}" "${OUTPUT_DIR}/{{target}}-live.iso"
echo "ISO ready: ${OUTPUT_DIR}/{{target}}-live.iso"
# Boot a built ISO in QEMU via UEFI (OVMF) with serial console output on stdout.
# Exit: Ctrl-A then X
boot-iso-serial target:
#!/usr/bin/bash
set -euo pipefail
QEMU=$(command -v /usr/libexec/qemu-kvm /usr/bin/qemu-kvm \
/usr/bin/qemu-system-x86_64 2>/dev/null | head -1)
[[ -z "$QEMU" ]] && { echo "qemu-kvm / qemu-system-x86_64 not found" >&2; exit 1; }
ISO=$(ls \
{{output_dir}}/{{target}}-live.iso \
2>/dev/null | head -1 || true)
if [[ -z "$ISO" ]]; then
echo "No ISO found for '{{target}}' — run: just iso-sd-boot {{target}}" >&2
exit 1
fi
OVMF_CODE=""
for f in \
/usr/share/OVMF/OVMF_CODE.fd \
/usr/share/edk2/ovmf/OVMF_CODE.fd \
/usr/share/edk2-ovmf/x64/OVMF_CODE.fd \
/usr/share/ovmf/OVMF.fd; do
[[ -f "$f" ]] && { OVMF_CODE="$f"; break; }
done
OVMF_VARS_SRC=""
for f in \
/usr/share/OVMF/OVMF_VARS.fd \
/usr/share/edk2/ovmf/OVMF_VARS.fd \
/usr/share/edk2-ovmf/x64/OVMF_VARS.fd; do
[[ -f "$f" ]] && { OVMF_VARS_SRC="$f"; break; }
done
if [[ -z "$OVMF_CODE" ]]; then
echo "OVMF firmware not found — install edk2-ovmf or ovmf" >&2
exit 1
fi
OVMF_VARS=$(mktemp /tmp/OVMF_VARS.XXXXXX.fd)
[[ -n "$OVMF_VARS_SRC" ]] && cp "${OVMF_VARS_SRC}" "${OVMF_VARS}"
trap "rm -f ${OVMF_VARS}" EXIT
echo "Booting ${ISO} via UEFI — serial console below (Ctrl-A X to quit)"
echo "SSH available on localhost:2222 (user: liveuser, password: live) if built with debug=1"
sudo "$QEMU" \
-machine q35 \
-m 4096 \
-accel kvm \
-cpu host \
-smp 4 \
-drive if=pflash,format=raw,readonly=on,file="${OVMF_CODE}" \
-drive if=pflash,format=raw,file="${OVMF_VARS}" \
-drive if=none,id=live-disk,file="${ISO}",media=cdrom,format=raw,readonly=on \
-device virtio-scsi-pci,id=scsi \
-device scsi-cd,drive=live-disk \
-net nic,model=virtio -net user,hostfwd=tcp::2222-:22 \
-serial mon:stdio \
-display none \
-no-reboot
# Boot ISO with VNC display (for seeing the Plasma desktop)
boot-iso-vnc target:
#!/usr/bin/bash
set -euo pipefail
QEMU=$(command -v /usr/libexec/qemu-kvm /usr/bin/qemu-kvm \
/usr/bin/qemu-system-x86_64 2>/dev/null | head -1)
[[ -z "$QEMU" ]] && { echo "qemu-kvm / qemu-system-x86_64 not found" >&2; exit 1; }
ISO=$(ls {{output_dir}}/{{target}}-live.iso 2>/dev/null | head -1 || true)
if [[ -z "$ISO" ]]; then
echo "No ISO found — run: just iso-sd-boot {{target}}" >&2; exit 1
fi
OVMF_CODE=""
for f in /usr/share/OVMF/OVMF_CODE.fd /usr/share/edk2/ovmf/OVMF_CODE.fd \
/usr/share/edk2-ovmf/x64/OVMF_CODE.fd /usr/share/ovmf/OVMF.fd; do
[[ -f "$f" ]] && { OVMF_CODE="$f"; break; }
done
OVMF_VARS=$(mktemp /tmp/OVMF_VARS.XXXXXX.fd)
for f in /usr/share/OVMF/OVMF_VARS.fd /usr/share/edk2/ovmf/OVMF_VARS.fd \
/usr/share/edk2-ovmf/x64/OVMF_VARS.fd; do
[[ -f "$f" ]] && { cp "$f" "${OVMF_VARS}"; break; }
done
[[ -z "$OVMF_CODE" ]] && { echo "OVMF firmware not found" >&2; exit 1; }
trap "rm -f ${OVMF_VARS}" EXIT
echo "Booting ${ISO}"
echo " VNC: vncviewer 127.0.0.1:5910 (display :10)"
echo " Serial: telnet 127.0.0.1 4445"
echo " SSH: ssh -p 2222 liveuser@127.0.0.1 (debug=1 only)"
sudo "$QEMU" \
-machine q35 -cpu host -m 4096 -smp 4 -accel kvm \
-drive if=pflash,format=raw,readonly=on,file="${OVMF_CODE}" \
-drive if=pflash,format=raw,file="${OVMF_VARS}" \
-drive if=none,id=live-disk,file="${ISO}",media=cdrom,format=raw,readonly=on \
-device virtio-scsi-pci,id=scsi \
-device scsi-cd,drive=live-disk \
-device virtio-vga \
-display vnc=127.0.0.1:10 \
-device virtio-net-pci,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22 \
-serial telnet:127.0.0.1:4445,server,nowait \
-no-reboot
# ─────────────────────────────────────────────────────────────────────────────
# LUKS install end-to-end test (ported from projectbluefin/dakota-iso)
# ─────────────────────────────────────────────────────────────────────────────
#
# Full CI test sequence:
# just debug=1 iso-sd-boot tromso
# just luks-test-qemu tromso
#
# Or step-by-step:
# just luks-boot-qemu-live tromso # boot live ISO in QEMU (daemonized)
# just luks-install-qemu tromso # SSH fisherman install
# just luks-boot-qemu-installed tromso # reboot QEMU into installed disk
# just luks-unlock-qemu tromso # send passphrase via QEMU monitor
# Path to a local fisherman checkout for bootcDirect (ostree) installs; unused
# on the default composefs path.
fisher_repo := ""
# QEMU memory (MiB) / vCPUs for the e2e test VMs.
qemu-mem := "8192"
qemu-smp := "4"
# QEMU install disk path (override with: just luks-qemu-disk=/path/disk.qcow2 ...)
luks-qemu-disk := "/var/tmp/tromso-luks-install.qcow2"
# Scratch disk mounted over /var/tmp inside the live VM — prevents ENOSPC
# during OCI blob extraction (overlay tmpfs is small).
luks-scratch-disk := "/var/tmp/tromso-luks-scratch.img"
# QEMU monitor sockets and serial logs
luks-qemu-monitor-live := "/tmp/tromso-qemu-live.sock"
luks-qemu-monitor-installed := "/tmp/tromso-qemu-installed.sock"
luks-qemu-serial-live := "/tmp/tromso-qemu-live-serial.log"
luks-qemu-serial-installed := "/tmp/tromso-qemu-installed-serial.log"
# SSH port for QEMU SLIRP forwarding
luks-qemu-ssh-port := "2222"
# Full end-to-end test: build the ISO then run the LUKS install + boot test.
# Usage: just debug=1 e2e tromso
e2e target:
#!/usr/bin/bash
set -euo pipefail
echo "=== Step 1/2: Building ISO (debug={{debug}}, installer_channel={{installer_channel}}) ==="
just debug={{debug}} installer_channel={{installer_channel}} output_dir={{output_dir}} iso-sd-boot {{target}}
echo "=== Step 2/2: LUKS end-to-end test ==="
rm -f /var/tmp/tromso-luks-install-*.qcow2 /var/tmp/tromso-luks-scratch-*.img \
"{{luks-qemu-monitor-live}}" "{{luks-qemu-monitor-installed}}" \
"{{luks-qemu-serial-live}}" "{{luks-qemu-serial-installed}}"
just luks-test-qemu {{target}}
# Run the full LUKS end-to-end test in QEMU (CI entry point).
# Builds nothing — expects the ISO to already exist in {{output_dir}}.
luks-test-qemu target installer_channel="stable":
#!/usr/bin/bash
set -euo pipefail
DISK="/var/tmp/tromso-luks-install-{{target}}-{{installer_channel}}.qcow2"
SCRATCH="/var/tmp/tromso-luks-scratch-{{target}}-{{installer_channel}}.img"
just luks-qemu-disk="$DISK" luks-scratch-disk="$SCRATCH" \
luks-qemu-monitor-live="{{luks-qemu-monitor-live}}" \
luks-qemu-serial-live="{{luks-qemu-serial-live}}" \
luks-boot-qemu-live {{target}}
just luks-qemu-ssh-port={{luks-qemu-ssh-port}} luks-install-qemu {{target}}
just luks-qemu-disk="$DISK" luks-scratch-disk="$SCRATCH" \
luks-qemu-monitor-installed="{{luks-qemu-monitor-installed}}" \
luks-qemu-serial-installed="{{luks-qemu-serial-installed}}" \
luks-boot-qemu-installed {{target}}
just luks-qemu-monitor-installed={{luks-qemu-monitor-installed}} \
luks-qemu-serial-installed={{luks-qemu-serial-installed}} \
luks-unlock-qemu {{target}}
# Boot the live ISO in QEMU (daemonized) with a blank install disk attached.
luks-boot-qemu-live target:
#!/usr/bin/bash
set -euo pipefail
QEMU=$(command -v /usr/libexec/qemu-kvm /usr/bin/qemu-kvm \
/usr/bin/qemu-system-x86_64 2>/dev/null | head -1)
[[ -z "$QEMU" ]] && { echo "qemu-kvm / qemu-system-x86_64 not found" >&2; exit 1; }
ISO=$(ls {{output_dir}}/{{target}}-live.iso 2>/dev/null | head -1 || true)
if [[ -z "$ISO" ]]; then
echo "No ISO found — run: just debug=1 iso-sd-boot {{target}}" >&2
exit 1
fi
OVMF_CODE=""; OVMF_VARS=""
for f in /usr/share/OVMF/OVMF_CODE_4M.fd /usr/share/OVMF/OVMF_CODE.fd \
/usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/ovmf/OVMF.fd; do
[[ -f "$f" ]] && { OVMF_CODE="$f"; break; }
done
for f in /usr/share/OVMF/OVMF_VARS_4M.fd /usr/share/OVMF/OVMF_VARS.fd \
/usr/share/edk2/ovmf/OVMF_VARS.fd; do
if [[ -f "$f" ]]; then cp "$f" /var/tmp/tromso-qemu-live-vars.fd; OVMF_VARS=/var/tmp/tromso-qemu-live-vars.fd; break; fi
done
[[ -z "$OVMF_CODE" ]] && { echo "OVMF firmware not found" >&2; exit 1; }
[[ -f "{{luks-qemu-disk}}" ]] || qemu-img create -f qcow2 "{{luks-qemu-disk}}" 64G
[[ -f "{{luks-scratch-disk}}" ]] || truncate -s 16G "{{luks-scratch-disk}}"
rm -f "{{luks-qemu-monitor-live}}" "{{luks-qemu-serial-live}}"
echo "Booting live ISO: $ISO"
# KVM access: try direct, then sudo, then fall back to TCG
QEMU_ACCEL="-accel kvm"
QEMU_PREFIX=""
if ! test -r /dev/kvm 2>/dev/null; then
if sudo test -r /dev/kvm 2>/dev/null; then
echo "Using sudo for KVM access"
QEMU_PREFIX="sudo"
else
echo "KVM not available, falling back to TCG emulation (slower)"
QEMU_ACCEL="-accel tcg,thread=multi"
QEMU_PREFIX=""
fi
fi
CPU_FLAG="-cpu host"
if [[ "$QEMU_ACCEL" =~ tcg ]]; then
CPU_FLAG="-cpu qemu64"
fi
$QEMU_PREFIX "$QEMU" \
-machine q35 $CPU_FLAG -m {{qemu-mem}} -smp {{qemu-smp}} $QEMU_ACCEL \
-drive "if=pflash,format=raw,readonly=on,file=${OVMF_CODE}" \
-drive "if=pflash,format=raw,file=${OVMF_VARS}" \
-drive "if=none,id=iso,file=${ISO},media=cdrom,readonly=on,format=raw" \
-device virtio-scsi-pci,id=scsi \
-device scsi-cd,drive=iso \
-drive "if=none,id=disk,file={{luks-qemu-disk}},format=qcow2" \
-device virtio-blk-pci,drive=disk \
-drive "if=none,id=scratch,file={{luks-scratch-disk}},format=raw,cache=unsafe" \
-device virtio-blk-pci,drive=scratch \
-netdev "user,id=net0,hostfwd=tcp::{{luks-qemu-ssh-port}}-:22" \
-device virtio-net-pci,netdev=net0 \
-monitor "unix:{{luks-qemu-monitor-live}},server,nowait" \
-serial "file:{{luks-qemu-serial-live}}" \
-display none \
-daemonize
echo "Live QEMU started (monitor: {{luks-qemu-monitor-live}})"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=5 -o PreferredAuthentications=password"
echo "Waiting for live environment on port {{luks-qemu-ssh-port}}..."
# Ready when the serial marker appears OR SSH connects (debug=1 builds).
for i in $(seq 1 60); do
if grep -q "TROMSO_LIVE_READY" "{{luks-qemu-serial-live}}" 2>/dev/null; then
echo "Live environment ready (serial marker seen)"
break
fi
if sshpass -p live ssh $SSH_OPTS liveuser@127.0.0.1 -p {{luks-qemu-ssh-port}} true 2>/dev/null; then
echo "Live environment ready (SSH connected)"
break
fi
[[ "$i" -eq 60 ]] && { echo "ERROR: live env not ready after 5m"; tail -30 "{{luks-qemu-serial-live}}" || true; exit 1; }
sleep 5
done
# Wait for the live boot GUI to render and stabilize before screenshotting
sudo python3 "{{target}}/src/luks-unlock.py" wait-live \
"{{luks-qemu-monitor-live}}" \
"/tmp/luks-screenshot-live.ppm" || true
# Run fisherman LUKS install via SSH into the live QEMU VM.
luks-install-qemu target:
./scripts/luks-install-qemu.sh "{{target}}" "{{luks-passphrase}}" "{{luks-qemu-ssh-port}}" "{{luks-qemu-monitor-live}}" "{{fisher_repo}}"
# Boot the installed disk in QEMU (no ISO). Called after luks-install-qemu.
luks-boot-qemu-installed target:
#!/usr/bin/bash
set -euo pipefail
QEMU=$(command -v /usr/libexec/qemu-kvm /usr/bin/qemu-kvm \
/usr/bin/qemu-system-x86_64 2>/dev/null | head -1)
[[ -z "$QEMU" ]] && { echo "qemu-kvm / qemu-system-x86_64 not found" >&2; exit 1; }
OVMF_CODE=""; OVMF_VARS=""
for f in /usr/share/OVMF/OVMF_CODE_4M.fd /usr/share/OVMF/OVMF_CODE.fd \
/usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/ovmf/OVMF.fd; do
[[ -f "$f" ]] && { OVMF_CODE="$f"; break; }
done
for f in /usr/share/OVMF/OVMF_VARS_4M.fd /usr/share/OVMF/OVMF_VARS.fd \
/usr/share/edk2/ovmf/OVMF_VARS.fd; do
if [[ -f "$f" ]]; then cp "$f" /var/tmp/tromso-qemu-installed-vars.fd; OVMF_VARS=/var/tmp/tromso-qemu-installed-vars.fd; break; fi
done
[[ -z "$OVMF_CODE" ]] && { echo "OVMF firmware not found" >&2; exit 1; }
rm -f "{{luks-qemu-monitor-installed}}" "{{luks-qemu-serial-installed}}"
echo "Booting installed disk: {{luks-qemu-disk}}"
# Wait for the live QEMU holding this disk to exit before rebooting it.
DISK_PATTERN="$(echo '{{luks-qemu-disk}}' | sed 's/\./\\./g')"
for i in {1..15}; do
if ! sudo pgrep -f "qemu-system.*${DISK_PATTERN}" >/dev/null 2>&1; then
break
fi
echo "Waiting for live QEMU to exit (attempt $i)..."
sleep 2
done
QEMU_ACCEL="-accel kvm"
QEMU_PREFIX=""
if ! test -r /dev/kvm 2>/dev/null; then
if sudo test -r /dev/kvm 2>/dev/null; then
echo "Using sudo for KVM access"
QEMU_PREFIX="sudo"
else
echo "KVM not available, falling back to TCG emulation (slower)"
QEMU_ACCEL="-accel tcg,thread=multi"
QEMU_PREFIX=""
fi
fi
CPU_FLAG="-cpu host"
if [[ "$QEMU_ACCEL" =~ tcg ]]; then
CPU_FLAG="-cpu qemu64"
fi
$QEMU_PREFIX "$QEMU" \
-machine q35 $CPU_FLAG -m {{qemu-mem}} -smp {{qemu-smp}} $QEMU_ACCEL \
-drive "if=pflash,format=raw,readonly=on,file=${OVMF_CODE}" \
-drive "if=pflash,format=raw,file=${OVMF_VARS}" \
-drive "if=none,id=disk,file={{luks-qemu-disk}},format=qcow2" \
-device virtio-blk-pci,drive=disk \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-monitor "unix:{{luks-qemu-monitor-installed}},server,nowait" \
-serial "file:{{luks-qemu-serial-installed}}" \
-display none \
-daemonize
echo "Installed QEMU started (monitor: {{luks-qemu-monitor-installed}})"
for i in $(seq 1 15); do
[[ -S "{{luks-qemu-monitor-installed}}" ]] && break
sleep 2
done
# Send LUKS passphrase to installed QEMU VM via monitor screendump + sendkey.
# Polls screendump size to detect Plymouth takeover, then injects keystrokes.
luks-unlock-qemu target:
#!/usr/bin/bash
set -euo pipefail
PASSPHRASE="{{luks-passphrase}}"
echo "Unlocking LUKS on installed QEMU VM..."
sudo python3 "{{target}}/src/luks-unlock.py" qemu \
"{{luks-qemu-monitor-installed}}" \
"$PASSPHRASE" \
"{{luks-qemu-serial-installed}}"
# Show key screenshots inline for terminals that support it (Kitty, iTerm2)
bash "{{target}}/src/show-screenshot.sh" /tmp/luks-screenshot-plymouth.ppm "Plymouth prompt" || true
bash "{{target}}/src/show-screenshot.sh" /tmp/luks-screenshot-final.ppm "Final boot" || true
# ── Plain (no-encryption) install e2e ──────────────────────────────────────
# This is the install path most users actually run and (until this) had zero
# e2e coverage. Reuses luks-boot-qemu-live/-installed (booting is identical
# either way) with its own disk/monitor/serial variables so a plain and LUKS
# run can coexist without colliding.
#
# Local equivalent:
# just debug=1 iso-sd-boot tromso
# just plain-test-qemu tromso
plain-qemu-monitor-live := "/tmp/tromso-qemu-plain-live.sock"
plain-qemu-monitor-installed := "/tmp/tromso-qemu-plain-installed.sock"
plain-qemu-serial-live := "/tmp/tromso-qemu-plain-live-serial.log"
plain-qemu-serial-installed := "/tmp/tromso-qemu-plain-installed-serial.log"
# Run the full plain install end-to-end test in QEMU (CI entry point).
# Builds nothing — expects the ISO to already exist in {{output_dir}}.
plain-test-qemu target installer_channel="stable":
#!/usr/bin/bash
set -euo pipefail
DISK="/var/tmp/tromso-plain-install-{{target}}-{{installer_channel}}.qcow2"
SCRATCH="/var/tmp/tromso-plain-scratch-{{target}}-{{installer_channel}}.img"
just luks-qemu-disk="$DISK" luks-scratch-disk="$SCRATCH" \
luks-qemu-monitor-live="{{plain-qemu-monitor-live}}" \
luks-qemu-serial-live="{{plain-qemu-serial-live}}" \
luks-boot-qemu-live {{target}}
just luks-qemu-ssh-port={{luks-qemu-ssh-port}} \
luks-qemu-monitor-live="{{plain-qemu-monitor-live}}" \
plain-install-qemu {{target}}
just luks-qemu-disk="$DISK" luks-scratch-disk="$SCRATCH" \
luks-qemu-monitor-installed="{{plain-qemu-monitor-installed}}" \
luks-qemu-serial-installed="{{plain-qemu-serial-installed}}" \
luks-boot-qemu-installed {{target}}
just plain-qemu-monitor-installed="{{plain-qemu-monitor-installed}}" \
plain-qemu-serial-installed="{{plain-qemu-serial-installed}}" \
plain-wait-boot {{target}}
# Run fisherman plain (no-encryption) install via SSH into the live QEMU VM.
plain-install-qemu target:
./scripts/plain-install-qemu.sh "{{target}}" "{{luks-qemu-ssh-port}}" "{{luks-qemu-monitor-live}}" "{{fisher_repo}}"
# Wait for the installed disk to boot to a ready state. No passphrase to
# handle here (that's the whole point of the plain path) — just watch serial
# for the same success/failure markers tromso/src/luks-unlock.py's qemu mode
# checks for, so a regression here surfaces the same way a LUKS one does.
plain-wait-boot target:
#!/usr/bin/bash
set -euo pipefail
# The installed-VM serial log is written by a QEMU launched under sudo, so
# it is root-owned and unreadable by the CI user: a plain grep/tail would
# silently never match (and `tail` would fail on the error path).
SERIAL="{{plain-qemu-serial-installed}}"
# Read into a variable: `grep -q` closing a pipe early would trip pipefail.
serial() { sudo cat "$SERIAL" 2>/dev/null || true; }
echo "Waiting for installed system to boot..."
for i in $(seq 1 60); do
LOG="$(serial)"
# Strip ANSI codes and collapse whitespace: systemd status lines can be
# split across lines by the serial console (as luks-unlock.py handles).
FLAT="$(sed 's/\x1b\[[0-9;]*[A-Za-z]//g' <<<"$LOG" | tr -s '[:space:]' ' ')"
if grep -qiE "emergency mode|emergency shell" <<<"$LOG"; then
echo "ERROR: installed system dropped to emergency shell"
tail -50 <<<"$LOG"
exit 1
fi
# Tromso is a KDE image (SDDM); the GDM/g-i-s markers are kept only
# for parity with luks-unlock.py's qemu_check_serial.
if grep -qE "Started sddm\.service|Started Simple Desktop Display Manager|Started gdm\.service|Started GNOME Display Manager|Started gnome-initial-setup" <<<"$FLAT"; then
echo "Installed system booted successfully"
sudo python3 "{{target}}/src/luks-unlock.py" wait-live \
"{{plain-qemu-monitor-installed}}" "/tmp/plain-screenshot-final.ppm" || true
bash "{{target}}/src/show-screenshot.sh" /tmp/plain-screenshot-final.ppm "Installed boot (plain)" || true
exit 0
fi
sleep 5
done
echo "ERROR: installed system did not reach a ready state after 5m"
serial | tail -50 || true
exit 1