run-vm
| 1 | #!/bin/sh |
| 2 | # Entrypoint of the build VM container. It packs /in into the job disk, boots |
| 3 | # the VM, and leaves the image at /out/image.tar. |
| 4 | # |
| 5 | # Exit codes: 0 image written, 1 build failed, 2 the VM did not run, |
| 6 | # 3 image over HF_VM_MAX_IMAGE_BYTES. |
| 7 | set -eu |
| 8 | |
| 9 | cpus=${HF_VM_CPUS:-2} |
| 10 | mem=${HF_VM_MEMORY_MB:-2048} |
| 11 | disk=${HF_VM_DISK_BYTES:-21474836480} |
| 12 | max=${HF_VM_MAX_IMAGE_BYTES:-4294967296} |
| 13 | |
| 14 | if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then |
| 15 | echo "hearthforge: /dev/kvm is missing or not writable in the build VM container" |
| 16 | exit 2 |
| 17 | fi |
| 18 | |
| 19 | tar -c -f /work/job.tar -C /in . |
| 20 | truncate -s "$disk" /work/scratch.img |
| 21 | : > /out/image.tar |
| 22 | |
| 23 | arch=$(uname -m) |
| 24 | case $arch in |
| 25 | x86_64) |
| 26 | # acpi=off: QEMU puts a large initramfs over the microvm ACPI tables, and |
| 27 | # the kernel hangs while it parses them. |
| 28 | machine=microvm,accel=kvm,acpi=off,pit=off,pic=off,isa-serial=on,rtc=on |
| 29 | console=ttyS0 |
| 30 | ;; |
| 31 | aarch64) |
| 32 | machine=virt,accel=kvm,gic-version=host |
| 33 | console=ttyAMA0 |
| 34 | ;; |
| 35 | *) |
| 36 | echo "hearthforge: unsupported architecture $arch" |
| 37 | exit 2 |
| 38 | ;; |
| 39 | esac |
| 40 | |
| 41 | # The serial numbers let the guest find its disks whatever order the |
| 42 | # virtio-mmio transports probe in. |
| 43 | qemu-system-"$arch" \ |
| 44 | -nodefaults -no-user-config -display none -no-reboot \ |
| 45 | -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ |
| 46 | -machine "$machine" -cpu host -smp "$cpus" -m "$mem" \ |
| 47 | -kernel /vm/vmlinuz -initrd /vm/initramfs.gz \ |
| 48 | -append "console=$console quiet panic=-1 reboot=t" \ |
| 49 | -serial stdio \ |
| 50 | -drive if=none,id=job,file=/work/job.tar,format=raw,readonly=on \ |
| 51 | -device virtio-blk-device,drive=job,serial=hfjob \ |
| 52 | -drive if=none,id=scratch,file=/work/scratch.img,format=raw,cache=unsafe,discard=unmap \ |
| 53 | -device virtio-blk-device,drive=scratch,serial=hfscratch \ |
| 54 | -netdev user,id=net -device virtio-net-device,netdev=net \ |
| 55 | -device virtio-rng-device \ |
| 56 | -device virtio-serial-device \ |
| 57 | -chardev file,id=image,path=/out/image.tar \ |
| 58 | -device virtserialport,chardev=image,name=hf.image & |
| 59 | qemu=$! |
| 60 | |
| 61 | # The guest controls how much it writes. Polling lets it overshoot by about |
| 62 | # one second of writes, which the engine's disk absorbs. |
| 63 | over=0 |
| 64 | while kill -0 "$qemu" 2>/dev/null; do |
| 65 | if [ "$(wc -c < /out/image.tar)" -gt "$max" ]; then |
| 66 | over=1 |
| 67 | # QEMU may have exited since the check. |
| 68 | kill "$qemu" 2>/dev/null || true |
| 69 | break |
| 70 | fi |
| 71 | sleep 1 |
| 72 | done |
| 73 | status=0 |
| 74 | wait "$qemu" || status=$? |
| 75 | |
| 76 | size=$(wc -c < /out/image.tar) |
| 77 | if [ "$over" = 1 ] || [ "$size" -gt "$max" ]; then |
| 78 | : > /out/image.tar |
| 79 | echo "hearthforge: the image is larger than $max bytes" |
| 80 | exit 3 |
| 81 | fi |
| 82 | if [ "$status" != 0 ]; then |
| 83 | echo "hearthforge: QEMU exited with status $status" |
| 84 | exit 2 |
| 85 | fi |
| 86 | [ "$size" -gt 0 ] || exit 1 |
| 87 |