Files
rajamesandClaude Opus 5.5 9e711563b4 demo: StarshipOS in a browser -- a throwaway machine per visitor, as Zuse (#111-#115)
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
2026-11-06 19:41:22 +00:00

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)