-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjustfile
More file actions
704 lines (650 loc) · 24.9 KB
/
justfile
File metadata and controls
704 lines (650 loc) · 24.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
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
#!/usr/bin/env just --justfile
proxy := env_var_or_default('HTTP_PROXY', '')
build_dir := env_var_or_default('BUILD_DIR', '/var/tmp/hypervisor-build')
local_registry := 'registry.local:5000'
tag := `date +%Y%m%d-%H%M`
fedora_version := env_var_or_default('FEDORA_VERSION', `yq '.stable' fedora-versions.yml`)
# Rechunk an image in user storage (copies to root, rechunks, copies back)
_rechunk image:
#!/usr/bin/env bash
set -euo pipefail
echo "Rechunking {{image}}..."
podman save {{image}} | sudo podman load
sudo podman run --rm --privileged \
-v /var/lib/containers:/var/lib/containers \
quay.io/fedora/fedora-bootc:rawhide \
/usr/libexec/bootc-base-imagectl rechunk \
{{image}} \
{{image}}-rechunked
sudo podman save {{image}}-rechunked | podman load
podman tag {{image}}-rechunked {{image}}
podman rmi {{image}}-rechunked
sudo podman rmi {{image}} {{image}}-rechunked || true
echo "Rechunked {{image}}"
# Install git hooks from .githooks/ into .git/hooks/
install-hooks:
#!/usr/bin/env bash
set -euo pipefail
for hook in .githooks/*; do
name=$(basename "$hook")
install -m 0755 "$hook" ".git/hooks/$name"
echo "installed .git/hooks/$name"
done
# === Container image builds =================================================
build-minimal version=fedora_version rechunk="false":
#!/usr/bin/env bash
set -euo pipefail
if [ ! -d "manifests" ]; then
echo "Cloning Fedora bootc manifests..."
git clone --depth 1 https://gitlab.com/fedora/bootc/base-images.git manifests
fi
cp policy-local.json manifests/policy.json
echo "Building fedora-bootc-minimal:{{version}}..."
cd manifests
FEDORA_VERSION={{version}} TIER=minimal BUILDER="sudo podman" \
BUILDER_EXTRA="--network=host --env=http_proxy={{proxy}} --env=https_proxy={{proxy}}" \
http_proxy={{proxy}} https_proxy={{proxy}} \
just build
cd ..
# Upstream tags as localhost/fedora-bootc:minimal — add our tags
sudo podman tag localhost/fedora-bootc:minimal \
localhost/fedora-bootc-minimal:{{version}}-{{tag}} \
localhost/fedora-bootc-minimal:{{version}} \
localhost/fedora-bootc-minimal:latest \
ghcr.io/bensmith/fedora-bootc-minimal:{{version}} \
ghcr.io/bensmith/fedora-bootc-minimal:latest
if [ "{{rechunk}}" == "true" ]; then
echo "Rechunking image..."
sudo podman run --rm --privileged \
-v /var/lib/containers:/var/lib/containers \
quay.io/fedora/fedora-bootc:{{version}} \
/usr/libexec/bootc-base-imagectl rechunk \
localhost/fedora-bootc-minimal:{{version}}-{{tag}} \
localhost/fedora-bootc-minimal:rechunked
sudo podman tag localhost/fedora-bootc-minimal:rechunked \
localhost/fedora-bootc-minimal:{{version}}-{{tag}} \
localhost/fedora-bootc-minimal:{{version}} \
localhost/fedora-bootc-minimal:latest
sudo podman rmi localhost/fedora-bootc-minimal:rechunked
fi
echo "Copying image to user storage..."
sudo podman save localhost/fedora-bootc-minimal:{{version}} | podman load
sudo podman save localhost/fedora-bootc-minimal:latest | podman load
echo "Build complete: localhost/fedora-bootc-minimal:{{version}}"
sync-cosy:
#!/usr/bin/env bash
set -euo pipefail
mkdir -p bin man
if [ -f ../cosy/src/cosy ] && [ -f ../cosy/src/cosy.1 ]; then
cp ../cosy/src/cosy bin/cosy
cp ../cosy/src/cosy.1 man/cosy.1
else
echo "Local cosy not found, fetching from GitHub..."
curl -fsSL https://raw.githubusercontent.com/BenSmith/cosy/main/cosy -o bin/cosy
curl -fsSL https://raw.githubusercontent.com/BenSmith/cosy/main/cosy.1 -o man/cosy.1
fi
build-base: sync-cosy
#!/usr/bin/env bash
set -euo pipefail
cp policy-local.json policy.json
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
--build-arg BASE_IMAGE=ghcr.io/bensmith/fedora-bootc-minimal:{{fedora_version}} \
-t localhost/hypervisor-bootc:{{fedora_version}}-{{tag}} \
-t localhost/hypervisor-bootc:{{fedora_version}} \
-t localhost/hypervisor-bootc:latest \
-t {{local_registry}}/hypervisor-bootc:latest \
-t ghcr.io/bensmith/hypervisor-bootc:{{fedora_version}}-{{tag}} \
-t ghcr.io/bensmith/hypervisor-bootc:latest \
-f hypervisor.Containerfile .
build-base-local: sync-cosy
#!/usr/bin/env bash
set -euo pipefail
cp policy-local.json policy.json
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--network=host \
--from localhost/fedora-bootc-minimal:{{fedora_version}} \
--build-arg ENABLE_PASSWORDLESS_SUDO=true \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
-t localhost/hypervisor-bootc:{{fedora_version}} \
-t localhost/hypervisor-bootc:latest \
-t {{local_registry}}/hypervisor-bootc:latest \
-f hypervisor.Containerfile .
build-nvidia-rpmfusion:
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--pull=never \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
--build-arg BASE=localhost/hypervisor-bootc:{{fedora_version}} \
-t localhost/hypervisor-nvidia:rpmfusion-{{fedora_version}}-{{tag}} \
-t localhost/hypervisor-nvidia:rpmfusion-{{fedora_version}} \
-t localhost/hypervisor-nvidia:rpmfusion \
-t ghcr.io/bensmith/hypervisor-nvidia:rpmfusion-{{fedora_version}}-{{tag}} \
-t ghcr.io/bensmith/hypervisor-nvidia:rpmfusion \
-t {{local_registry}}/hypervisor-nvidia:rpmfusion \
-f hypervisor-nvidia-rpmfusion.Containerfile .
build-nvidia-negativo17:
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--pull=never \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
--build-arg BASE=localhost/hypervisor-bootc:{{fedora_version}} \
-t localhost/hypervisor-nvidia:negativo17-{{fedora_version}}-{{tag}} \
-t localhost/hypervisor-nvidia:negativo17-{{fedora_version}} \
-t localhost/hypervisor-nvidia:negativo17 \
-t ghcr.io/bensmith/hypervisor-nvidia:negativo17-{{fedora_version}}-{{tag}} \
-t ghcr.io/bensmith/hypervisor-nvidia:negativo17 \
-t {{local_registry}}/hypervisor-nvidia:negativo17 \
-f hypervisor-nvidia-negativo17.Containerfile .
build-amd:
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--pull=never \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
--build-arg BASE=localhost/hypervisor-bootc:{{fedora_version}} \
-t localhost/hypervisor-amd:{{fedora_version}}-{{tag}} \
-t localhost/hypervisor-amd:{{fedora_version}} \
-t localhost/hypervisor-amd:latest \
-t {{local_registry}}/hypervisor-amd:latest \
-f hypervisor-amd.Containerfile .
# Local variants - build from locally-built base instead of GHCR
build-amd-local:
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--network=host \
--from localhost/hypervisor-bootc:{{fedora_version}} \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
-t localhost/hypervisor-amd:{{fedora_version}} \
-t localhost/hypervisor-amd:latest \
-t {{local_registry}}/hypervisor-amd:latest \
-f hypervisor-amd.Containerfile .
build-nvidia-rpmfusion-local: build-base-local
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--network=host \
--from localhost/hypervisor-bootc:{{fedora_version}} \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
-t localhost/hypervisor-nvidia:rpmfusion-{{fedora_version}} \
-t localhost/hypervisor-nvidia:rpmfusion \
-f hypervisor-nvidia-rpmfusion.Containerfile .
build-nvidia-negativo17-local: build-base-local
http_proxy={{proxy}} https_proxy={{proxy}} \
podman build \
--network=host \
--from localhost/hypervisor-bootc:{{fedora_version}} \
--env=http_proxy={{proxy}} --env=https_proxy={{proxy}} \
-t localhost/hypervisor-nvidia:negativo17-{{fedora_version}} \
-t localhost/hypervisor-nvidia:negativo17 \
-f hypervisor-nvidia-negativo17.Containerfile .
build-all: build-base build-nvidia-rpmfusion build-nvidia-negativo17 build-amd
build-all-local: build-base-local build-amd-local build-nvidia-rpmfusion-local build-nvidia-negativo17-local
# Tag and push all locally-built images to a registry
# Usage: just local_registry=registry.local:5000 push-all
push-all:
#!/usr/bin/env bash
set -euo pipefail
images=(
hypervisor-bootc:latest
hypervisor-amd:latest
hypervisor-nvidia:rpmfusion
hypervisor-nvidia:negativo17
)
for img in "${images[@]}"; do
echo "Tagging and pushing ${img}..."
podman tag "localhost/${img}" "{{local_registry}}/${img}"
podman push "{{local_registry}}/${img}"
done
echo "All images pushed to {{local_registry}}"
# === Disk image builds (ISO / qcow2) ========================================
# Internal: build an anaconda ISO from a bootc image
_build-iso image subdir label iso_name rootfs:
#!/usr/bin/env bash
set -euo pipefail
mkdir -p {{build_dir}}/store {{build_dir}}/output/{{subdir}} {{build_dir}}/rpmmd
echo "Pulling image..."
sudo podman pull {{image}}
sudo podman run \
--privileged --pull=newer --rm \
--security-opt label=type:unconfined_t \
-v $(pwd)/config-iso.toml:/config.toml:ro \
-v {{build_dir}}/output/{{subdir}}:/output \
-v {{build_dir}}/rpmmd:/rpmmd \
-v {{build_dir}}/store:/store \
-v /var/lib/containers/storage:/var/lib/containers/storage \
quay.io/centos-bootc/bootc-image-builder:latest build \
--chown $(id -u):$(id -g) \
--output /output \
--rootfs {{rootfs}} \
--rpmmd /rpmmd \
--store /store \
--type anaconda-iso \
{{image}}
echo "Relabeling ISO..."
just relabel-iso \
{{build_dir}}/output/{{subdir}}/bootiso/install.iso \
{{build_dir}}/output/{{iso_name}}-{{tag}}.iso \
"{{label}}"
echo "ISO ready: {{build_dir}}/output/{{iso_name}}-{{tag}}.iso (label: {{label}})"
build-iso-minimal rootfs="xfs":
@just _build-iso ghcr.io/bensmith/fedora-bootc-minimal:latest minimal BOOTC-MIN fedora-bootc-minimal {{rootfs}}
build-iso-minimal-local rootfs="xfs":
@just _build-iso {{local_registry}}/fedora-bootc-minimal:latest minimal BOOTC-MIN fedora-bootc-minimal {{rootfs}}
build-iso-base rootfs="xfs":
@just _build-iso ghcr.io/bensmith/hypervisor-bootc:latest base HV-BASE hypervisor-bootc {{rootfs}}
build-iso-base-local rootfs="xfs":
@just _build-iso {{local_registry}}/hypervisor-bootc:latest base HV-BASE hypervisor-bootc {{rootfs}}
build-iso-nvidia-rpmfusion rootfs="xfs":
@just _build-iso ghcr.io/bensmith/hypervisor-nvidia:rpmfusion nvidia-rpmfusion HV-NV-RPMFUSION hypervisor-nvidia-rpmfusion {{rootfs}}
build-iso-nvidia-rpmfusion-local rootfs="xfs":
@just _build-iso {{local_registry}}/hypervisor-nvidia:rpmfusion-latest nvidia-rpmfusion HV-NV-RPMFUSION hypervisor-nvidia-rpmfusion {{rootfs}}
build-iso-nvidia-negativo17 rootfs="xfs":
@just _build-iso ghcr.io/bensmith/hypervisor-nvidia:negativo17 nvidia-negativo17 HV-NV-NEG17 hypervisor-nvidia-negativo17 {{rootfs}}
build-iso-nvidia-negativo17-local rootfs="xfs":
@just _build-iso {{local_registry}}/hypervisor-nvidia:negativo17-latest nvidia-negativo17 HV-NV-NEG17 hypervisor-nvidia-negativo17 {{rootfs}}
build-iso-amd rootfs="xfs":
@just _build-iso ghcr.io/bensmith/hypervisor-amd:latest amd HV-AMD hypervisor-amd {{rootfs}}
build-iso-amd-local rootfs="xfs":
@just _build-iso {{local_registry}}/hypervisor-amd:latest amd HV-AMD hypervisor-amd {{rootfs}}
build-all-isos rootfs="xfs":
#!/usr/bin/env bash
rc=0
just build-iso-minimal {{rootfs}} || rc=1
just build-iso-base {{rootfs}} || rc=1
just build-iso-nvidia-rpmfusion {{rootfs}} || rc=1
just build-iso-nvidia-negativo17 {{rootfs}} || rc=1
just build-iso-amd {{rootfs}} || rc=1
exit $rc
build-all-isos-local rootfs="xfs":
#!/usr/bin/env bash
rc=0
just build-iso-minimal-local {{rootfs}} || rc=1
just build-iso-base-local {{rootfs}} || rc=1
just build-iso-nvidia-rpmfusion-local {{rootfs}} || rc=1
just build-iso-nvidia-negativo17-local {{rootfs}} || rc=1
just build-iso-amd-local {{rootfs}} || rc=1
exit $rc
# Generate config.toml for bootc-image-builder (VM user credentials)
# Reads from env vars: VM_USER (default: current user), VM_PASSWORD (default: random),
# VM_SSH_KEY (default: first key in ~/.ssh/)
_generate-vm-config:
#!/usr/bin/env bash
set -euo pipefail
if [ -f config.toml ]; then
exit 0
fi
user="${VM_USER:-$(whoami)}"
password="${VM_PASSWORD:-}"
ssh_key="${VM_SSH_KEY:-}"
if [ -z "$ssh_key" ]; then
for f in ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub ~/.ssh/id_ecdsa.pub; do
if [ -f "$f" ]; then
ssh_key=$(cat "$f")
echo "Using SSH key from $f"
break
fi
done
fi
if [ -z "$password" ]; then
echo "Error: VM_PASSWORD must be set (no config.toml found)"
echo "Usage: VM_PASSWORD=mypass just <recipe>"
exit 1
fi
{
echo '[[customizations.user]]'
echo "name = \"$user\""
echo "password = \"$password\""
echo 'groups = ["wheel"]'
if [ -n "$ssh_key" ]; then
echo "key = \"$ssh_key\""
fi
} > config.toml
echo "Generated config.toml for user '$user'"
# Internal: build a qcow2 disk image from a bootc image
_build-qcow2 image rootfs size="": _generate-vm-config
#!/usr/bin/env bash
set -euo pipefail
mkdir -p {{build_dir}}/store {{build_dir}}/output/qcow2 {{build_dir}}/rpmmd
echo "Pulling image..."
sudo podman pull {{image}}
sudo podman run \
--privileged --pull=newer --rm \
--security-opt label=type:unconfined_t \
-v $(pwd)/config.toml:/config.toml:ro \
-v {{build_dir}}/output/qcow2:/output \
-v {{build_dir}}/rpmmd:/rpmmd \
-v {{build_dir}}/store:/store \
-v /var/lib/containers/storage:/var/lib/containers/storage \
quay.io/centos-bootc/bootc-image-builder:latest build \
--chown $(id -u):$(id -g) \
--output /output \
--rootfs {{rootfs}} \
--rpmmd /rpmmd \
--store /store \
--type qcow2 \
{{image}}
if [ -n "{{size}}" ]; then
echo "Resizing disk to {{size}}..."
qemu-img resize {{build_dir}}/output/qcow2/qcow2/disk.qcow2 {{size}}
fi
echo "QCOW2 ready: {{build_dir}}/output/qcow2/qcow2/disk.qcow2"
echo "To use: sudo cp {{build_dir}}/output/qcow2/qcow2/disk.qcow2 /var/lib/libvirt/images/hypervisor-{{tag}}.qcow2"
build-qcow2-base rootfs="xfs":
@just _build-qcow2 ghcr.io/bensmith/hypervisor-bootc:latest {{rootfs}}
build-qcow2-base-local rootfs="xfs" size="20G":
@just _build-qcow2 {{local_registry}}/hypervisor-bootc:latest {{rootfs}} {{size}}
# === Relabel ISO ============================================================
relabel-iso input output label:
#!/usr/bin/env bash
set -euo pipefail
echo "Relabeling {{input}} -> {{output}} with label '{{label}}'"
TMPDIR=$(mktemp -d)
trap "sudo rm -rf $TMPDIR" EXIT
MOUNT_DIR="$TMPDIR/mount"
mkdir -p "$MOUNT_DIR"
sudo mount -o loop,ro "{{input}}" "$MOUNT_DIR"
WORK_DIR="$TMPDIR/iso"
mkdir -p "$WORK_DIR"
sudo cp -a "$MOUNT_DIR"/* "$WORK_DIR/" 2>/dev/null || true
sudo cp -a "$MOUNT_DIR"/.[!.]* "$WORK_DIR/" 2>/dev/null || true
sudo umount "$MOUNT_DIR"
ORIG_LABEL=$(isoinfo -d -i "{{input}}" | grep "Volume id:" | sed 's/Volume id: //')
echo "Original label: $ORIG_LABEL"
echo "New label: {{label}}"
for grub_cfg in "$WORK_DIR/boot/grub2/grub.cfg" "$WORK_DIR/EFI/BOOT/grub.cfg"; do
if [ -f "$grub_cfg" ]; then
echo "Updating $grub_cfg..."
sudo sed -i "s/$ORIG_LABEL/{{label}}/g" "$grub_cfg"
fi
done
sudo xorriso -as mkisofs \
-V "{{label}}" \
-r -J \
-b images/eltorito.img \
-c boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-eltorito-alt-boot \
-e images/efiboot.img \
-no-emul-boot \
-o "{{output}}" \
"$WORK_DIR"
sudo chown $(id -u):$(id -g) "{{output}}"
echo "ISO relabeled successfully: {{output}}"
# === All-in-one local dev ===================================================
aio-local vmname="hypervisor-test" memory="4096" vcpus="2" rootfs="xfs" size="20G":
#!/usr/bin/env bash
set -euo pipefail
echo "=== Step 1: Building container image ==="
just build-base-local
echo ""
echo "=== Step 1.5: Pushing image to local registry ==="
podman push {{local_registry}}/hypervisor-bootc:latest
echo ""
echo "=== Step 2: Building qcow2 disk image ==="
just build-qcow2-base-local {{rootfs}} {{size}}
echo ""
echo "=== Step 3: Deploying to VM '{{vmname}}' ==="
sudo mkdir -p /var/lib/libvirt/images
sudo cp {{build_dir}}/output/qcow2/qcow2/disk.qcow2 /var/lib/libvirt/images/{{vmname}}-{{tag}}.qcow2
if sudo virsh dominfo {{vmname}} &>/dev/null; then
echo "Destroying existing VM '{{vmname}}'..."
sudo virsh destroy {{vmname}} 2>/dev/null || true
sudo virsh undefine {{vmname}}
fi
echo "Creating VM '{{vmname}}' ({{memory}}MB RAM, {{vcpus}} vCPUs)..."
sudo virt-install \
--name {{vmname}} \
--memory {{memory}} \
--vcpus {{vcpus}} \
--disk path=/var/lib/libvirt/images/{{vmname}}-{{tag}}.qcow2,format=qcow2 \
--import \
--os-variant fedora41 \
--network network=default \
--graphics spice,gl.enable=yes,listen=none \
--video virtio \
--noautoconsole
echo ""
echo "=== Waiting for VM to boot and obtain IP address ==="
timeout=60
elapsed=0
ip_addr=""
while [ $elapsed -lt $timeout ]; do
sleep 2
elapsed=$((elapsed + 2))
ip_line=$(sudo virsh domifaddr {{vmname}} 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1 || true)
if [ -n "$ip_line" ]; then
ip_addr="$ip_line"
break
fi
echo -n "."
done
echo ""
echo ""
echo "=== Deployment complete! ==="
echo "VM name: {{vmname}}"
echo "Disk: /var/lib/libvirt/images/{{vmname}}-{{tag}}.qcow2"
if [ -n "$ip_addr" ]; then
echo "IP address: $ip_addr"
echo ""
echo "To connect:"
echo " ssh ben@$ip_addr"
echo " sudo virsh console {{vmname}} # Serial console (Ctrl+] to exit)"
else
echo "IP address: (timeout waiting for DHCP - check with: sudo virsh domifaddr {{vmname}})"
echo ""
echo "To connect:"
echo " sudo virsh domifaddr {{vmname}} # Get IP address"
echo " sudo virsh console {{vmname}} # Serial console (Ctrl+] to exit)"
fi
echo ""
echo "VM management:"
echo " sudo virsh start {{vmname}} # Start VM"
echo " sudo virsh shutdown {{vmname}} # Graceful shutdown"
echo " sudo virsh destroy {{vmname}} # Force power off"
echo " sudo virsh undefine {{vmname}} # Delete VM config"
# === Tests ==================================================================
test:
cd workloadctl && just test
test-unit:
cd workloadctl && just test-unit
test-integration:
cd workloadctl && just test-integration
# Build workloadctl RPM
workload-rpm:
cd workloadctl && just rpm-build
# --- VM integration tests (requires sudo, QEMU, swtpm) ---------------------
# Build the test VM image: base hypervisor image + test workload configs
test-vm-build:
#!/usr/bin/env bash
set -euo pipefail
# Generate ephemeral config with known test credentials (must match sshpass in test-vm)
TEST_CONFIG=$(mktemp)
trap "rm -f $TEST_CONFIG" EXIT
printf '%s\n' '[[customizations.user]]' 'name = "ben"' 'password = "password123"' 'groups = ["wheel"]' > "$TEST_CONFIG"
echo "=== Building base image (if needed) ==="
if ! podman image exists localhost/hypervisor-bootc:latest; then
echo "Base image not found. Building with: just build-base-local"
just build-base-local
fi
echo ""
echo "=== Building test VM image ==="
podman build --no-cache \
-f tests/vm/test-vm.Containerfile \
-t localhost/hypervisor-test:latest \
tests/vm/
echo ""
echo "=== Copying image to rootful storage ==="
podman save localhost/hypervisor-test:latest | sudo podman load
echo ""
echo "=== Building qcow2 disk ==="
mkdir -p {{build_dir}}/store {{build_dir}}/output/test-vm {{build_dir}}/rpmmd
sudo podman run \
--privileged --pull=newer --rm \
--security-opt label=type:unconfined_t \
-v "$TEST_CONFIG":/config.toml:ro \
-v {{build_dir}}/output/test-vm:/output \
-v {{build_dir}}/rpmmd:/rpmmd \
-v {{build_dir}}/store:/store \
-v /var/lib/containers/storage:/var/lib/containers/storage \
quay.io/centos-bootc/bootc-image-builder:latest build \
--chown $(id -u):$(id -g) \
--output /output \
--rootfs xfs \
--rpmmd /rpmmd \
--store /store \
--type qcow2 \
localhost/hypervisor-test:latest
echo ""
echo "Test VM disk ready: {{build_dir}}/output/test-vm/qcow2/disk.qcow2"
# Boot test VM interactively for debugging (graphics + no auto-teardown)
test-vm-debug memory="4096" vcpus="2":
#!/usr/bin/env bash
set -euo pipefail
VMNAME="workload-test-debug"
DISK="{{build_dir}}/output/test-vm/qcow2/disk.qcow2"
if [ ! -f "$DISK" ]; then
echo "Test VM disk not found. Run: just test-vm-build"
exit 1
fi
DISK_PATH="/var/lib/libvirt/images/${VMNAME}.qcow2"
sudo virsh destroy "$VMNAME" 2>/dev/null || true
sudo virsh undefine "$VMNAME" --nvram 2>/dev/null || true
sudo rm -f "$DISK_PATH"
sudo cp "$DISK" "$DISK_PATH"
sudo qemu-img resize "$DISK_PATH" 20G
echo "=== Creating debug VM '$VMNAME' ==="
sudo virt-install \
--name "$VMNAME" \
--memory {{memory}} \
--vcpus {{vcpus}} \
--disk "path=$DISK_PATH,format=qcow2" \
--import \
--os-variant fedora41 \
--network network=default \
--tpm backend.type=emulator,backend.version=2.0,model=tpm-tis \
--graphics spice,gl.enable=yes,listen=none \
--video virtio \
--noautoconsole
echo ""
echo "VM '$VMNAME' is running. Connect with:"
echo " sudo virt-viewer $VMNAME"
echo ""
echo "When done:"
echo " sudo virsh destroy $VMNAME"
echo " sudo virsh undefine $VMNAME --nvram"
echo " sudo rm /var/lib/libvirt/images/${VMNAME}.qcow2"
# Run VM integration tests: boot a test VM with libvirt+SWTPM, run tests via SSH, tear down
test-vm timeout="300" memory="4096" vcpus="2" console="false":
#!/usr/bin/env bash
set -euo pipefail
VMNAME="workload-test-$$"
DISK="{{build_dir}}/output/test-vm/qcow2/disk.qcow2"
if [ ! -f "$DISK" ]; then
echo "Test VM disk not found. Run: just test-vm-build"
exit 1
fi
DISK_PATH="/var/lib/libvirt/images/${VMNAME}.qcow2"
CONSOLE_LOG="/tmp/${VMNAME}-console.log"
cleanup() {
echo "Cleaning up..."
[ -n "${TAIL_PID:-}" ] && kill "$TAIL_PID" 2>/dev/null || true
sudo virsh destroy "$VMNAME" 2>/dev/null || true
sudo virsh undefine "$VMNAME" --nvram 2>/dev/null || true
sudo rm -f "$DISK_PATH"
sudo rm -f "$CONSOLE_LOG"
}
trap cleanup EXIT
echo "=== Preparing test VM disk ==="
sudo cp "$DISK" "$DISK_PATH"
sudo qemu-img resize "$DISK_PATH" 20G
echo "=== Creating test VM '$VMNAME' ==="
sudo virt-install \
--name "$VMNAME" \
--memory {{memory}} \
--vcpus {{vcpus}} \
--disk "path=$DISK_PATH,format=qcow2" \
--import \
--os-variant fedora41 \
--network network=default \
--tpm backend.type=emulator,backend.version=2.0,model=tpm-tis \
--serial file,path="$CONSOLE_LOG" \
--graphics spice,gl.enable=yes,listen=none \
--video virtio \
--noautoconsole
if [ "{{console}}" = "true" ]; then
sudo tail -f "$CONSOLE_LOG" 2>/dev/null &
TAIL_PID=$!
fi
echo "Waiting for VM IP..."
elapsed=0
ip_addr=""
while [ $elapsed -lt {{timeout}} ]; do
ip_addr=$(sudo virsh domifaddr "$VMNAME" 2>/dev/null \
| grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1 || true)
if [ -n "$ip_addr" ]; then
break
fi
sleep 5
elapsed=$((elapsed + 5))
echo -n "."
done
echo ""
if [ -z "$ip_addr" ]; then
echo "FAIL: VM did not get an IP after {{timeout}}s"
echo ""
echo "Console log: $CONSOLE_LOG"
echo "Debug with: sudo virt-viewer $VMNAME"
echo "Cleanup: sudo virsh destroy $VMNAME && sudo virsh undefine $VMNAME --nvram"
[ -n "${TAIL_PID:-}" ] && kill "$TAIL_PID" 2>/dev/null || true
trap - EXIT
exit 1
fi
echo "VM IP: $ip_addr"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5"
if command -v sshpass &>/dev/null; then
SSH="sshpass -p password123 ssh $SSH_OPTS ben@$ip_addr"
else
echo "WARNING: sshpass not installed. Install with: sudo dnf install sshpass"
echo "Falling back to key-based auth"
SSH="ssh $SSH_OPTS ben@$ip_addr"
fi
echo "Waiting for SSH..."
while [ $elapsed -lt {{timeout}} ]; do
if $SSH true 2>/dev/null; then
break
fi
sleep 5
elapsed=$((elapsed + 5))
echo -n "."
done
echo ""
if [ $elapsed -ge {{timeout}} ]; then
echo "FAIL: SSH not available after {{timeout}}s"
echo ""
echo "Console log: $CONSOLE_LOG"
echo "Debug with: sudo virt-viewer $VMNAME"
echo "Cleanup: sudo virsh destroy $VMNAME && sudo virsh undefine $VMNAME --nvram"
[ -n "${TAIL_PID:-}" ] && kill "$TAIL_PID" 2>/dev/null || true
trap - EXIT
exit 1
fi
echo "SSH available after ${elapsed}s"
sleep 2
echo ""
echo "=== Running VM tests ==="
if $SSH "sudo /usr/local/bin/run-vm-tests"; then
echo ""
echo "=== VM tests PASSED ==="
RC=0
else
echo ""
echo "=== VM tests FAILED ==="
RC=1
fi
echo "Shutting down VM..."
$SSH "sudo poweroff" 2>/dev/null || true
sleep 3
exit $RC