#!/bin/bash # Unattended install of an Omarchy ISO into a qcow2, through a cidata drive. # # vm-install # # out_dir gets base.qcow2 (the installed disk, powered off), OVMF_VARS.4m.fd # and the key that can log in to it. vm-seal turns that into a reusable image. # Same answer files and VM shape as omarchy-iso's test/integration.d/base-test.sh. set -uo pipefail [[ $# -eq 2 ]] || { echo "Usage: vm-install " >&2; exit 2; } ISO=$(realpath "$1"); OUT=$(realpath -m "$2"); W=$OUT/work # Arch and Ubuntu keep the firmware in different places. for fw in /usr/share/edk2/x64/OVMF_CODE.4m.fd:/usr/share/edk2/x64/OVMF_VARS.4m.fd /usr/share/OVMF/OVMF_CODE_4M.fd:/usr/share/OVMF/OVMF_VARS_4M.fd; do [[ -f ${fw%%:*} && -f ${fw##*:} ]] && { FW_CODE=${fw%%:*}; FW_VARS=${fw##*:}; break; } done : "${FW_CODE:?no OVMF firmware found}" [[ -r /dev/kvm && -w /dev/kvm ]] || { echo "vm-install: /dev/kvm is not usable" >&2; exit 1; } GUEST_USER=omarchy; GUEST_PASSWORD=omarchy; GUEST_HOSTNAME=omarchy-vm; SSH_PORT=2422 rm -rf "$W"; mkdir -p "$W/cidata" "$W/shots"; t0=$(date +%s); el() { echo $(( $(date +%s) - t0 )); } ssh-keygen -q -t ed25519 -N '' -C omarchy-vm-build -f "$W/id_ed25519" hash=$(openssl passwd -6 "$GUEST_PASSWORD") disk_bytes=$((40*1024**3)); mib=$((1024**2)); boot_start=$mib; boot_size=$((2*1024**3)); main_start=$((boot_size+boot_start)); main_size=$((disk_bytes-main_start-mib)) jq -n --arg h "$hash" --arg u "$GUEST_USER" '{root_enc_password:$h, users:[{enc_password:$h, groups:[], sudo:true, username:$u}]}' > "$W/cidata/user_credentials.json" cat > "$W/cidata/user_configuration.json" </dev/null || { echo "bad json"; exit 1; } echo "Omarchy Test" > "$W/cidata/user_full_name.txt"; echo "test@omarchy.org" > "$W/cidata/user_email_address.txt"; echo false > "$W/cidata/user_encrypt_installation.txt" cp "$W/id_ed25519.pub" "$W/cidata/authorized_keys" truncate -s 4M "$W/cidata.img"; mkfs.vfat -n CIDATA "$W/cidata.img" >/dev/null; mcopy -i "$W/cidata.img" "$W"/cidata/* ::/ qemu-img create -f qcow2 "$W/disk.qcow2" 40G >/dev/null; cp "$FW_VARS" "$W/OVMF_VARS.4m.fd" qemu-system-x86_64 -cpu host -enable-kvm -machine q35,accel=kvm -smp 8 -m 8G \ -drive if=pflash,format=raw,readonly=on,file="$FW_CODE" -drive if=pflash,format=raw,file="$W/OVMF_VARS.4m.fd" \ -drive file="$W/disk.qcow2",format=qcow2,if=none,id=drive0 -device virtio-blk-pci,drive=drive0,bootindex=1 \ -device virtio-vga -display none -usb -device usb-tablet \ -netdev user,id=net0,hostfwd=tcp:127.0.0.1:$SSH_PORT-:22 -device virtio-net-pci,netdev=net0 \ -qmp "unix:$W/qmp.sock,server,nowait" -serial "file:$W/serial.log" -pidfile "$W/qemu.pid" -daemonize \ -drive "file=$ISO,media=cdrom,if=none,format=raw,id=cdrom0,readonly=on" -device ide-cd,drive=cdrom0,bootindex=2 \ -drive "file=$W/cidata.img,format=raw,if=none,id=cidata" -device usb-storage,drive=cidata || exit 1 qmp() { printf '{"execute":"qmp_capabilities"}\n%s\n' "$1" | socat - "unix-connect:$W/qmp.sock" >/dev/null 2>&1; } shot() { qmp "{\"execute\":\"screendump\",\"arguments\":{\"filename\":\"$W/shots/$1.png\",\"format\":\"png\"}}"; } sshg() { ssh -i "$W/id_ed25519" -p $SSH_PORT -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 -o LogLevel=ERROR $GUEST_USER@127.0.0.1 "$@"; } echo "install started"; n=0 while :; do sshg true 2>/dev/null && { echo "ssh_up $(el)s"; break; } kill -0 "$(cat "$W/qemu.pid")" 2>/dev/null || { echo "qemu exited at $(el)s"; exit 1; } (( $(el) > 2400 )) && { shot timeout; echo "timeout"; exit 1; } n=$((n+1)); (( n % 6 == 0 )) && { shot "$(printf 'p-%04d' "$(el)")"; sleep 1 txt=$(tesseract "$W/shots/$(printf 'p-%04d' "$(el)")".png - 2>/dev/null || true) grep -qi "Reboot Now" <<<"$txt" && { echo "reboot prompt $(el)s"; qmp '{"execute":"send-key","arguments":{"keys":[{"type":"qcode","data":"ret"}]}}'; } grep -qi "installation stopped" <<<"$txt" && { echo "install stopped $(el)s"; exit 1; } echo "... $(el)s"; } sleep 10 done sshg 'timeout 180 systemctl is-system-running --wait; systemd-analyze; cat /etc/os-release | head -3; pacman -Q omarchy linux-omarchy 2>&1; cat ~/.local/share/omarchy/version 2>/dev/null' > "$W/first-boot.txt" 2>&1 sshg 'sudo -n cat /var/log/omarchy-install-timing.json' > "$W/install-timing.json" 2>/dev/null shot first-boot; echo "first_boot_checked $(el)s"; cat "$W/first-boot.txt" # Power off cleanly, so the disk is consistent, and leave the three files vm-seal reads. printf '%s\n' "$GUEST_PASSWORD" | sshg "sudo -S -p '' systemctl poweroff" 2>/dev/null || true pid=$(cat "$W/qemu.pid"); for i in $(seq 1 120); do kill -0 "$pid" 2>/dev/null || break; sleep 1; done kill -0 "$pid" 2>/dev/null && { echo "vm-install: the VM did not power off" >&2; exit 1; } mv "$W/disk.qcow2" "$OUT/base.qcow2"; cp "$W/OVMF_VARS.4m.fd" "$W/id_ed25519" "$W/id_ed25519.pub" "$OUT/" echo "installed: $OUT/base.qcow2 ($(du -sh "$OUT/base.qcow2" | cut -f1)) in $(el)s"