#!/bin/bash # ===================================================================== # Builds the frozen Ubuntu base image, unattended, from the stock ISO. # # sudo ./build.sh # ~25 minutes, mostly apt # # HOW THE INSTALL IS MADE UNATTENDED # # Subiquity will read an autoinstall config from a NoCloud volume, but it # stops to ask for confirmation unless `autoinstall` is on the kernel # command line. There is no way to pass a kernel argument to a CD-ROM, so # the ISO is repacked with that one word added to its grub menu entry. # The alternative - booting the ISO's kernel directly with -kernel - was # rejected because it bypasses OVMF, and an image installed outside UEFI # will not boot under owuivm-launch, which uses it. # # The repack is `xorriso ... -boot_image any replay`, which copies the # original boot records across verbatim. Anything else produces an ISO # that is subtly not bootable, usually only under UEFI, usually only on # the second attempt. # # THE BUILD RUNS AS owuivm ON PURPOSE # # Same uid, same egress filter, same everything as a real session. If the # sandbox's network rules would break `apt`, this is where that is found # - not three phases later with an agent in the loop to confuse it. # ===================================================================== set -euo pipefail cd "$(dirname "$(readlink -f "$0")")" [ "$(id -u)" -eq 0 ] || { echo "run as root" >&2; exit 1; } ISO_DIR=/var/lib/owuivm/iso BASE_DIR=/var/lib/owuivm/base WORK=$(mktemp -d /var/tmp/owuivm-build.XXXXXX) trap 'rm -rf "$WORK"' EXIT SRC_ISO="$ISO_DIR/$(awk '{print $1}' "$ISO_DIR/ubuntu.manifest")" AUTO_ISO="$WORK/ubuntu-autoinstall.iso" SEED_ISO="$WORK/seed.iso" DISK="$BASE_DIR/ubuntu.qcow2" VARS="$BASE_DIR/ubuntu-OVMF_VARS.fd" LOG=/var/log/owuivm-build-ubuntu.log [ -f "$SRC_ISO" ] || { echo "no source ISO - run ../fetch.sh ubuntu first" >&2; exit 1; } if [ -e "$DISK" ]; then echo "$DISK already exists." echo "Refusing to overwrite a base image: unfreeze and edit it through the" echo "maintenance tool, or delete it deliberately and re-run." exit 1 fi echo "==> patching the ISO's boot menu" xorriso -osirrox on -indev "$SRC_ISO" -extract /boot/grub/grub.cfg "$WORK/grub.cfg" >/dev/null 2>&1 # `autoinstall` - skips subiquity's confirmation prompt. # `console=ttyS0`- so the install is watchable in $LOG instead of being a # 25-minute black box that either works or does not. sed -i -e 's|^\(\s*linux\s*/casper/vmlinuz\)\s*---|\1 autoinstall console=ttyS0,115200 ---|' \ -e 's|^set timeout=.*|set timeout=1|' "$WORK/grub.cfg" grep -q 'autoinstall' "$WORK/grub.cfg" || { echo "grub.cfg patch did not apply" >&2; exit 1; } xorriso -indev "$SRC_ISO" -outdev "$AUTO_ISO" \ -boot_image any replay \ -map "$WORK/grub.cfg" /boot/grub/grub.cfg \ -commit >/dev/null 2>&1 [ -s "$AUTO_ISO" ] || { echo "repack produced nothing" >&2; exit 1; } echo "==> building the NoCloud seed" # cloud-init finds this by volume label alone; no ds= argument needed. mkdir -p "$WORK/seed" && cp user-data meta-data "$WORK/seed/" xorriso -as mkisofs -o "$SEED_ISO" -V CIDATA -J -r "$WORK/seed" >/dev/null 2>&1 echo "==> creating the disk" install -d -o owuivmadm -g owuivmadm -m 0755 "$BASE_DIR" qemu-img create -f qcow2 "$DISK" 40G >/dev/null cp /usr/share/OVMF/OVMF_VARS_4M.fd "$VARS" chown owuivm:owuivm "$DISK" "$VARS" chmod 0644 "$DISK" "$VARS" chmod 0777 "$WORK"; chmod 0644 "$AUTO_ISO" "$SEED_ISO" echo "==> installing (watch: tail -f $LOG)" : > "$LOG"; chown owuivm:owuivm "$LOG" # -no-reboot: subiquity reboots when it is done, and that is our signal # that the install finished. Without it the fresh system boots and the # build never returns. setpriv --reuid=owuivm --regid=owuivm --groups=kvm --inh-caps=-all -- \ qemu-system-x86_64 \ -no-user-config -nodefaults -no-reboot \ -machine q35,accel=kvm -cpu host -smp 4 -m 4096 \ -drive "if=pflash,format=raw,unit=0,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd" \ -drive "if=pflash,format=raw,unit=1,file=$VARS" \ -drive "file=$DISK,if=none,id=disk0,format=qcow2,cache=unsafe" \ -device virtio-blk-pci,drive=disk0,bootindex=1 \ -drive "file=$AUTO_ISO,if=none,id=cd0,media=cdrom,readonly=on" \ -device ide-cd,drive=cd0,bus=ide.0,bootindex=2 \ -drive "file=$SEED_ISO,if=none,id=cd1,media=cdrom,readonly=on" \ -device ide-cd,drive=cd1,bus=ide.1 \ -netdev user,id=net0,ipv6=off -device virtio-net-pci,netdev=net0,romfile= \ -device VGA -display none \ -serial "file:$LOG" echo "==> freezing" # The write bit IS the freeze. owuivm is neither the owner nor in the # group, so the uid that runs every session cannot clear it; only the # maintenance unit, which runs as owuivmadm behind a polkit check, can. chown owuivmadm:owuivmadm "$DISK" "$VARS" chmod 0444 "$DISK" "$VARS" sha256sum "$DISK" "$VARS" > "$BASE_DIR/ubuntu.sha256" chown owuivmadm:owuivmadm "$BASE_DIR/ubuntu.sha256" echo echo "base image built and frozen:" ls -l "$DISK" "$VARS" cat "$BASE_DIR/ubuntu.sha256"