Initial Commit

This commit is contained in:
2026-09-15 15:24:03 -04:00
commit 4dc7086e7e
605 changed files with 101380 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
#!/bin/bash
# =====================================================================
# Import a prebuilt base image instead of building one.
#
# sudo ./vm/images/import.sh ubuntu
# sudo ./vm/images/import.sh windows
# sudo ./vm/images/import.sh both
#
# WHY THIS EXISTS
#
# Building Ubuntu takes about 25 unattended minutes. Windows has no
# unattended path at all - it is a person clicking through setup in a VNC
# console, loading a virtio driver by hand, and knowing when to stop. For
# anyone who just wants the sub-agent working, downloading a finished
# image is the difference between an afternoon and a coffee.
#
# WHY IT IS A SCRIPT AND NOT A LIST OF STEPS
#
# Three things have to be exactly right or the result fails later, in
# ways that do not point at their cause:
#
# * the owner must be owuivmadm, or the maintenance tool cannot thaw it
# * the mode must be 0444, because FROZEN IS THE OWNER WRITE BIT and
# nothing else - a 0644 base means vmd refuses every session with
# "freeze it and try again"
# * the .sha256 must land beside the disk, or every saved fast-start
# machine is treated as stale and sessions silently boot from cold
#
# A checklist gets those wrong. This does not.
# =====================================================================
set -euo pipefail
BASE=${OWUIVM_BASE_DIR:-/var/lib/owuivm/base}
# Override to host your own. Any web server will do: the script only ever
# does a ranged GET, and it verifies what it gets.
SOURCE=${OWUIVM_IMAGE_SOURCE:-https://nextcloud.grantlan.com/s/OWUIToolsVMDisks}
[ "$(id -u)" -eq 0 ] || { echo "import.sh must run as root (it writes $BASE)" >&2; exit 1; }
command -v curl >/dev/null || { echo "FAILED: curl is not installed" >&2; exit 1; }
getent passwd owuivmadm >/dev/null || {
echo "FAILED: there is no owuivmadm user - run vm/host/install.sh first" >&2; exit 1; }
# Roughly what each one needs, so a download does not die at 95% with the
# filesystem full. qcow2 is sparse but these are not thin.
declare -A NEED=( [ubuntu]=11 [windows]=44 )
fetch() { # fetch <remote-name> <destination>
local name="$1" dest="$2"
# Nextcloud's public-share download endpoint. `-C -` resumes, which
# matters: windows.qcow2 is 40 GB and a dropped connection three
# quarters of the way through should not mean starting again.
curl -fSL --retry 3 --retry-delay 5 -C - \
-o "$dest" "$SOURCE/download?path=%2F&files=$name"
}
import_one() {
local name="$1"
local disk="$BASE/$name.qcow2" vars="$BASE/$name-OVMF_VARS.fd" sums="$BASE/$name.sha256"
# A FINISHED IMAGE IS ONE THAT IS FROZEN. A part-downloaded one is not.
#
# The obvious guard - refuse if the file exists - contradicts the promise
# that this is resumable: a download interrupted at 30 GB could never be
# re-run. So the test is the same one everything else here uses, the
# owner write bit. import.sh sets it 0444 only after the checksum has
# passed, so a writable disk is by definition an unfinished one.
if [ -e "$disk" ] && [ "$(( 8#$(stat -c %a "$disk") & 8#200 ))" -eq 0 ]; then
echo "FAILED: $disk is already here and frozen." >&2
echo " Importing would replace a base image you may have changed. Delete it" >&2
echo " first if that is what you want - and note that any saved fast-start" >&2
echo " machine for it becomes stale." >&2
exit 1
fi
if [ -e "$disk" ]; then
echo "==> $name: an unfinished download is here; resuming it"
fi
local free_gb
free_gb=$(df -BG --output=avail "$BASE" | tail -1 | tr -dc '0-9')
if [ "$free_gb" -lt "${NEED[$name]}" ]; then
echo "FAILED: $name needs about ${NEED[$name]} GB and $BASE has ${free_gb} GB free" >&2
exit 1
fi
echo "==> $name: downloading (${NEED[$name]} GB or so; resumable, ^C and re-run is safe)"
install -d -o owuivmadm -g owuivmadm -m 0775 "$BASE"
fetch "$name.sha256" "$sums"
fetch "$name-OVMF_VARS.fd" "$vars"
fetch "$name.qcow2" "$disk"
echo "==> $name: verifying"
# BY BASENAME, NOT `sha256sum -c`.
#
# The manifest carries absolute paths - /var/lib/owuivm/base/... - because
# that is where the freeze wrote it. `sha256sum -c` would therefore check
# the files at THOSE paths, which are the ones already installed, not the
# ones just downloaded. At the default location they are the same file and
# it passes for the wrong reason; with OWUIVM_BASE_DIR set it verifies
# somebody else's disk, or fails on a file that is not there.
#
# Measured: a test import into a scratch directory reported both files OK
# while the scratch copy was a quarter downloaded.
local bad=0 want got file
while read -r want file; do
[ -n "$want" ] || continue
got=$(sha256sum "$BASE/$(basename "$file")" | awk '{print $1}')
if [ "$want" = "$got" ]; then
echo " $(basename "$file"): OK"
else
echo " $(basename "$file"): MISMATCH" >&2
bad=1
fi
done < "$sums"
if [ "$bad" -ne 0 ]; then
echo "FAILED: the download is corrupt or incomplete." >&2
echo " Re-run to resume, or delete $BASE/$name.* and start again." >&2
exit 1
fi
echo "==> $name: freezing"
chown owuivmadm:owuivmadm "$disk" "$vars" "$sums"
# 0444 IS the freeze. See the header.
chmod 0444 "$disk" "$vars"
chmod 0644 "$sums"
echo " $name imported and frozen"
}
case "${1:-}" in
ubuntu|windows) import_one "$1" ;;
both) import_one ubuntu; import_one windows ;;
*) echo "usage: import.sh {ubuntu|windows|both}" >&2; exit 64 ;;
esac
echo
echo "Done. Open Tool Server VM Maintenance and the row should read 'frozen'."
echo "Sessions boot these from cold; press 'Save fast start' to make them resume"
echo "in seconds instead."