As they were tested by hand on the 4.0.4 ISO, before any of it is wired
into a workflow:
vm-install unattended install of an ISO into a qcow2, through cidata
vm-seal makes the disk publishable: SSH takes keys only, no machine
ID, host keys, build key or build logs left, compressed
with zstd, with checksums and the kernel pulled out for
direct boot
vm-verify boots the sealed image once and checks all of that
vm-boot boots a throwaway copy with a key made for that boot and
runs a command in it
On this machine: install 114 s, seal 27 s, a 4.2 GB image, SSH about 5 s
after power-on, 17 of 17 verify checks, and 7 of them failing on an
image sealed without these fixes.
91 lines
7.4 KiB
Bash
Executable File
91 lines
7.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# Unattended install of an Omarchy ISO into a qcow2, through a cidata drive.
|
|
#
|
|
# vm-install <omarchy.iso> <out_dir>
|
|
#
|
|
# 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 <omarchy.iso> <out_dir>" >&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" <<EOF
|
|
{
|
|
"app_config": null, "archinstall-language": "English", "auth_config": {},
|
|
"audio_config": { "audio": "pipewire" },
|
|
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
|
|
"custom_commands": [],
|
|
"omarchy_install": { "mode": "full_disk", "defer_provisioning": false, "target_mount": "/mnt",
|
|
"boot": { "esp_mount": "/boot", "esp_path": "/EFI/limine", "efi_binary": "limine_x64.efi", "enable_fallback": true },
|
|
"storage": { "kernel": "linux-omarchy" } },
|
|
"disk_config": { "config_type": "default_layout", "device_modifications": [ { "device": "/dev/vda", "partitions": [
|
|
{ "btrfs": [], "dev_path": null, "flags": [ "boot", "esp" ], "fs_type": "fat32", "mount_options": [], "mountpoint": "/boot",
|
|
"obj_id": "ea21d3f2-82bb-49cc-ab5d-6f81ae94e18d",
|
|
"size": { "sector_size": { "unit": "B", "value": 512 }, "unit": "B", "value": $boot_size },
|
|
"start": { "sector_size": { "unit": "B", "value": 512 }, "unit": "B", "value": $boot_start }, "status": "create", "type": "primary" },
|
|
{ "btrfs": [ { "mountpoint": "/", "name": "@" }, { "mountpoint": "/home", "name": "@home" }, { "mountpoint": "/var/log", "name": "@log" }, { "mountpoint": "/var/cache/pacman/pkg", "name": "@pkg" } ],
|
|
"dev_path": null, "flags": [], "fs_type": "btrfs", "mount_options": [ "compress=zstd" ], "mountpoint": null,
|
|
"obj_id": "8c2c2b92-1070-455d-b76a-56263bab24aa",
|
|
"size": { "sector_size": { "unit": "B", "value": 512 }, "unit": "B", "value": $main_size },
|
|
"start": { "sector_size": { "unit": "B", "value": 512 }, "unit": "B", "value": $main_start }, "status": "create", "type": "primary" } ],
|
|
"wipe": true } ] },
|
|
"hostname": "$GUEST_HOSTNAME", "kernels": [ "linux-omarchy" ], "network_config": { "type": "iso" }, "ntp": true,
|
|
"parallel_downloads": 8, "script": null, "services": [], "swap": true, "timezone": "UTC",
|
|
"locale_config": { "kb_layout": "us", "sys_enc": "UTF-8", "sys_lang": "en_US.UTF-8" },
|
|
"mirror_config": { "custom_repositories": [], "custom_servers": [
|
|
{"url": "https://mirror.omarchy.org/\$repo/os/\$arch"}, {"url": "https://mirror.rackspace.com/archlinux/\$repo/os/\$arch"}, {"url": "https://geo.mirror.pkgbuild.com/\$repo/os/\$arch"} ],
|
|
"mirror_regions": {}, "optional_repositories": [] },
|
|
"packages": [ "base-devel", "git", "omarchy-keyring", "omarchy-settings", "omarchy" ],
|
|
"profile_config": { "gfx_driver": null, "greeter": null, "profile": {} }, "version": "3.0.9"
|
|
}
|
|
EOF
|
|
jq -e . "$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"
|