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
61 lines
2.3 KiB
Python
Executable File
61 lines
2.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""console.py -- the visitor's terminal joined to the demo machine's serial console, once the
|
|
machine is running (issue #112).
|
|
|
|
console.py <qmp.sock> <console.sock>
|
|
|
|
A resumed machine takes a moment to load. Anything typed meanwhile would reach a serial port
|
|
whose state is about to be replaced, and be lost half-way through a line ("3 4 + ." arriving as
|
|
"4 + ."). So: wait until QEMU says the machine is running, throw away what was typed before,
|
|
say so, and only then pass bytes both ways. Ends when either side closes.
|
|
"""
|
|
import json, os, select, socket, sys, termios, time, tty
|
|
|
|
qmp_path, con_path = sys.argv[1], sys.argv[2]
|
|
|
|
def connect(path, limit=30):
|
|
end = time.time() + limit
|
|
while True:
|
|
try:
|
|
s = socket.socket(socket.AF_UNIX); s.connect(path); return s
|
|
except OSError:
|
|
if time.time() > end: raise
|
|
time.sleep(0.1)
|
|
|
|
qmp = connect(qmp_path).makefile("rw")
|
|
qmp.readline()
|
|
qmp.write(json.dumps({"execute": "qmp_capabilities"}) + "\n"); qmp.flush(); qmp.readline()
|
|
end = time.time() + 60
|
|
while True: # until the machine runs
|
|
qmp.write(json.dumps({"execute": "query-status"}) + "\n"); qmp.flush()
|
|
while True:
|
|
r = json.loads(qmp.readline())
|
|
if "return" in r or "error" in r: break # skip events
|
|
if r.get("return", {}).get("status") == "running": break
|
|
if time.time() > end: sys.exit("The machine did not start.")
|
|
time.sleep(0.1)
|
|
|
|
con = connect(con_path)
|
|
stdin, stdout = sys.stdin.fileno(), sys.stdout.fileno()
|
|
saved = None
|
|
if os.isatty(stdin):
|
|
termios.tcflush(stdin, termios.TCIFLUSH) # what was typed while the machine was loading
|
|
saved = termios.tcgetattr(stdin)
|
|
tty.setraw(stdin) # the machine echoes and edits the line itself
|
|
os.write(stdout, b"Ready -- type a line and press Enter.\r\n")
|
|
try:
|
|
while True:
|
|
r, _, _ = select.select([stdin, con], [], [])
|
|
if stdin in r:
|
|
data = os.read(stdin, 1024)
|
|
if not data: break
|
|
con.sendall(data)
|
|
if con in r:
|
|
data = con.recv(4096)
|
|
if not data: break
|
|
os.write(stdout, data)
|
|
except OSError:
|
|
pass # the visitor's terminal went away
|
|
finally:
|
|
if saved: termios.tcsetattr(stdin, termios.TCSADRAIN, saved)
|