Captain Bob, 2026-11-06: a terminal with QEMU amd64 for people to play with; "Mint Zuse for demo console" (#111); "Make the most recent of builds that's green the demo"; a Try-it XWiki page with the console embedded. demo/ -- see its README: mint-zuse.sh a Zuse of the demo's own, as a stick: blank pair, system=v3 genesis, a second boot to confirm, make zusestick. Never anyone's real Zuse build-iso.sh the amd64 ISO, packed as make qemu packs it make-snapshot.sh boot once to [zuse@Hera] ok>, save the machine start-session.sh one visitor: the saved machine resumed, ended on hang-up, close or time-out console.py joins the visitor's terminal only once the machine runs; keys typed while it loads are discarded qemu-args.sh the machine, shared by save and resume starshipos-demo.service, apache-demo.conf, xwiki-try-it.xwiki Every session is a copy: no network, read-only image, the Zuse stick's writes discarded, QEMU's seccomp sandbox, an unprivileged user, a fenced systemd unit, 30 minutes at most. Tested here, from the sources master accepted (run 153): the Zuse minted (logs/20261106-192626, -192739); the saved machine at POST 538/538 and [zuse@Hera] ok>; a session through ttyd's websocket ready in 0.2 s, 3 4 + . -> 7 ok, 7 SQUARE . -> 49 ok; QEMU gone at once on hang-up, close and time-out. Found and fixed on the way: a machine saved stopped resumes stopped; a prompt matched as a regex; QEMU left behind by a hang-up; keys lost while the machine loads. Not tested here: Apache, several sessions at once (#116). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
57 lines
2.8 KiB
Bash
Executable File
57 lines
2.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# make-snapshot.sh -- boot the demo machine once, to its prompt, and save it: every session
|
|
# resumes from here, at the prompt in seconds instead of after boot and POST (issue #113).
|
|
#
|
|
# demo/make-snapshot.sh <image.iso> <state-dir> [identity.img]
|
|
#
|
|
# <state-dir> gets the image, the identity drive if any, the firmware's variables as the boot
|
|
# left them (vars.fd) and the machine's state (state). One QEMU, in the foreground.
|
|
set -euo pipefail
|
|
here=$(cd "$(dirname "$0")" && pwd)
|
|
. "$here/qemu-args.sh"
|
|
iso=${1:?usage: demo/make-snapshot.sh <image.iso> <state-dir> [identity.img]}
|
|
dir=$(realpath -m "${2:?usage: demo/make-snapshot.sh <image.iso> <state-dir> [identity.img]}")
|
|
ident=${3:-}
|
|
prompt=${DEMO_PROMPT:-ok> } # what the console prints when it is ready
|
|
boot_limit=${DEMO_BOOT_LIMIT:-600} # seconds a boot may take, POST included
|
|
|
|
rm -rf "$dir"; mkdir -p "$dir"
|
|
cp "$iso" "$dir/StarshipOS.iso"
|
|
[ -z "$ident" ] || cp "$ident" "$dir/identity.img"
|
|
cp /usr/share/OVMF/OVMF_VARS_4M.fd "$dir/vars.fd"
|
|
demo_qemu_args "$dir" "$dir/vars.fd"
|
|
|
|
log=$dir/boot.log qmp=$dir/qmp.sock
|
|
qemu-system-x86_64 "${QEMU_ARGS[@]}" -serial "file:$log" -qmp "unix:$qmp,server=on,wait=off" &
|
|
pid=$!
|
|
trap 'kill $pid 2>/dev/null || true' EXIT
|
|
qmp() { # one QMP command, after the greeting and capabilities; prints QEMU's replies
|
|
python3 - "$qmp" "$1" <<'PY'
|
|
import json, socket, sys, time
|
|
s = socket.socket(socket.AF_UNIX); s.connect(sys.argv[1]); f = s.makefile("rw")
|
|
f.readline(); f.write(json.dumps({"execute": "qmp_capabilities"}) + "\n"); f.flush(); f.readline()
|
|
f.write(sys.argv[2] + "\n"); f.flush(); print(f.readline().strip())
|
|
PY
|
|
}
|
|
echo "demo: booting $iso to '$prompt' (at most ${boot_limit}s)"
|
|
t0=$(date +%s)
|
|
until [ -S "$qmp" ] && grep -aqF -- "$prompt" "$log" 2>/dev/null; do
|
|
kill -0 $pid 2>/dev/null || { echo "demo: QEMU exited during the boot" >&2; tail -5 "$log" >&2; exit 1; }
|
|
[ $(( $(date +%s) - t0 )) -lt "$boot_limit" ] || { echo "demo: no prompt in ${boot_limit}s" >&2; tail -5 "$log" >&2; exit 1; }
|
|
sleep 1
|
|
done
|
|
echo "demo: at the prompt after $(( $(date +%s) - t0 ))s; saving"
|
|
sleep 2 # let the console finish the line
|
|
# Saved running, not stopped first: QEMU saves the run state too, and a machine saved stopped
|
|
# resumes stopped -- every session would be a frozen screen. Migration stops it at the end itself.
|
|
qmp "{\"execute\":\"migrate\",\"arguments\":{\"uri\":\"file:$dir/state\"}}" >/dev/null
|
|
until qmp '{"execute":"query-migrate"}' | grep -q '"status": "completed"'; do
|
|
qmp '{"execute":"query-migrate"}' | grep -q '"status": "failed"' && { echo "demo: saving failed" >&2; exit 1; }
|
|
sleep 1
|
|
done
|
|
qmp '{"execute":"quit"}' >/dev/null || true
|
|
wait $pid 2>/dev/null || true
|
|
trap - EXIT
|
|
rm -f "$qmp"
|
|
echo "demo: saved $(du -h "$dir/state" | cut -f1) to $dir/state"
|