Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6a267be4 | ||
|
|
9e711563b4 |
@@ -0,0 +1,63 @@
|
||||
# demo/ -- StarshipOS in a browser
|
||||
|
||||
A public "Try it" console: each visitor gets a StarshipOS machine of their own, logged in as a
|
||||
Zuse minted for the demo, resumed at its prompt in under a second, gone when they leave.
|
||||
Milestone *Public demo terminal* (#111-#117).
|
||||
|
||||
```
|
||||
XWiki "Try it" page (iframe) -> Apache: TLS, websocket -> ttyd -> start-session.sh
|
||||
-> QEMU amd64, resumed from the saved machine
|
||||
<-> console.py <-> the visitor's terminal
|
||||
```
|
||||
|
||||
## The pieces
|
||||
|
||||
| File | What it does |
|
||||
|---|---|
|
||||
| `mint-zuse.sh` | Mints a Zuse of the demo's own -- never anyone's real one -- as a stick (#111, ruled 2026-11-06) |
|
||||
| `build-iso.sh` | Packs the amd64 ISO from a kernel build, as `make qemu` packs it |
|
||||
| `make-snapshot.sh` | Boots the machine once to `[zuse@Hera] ok>` and saves it (#113) |
|
||||
| `start-session.sh` | One visitor's session: resumes the saved machine, ends it on hang-up, close or time-out (#112) |
|
||||
| `console.py` | Joins the visitor's terminal to the machine only once it runs, discarding keys typed while it loads |
|
||||
| `qemu-args.sh` | The machine, shared by the two above: a resumed machine must match the saved one exactly |
|
||||
| `starshipos-demo.service` | ttyd as a systemd service, on localhost, at most 3 sessions (#114) |
|
||||
| `apache-demo.conf` | Example vhost: TLS, the websocket, framing allowed for the XWiki only (#115) |
|
||||
| `xwiki-try-it.xwiki` | The XWiki "Try it" page, with the console embedded (XWiki 2.1 syntax) |
|
||||
|
||||
## What a visitor cannot do
|
||||
|
||||
Each session is a copy: the firmware variables are copied, the image is read-only, and the Zuse
|
||||
stick's writes are discarded (`snapshot=on`). The guest has no network device. QEMU runs under its
|
||||
seccomp sandbox (no spawning, no privilege changes), as an unprivileged user, inside a systemd unit
|
||||
that fences memory, CPU and the file system. A session lasts at most `DEMO_SESSION_LIMIT` seconds
|
||||
(1800).
|
||||
|
||||
## Setting it up
|
||||
|
||||
From a checkout of the most recent green commit -- the latest that passed acceptance, `master`
|
||||
(or, once it is published, the nightly's `StarshipOS.iso`):
|
||||
|
||||
```
|
||||
make ARCH=amd64 init INIT_DOCS=0
|
||||
demo/mint-zuse.sh /tmp/demo-zuse # once; keep the stick
|
||||
demo/build-iso.sh /tmp/StarshipOS.iso
|
||||
DEMO_PROMPT='[zuse@Hera]' demo/make-snapshot.sh /tmp/StarshipOS.iso /var/lib/starshipos-demo /tmp/demo-zuse/zusestick.img
|
||||
```
|
||||
|
||||
Then install `starshipos-demo.service` and `apache-demo.conf` (their headers say how), and paste
|
||||
`xwiki-try-it.xwiki` into a page on the XWiki. The `{{html}}` macro there needs a page author
|
||||
with script rights.
|
||||
|
||||
A new image means re-running `build-iso.sh` and `make-snapshot.sh`: the saved machine belongs to
|
||||
one image and one QEMU version. Sessions already running keep theirs.
|
||||
|
||||
## Tested (2026-11-06, Ubuntu 24.04, QEMU 8.2.2, ttyd 1.7.4)
|
||||
|
||||
- The Zuse minted: "genesis minted", then "identity confirmed" (`logs/20261106-192626`, `-192739`).
|
||||
- The saved machine: POST 538/538, "identity confirmed", `[zuse@Hera] ok>` after an 87 s boot.
|
||||
- A session, in a terminal and through ttyd's websocket: ready 0.2 s after it starts; `3 4 + .`
|
||||
answers `7 ok`, `: SQUARE DUP * ; 7 SQUARE .` answers `49 ok`; keys typed before "Ready" are
|
||||
discarded, not half-delivered.
|
||||
- QEMU is gone 0.1 s or less after a hang-up or a closed terminal, and on time at the session
|
||||
limit.
|
||||
- Not tested here: Apache (none in the test container), several sessions at once (#116).
|
||||
@@ -0,0 +1,22 @@
|
||||
# apache-demo.conf -- the demo terminal on the public face (issue #115). An example: the host
|
||||
# name, the certificate paths and the XWiki allowed to embed it are Captain Bob's to set.
|
||||
# a2enmod proxy proxy_http proxy_wstunnel headers ssl rewrite
|
||||
<VirtualHost *:443>
|
||||
ServerName try.strshipos.com
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/try.strshipos.com/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/try.strshipos.com/privkey.pem
|
||||
|
||||
# The terminal may be framed by the XWiki's "Try it" page, and by nothing else.
|
||||
Header always unset X-Frame-Options
|
||||
Header always set Content-Security-Policy "frame-ancestors https://www.starshipos-f18.org https://starshipos-f18.org"
|
||||
|
||||
ProxyPreserveHost On
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||
RewriteRule ^/ws$ ws://127.0.0.1:7681/ws [P,L]
|
||||
ProxyPass / http://127.0.0.1:7681/
|
||||
ProxyPassReverse / http://127.0.0.1:7681/
|
||||
# A session lives up to 30 minutes; keep its websocket open that long.
|
||||
ProxyTimeout 1900
|
||||
</VirtualHost>
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# build-iso.sh -- the demo's ISO: the amd64 kernel's loader on an El Torito ISO, packed as
|
||||
# `make -f kernel/Makefile ARCH=amd64 qemu` packs it (kernel/Makefile, target qemu).
|
||||
#
|
||||
# demo/build-iso.sh <out.iso> [VAR=value ...]
|
||||
#
|
||||
# The VAR=value arguments go to the kernel build: EMERGENCY_CONSOLE_ENABLED=1 for a console with
|
||||
# no identity drive -- one of the two answers to issue #111, not yet ruled. A nightly's
|
||||
# StarshipOS.iso can be used in place of this, once the ruling says which image the demo serves.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
out=$(realpath -m "${1:?usage: demo/build-iso.sh <out.iso> [VAR=value ...]}"); shift
|
||||
make --no-print-directory -f kernel/Makefile ARCH=amd64 clean
|
||||
make --no-print-directory -f kernel/Makefile ARCH=amd64 all "$@"
|
||||
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
||||
dd if=/dev/zero of="$tmp/efi.img" bs=512 count=8192 2>/dev/null
|
||||
mformat -i "$tmp/efi.img" ::
|
||||
mmd -i "$tmp/efi.img" ::/EFI ::/EFI/BOOT
|
||||
mcopy -i "$tmp/efi.img" build/amd64/kernel/starkernel_loader.efi ::/EFI/BOOT/BOOTX64.EFI
|
||||
xorriso -as mkisofs -r -J -e efi.img -no-emul-boot -o "$out" "$tmp" 2>/dev/null
|
||||
git checkout -q -- capsules/BLOCK_MAP.md v3/include/version.h 2>/dev/null || true # the build regenerates these
|
||||
echo "demo: $out"
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/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)
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/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"
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# mint-zuse.sh -- a Zuse of the demo's own, as a stick (issue #111, ruled by Captain Bob,
|
||||
# 2026-11-06: "Mint Zuse for demo console").
|
||||
#
|
||||
# demo/mint-zuse.sh <out-dir>
|
||||
#
|
||||
# A fresh pair, never anyone's real Zuse: whatever a visitor does as this Zuse stays in a
|
||||
# throwaway machine, and the drive's writes are discarded (qemu-args.sh). Minted the way
|
||||
# disk/zuse-scratch was (disk/README.md): a blank 1 GiB Artemis disk and a blank 64 MiB identity
|
||||
# drive, booted with system=v3 on amd64 -- "Zuse: genesis minted onto attached thumbdrive" --
|
||||
# then booted again -- "Zuse: identity confirmed from attached thumbdrive" -- then laid out as a
|
||||
# StarshipOS stick (make zusestick), which is what the v4 demo machine attaches.
|
||||
#
|
||||
# <out-dir> gets artemis.img and zuse-thumb-ident.img (the pair) and zusestick.img (the stick).
|
||||
# Two QEMU boots, one at a time, in the foreground; their serial logs go to logs/ as any boot's.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
out=$(realpath -m "${1:?usage: demo/mint-zuse.sh <out-dir>}")
|
||||
[ ! -e "$out/zuse-thumb-ident.img" ] || { echo "demo: $out already has a pair -- refusing to mint over it" >&2; exit 1; }
|
||||
mkdir -p "$out"
|
||||
truncate -s 1G "$out/artemis.img"
|
||||
truncate -s 64M "$out/zuse-thumb-ident.img"
|
||||
|
||||
boot() { # boot <what to wait for> <what means it failed>: one system=v3 boot with the pair attached
|
||||
local want=$1 bad=$2 mk log qmp t0
|
||||
mk=$(mktemp)
|
||||
make --no-print-directory -f kernel/Makefile ARCH=amd64 clean qemu QEMU_DISPLAY=none \
|
||||
KERNEL_ARGS=system=v3 ARTDISK="$out/artemis.img" ZUSEDISK="$out/zuse-thumb-ident.img" > "$mk" 2>&1 &
|
||||
local pid=$!
|
||||
t0=$(date +%s)
|
||||
until log=$(sed -n 's/^ Serial log: //p' "$mk") && [ -n "$log" ] && grep -aq -- "$want" "$log" 2>/dev/null; do
|
||||
if [ -n "${log:-}" ] && grep -aq -- "$bad" "$log" 2>/dev/null; then echo "demo: '$bad'" >&2; kill $pid; exit 1; fi
|
||||
kill -0 $pid 2>/dev/null || { echo "demo: the boot ended without '$want'" >&2; tail -20 "$mk" >&2; exit 1; }
|
||||
[ $(( $(date +%s) - t0 )) -lt 900 ] || { echo "demo: no '$want' in 900s" >&2; kill $pid; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
echo "demo: $want ($log, $(( $(date +%s) - t0 ))s)"
|
||||
qmp=$(sed -n 's/^ QMP socket: //p' "$mk")
|
||||
python3 - "$qmp" <<'PY'
|
||||
import json, socket, sys
|
||||
s = socket.socket(socket.AF_UNIX); s.connect(sys.argv[1]); f = s.makefile("rw")
|
||||
f.readline()
|
||||
for c in ({"execute": "qmp_capabilities"}, {"execute": "quit"}):
|
||||
f.write(json.dumps(c) + "\n"); f.flush(); f.readline()
|
||||
PY
|
||||
wait $pid || true
|
||||
rm -f "$mk"
|
||||
}
|
||||
|
||||
boot "Zuse: genesis minted onto attached thumbdrive" "fence marker write FAILED"
|
||||
boot "Zuse: identity confirmed from attached thumbdrive" "not recognized"
|
||||
make --no-print-directory -f kernel/Makefile ARCH=amd64 zusestick ZUSE_PAIR="$out"
|
||||
cp build/amd64/kernel/zusestick.img "$out/zusestick.img"
|
||||
git checkout -q -- capsules/BLOCK_MAP.md v3/include/version.h 2>/dev/null || true # the build regenerates these
|
||||
echo "demo: Zuse minted -- $out/zusestick.img"
|
||||
@@ -0,0 +1,37 @@
|
||||
# qemu-args.sh -- the demo machine, sourced by make-snapshot.sh and start-session.sh.
|
||||
#
|
||||
# A session resumes a machine saved by make-snapshot.sh, and QEMU can resume a saved machine only
|
||||
# into the same machine: the same board, CPU, memory, firmware and devices. Both scripts build the
|
||||
# command line here, so they cannot drift apart. It is the machine `make -f kernel/Makefile
|
||||
# ARCH=amd64 qemu` boots (q35, TCG, 1 GiB, OVMF, the ISO as a CD), less what a visitor must not
|
||||
# have: no network device, no Artemis disk, no display, and QEMU's seccomp sandbox on.
|
||||
#
|
||||
# demo_qemu_args <state-dir> <vars.fd> -> fills the array QEMU_ARGS
|
||||
#
|
||||
# <state-dir>/StarshipOS.iso is the image; <state-dir>/identity.img, if there, is the identity
|
||||
# drive every session gets (issue #111), attached with its writes discarded.
|
||||
|
||||
OVMF_CODE=${OVMF_CODE:-/usr/share/OVMF/OVMF_CODE_4M.fd}
|
||||
|
||||
demo_qemu_args() {
|
||||
local dir=$1 vars=$2
|
||||
QEMU_ARGS=(
|
||||
-machine q35,accel=tcg
|
||||
-cpu qemu64,+rdrand
|
||||
-m 1024
|
||||
-smp 1
|
||||
-drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE"
|
||||
-drive "if=pflash,format=raw,file=$vars"
|
||||
-drive "if=none,id=cd,media=cdrom,readonly=on,file=$dir/StarshipOS.iso"
|
||||
-device ide-cd,drive=cd,bootindex=0
|
||||
-device qemu-xhci,id=xhci0,p2=16,p3=16 # kernel/Makefile XHCI_PORTS
|
||||
-nic none
|
||||
-display none
|
||||
-no-reboot
|
||||
-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny
|
||||
)
|
||||
if [ -f "$dir/identity.img" ]; then
|
||||
QEMU_ARGS+=(-drive "if=none,id=ident,format=raw,snapshot=on,file=$dir/identity.img"
|
||||
-device usb-storage,bus=xhci0.0,port=1,drive=ident)
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# starshipos-demo.service -- the public demo's web terminal (issue #114): ttyd on localhost, one
|
||||
# throwaway StarshipOS machine per browser connection (start-session.sh). Apache in front
|
||||
# (apache-demo.conf) does TLS and the websocket.
|
||||
#
|
||||
# Install: the repository's demo/ at /opt/starshipos-demo/demo, the saved machine (make-snapshot.sh)
|
||||
# in /var/lib/starshipos-demo, a user starshipos-demo; then systemctl enable --now starshipos-demo.
|
||||
[Unit]
|
||||
Description=StarshipOS public demo terminal (ttyd)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=starshipos-demo
|
||||
Group=starshipos-demo
|
||||
Environment=DEMO_STATE=/var/lib/starshipos-demo
|
||||
Environment=DEMO_SESSION_LIMIT=1800
|
||||
# -m: sessions at once -- each a QEMU of about 1 GiB and most of a core (issue #116 measures it)
|
||||
ExecStart=/usr/bin/ttyd --port 7681 --interface lo --writable --max-clients 3 \
|
||||
--client-option titleFixed=StarshipOS --client-option disableLeaveAlert=true \
|
||||
/opt/starshipos-demo/demo/start-session.sh
|
||||
Restart=on-failure
|
||||
# Each session is already a sandboxed QEMU with no network; this fences ttyd and its children too.
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ReadOnlyPaths=/var/lib/starshipos-demo
|
||||
MemoryMax=4G
|
||||
CPUQuota=300%
|
||||
TasksMax=200
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# start-session.sh -- one visitor's session: the saved demo machine, resumed at its prompt, its
|
||||
# serial console on this terminal; gone when the visitor leaves or the time is up (issue #112).
|
||||
# ttyd runs this once per browser connection (issue #114).
|
||||
#
|
||||
# demo/start-session.sh [state-dir] (default: /var/lib/starshipos-demo)
|
||||
#
|
||||
# Nothing a visitor does survives the session: the firmware variables are a copy, the identity
|
||||
# drive's writes are discarded, the image is read-only, and the guest has no network.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/qemu-args.sh"
|
||||
dir=${1:-${DEMO_STATE:-/var/lib/starshipos-demo}}
|
||||
limit=${DEMO_SESSION_LIMIT:-1800} # seconds a session may last
|
||||
|
||||
[ -f "$dir/state" ] || { echo "The demo is not set up yet: no saved machine in $dir." >&2; exit 1; }
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/starshipos-session.XXXXXX")
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
cp "$dir/vars.fd" "$tmp/vars.fd"
|
||||
demo_qemu_args "$dir" "$tmp/vars.fd"
|
||||
|
||||
printf '\r\nStarshipOS -- starting a throwaway machine, yours for up to %d minutes. Nothing you do is kept.\r\n' $((limit / 60))
|
||||
printf 'Try: 3 4 + . : SQUARE DUP * ; 7 SQUARE .\r\n\r\n'
|
||||
# QEMU is a tracked child, not exec'd, so that whatever ends the session ends the machine: ttyd
|
||||
# hangs up (it signals this script only), the service stops, or the time runs out. Its console is
|
||||
# a private socket, joined to this terminal by console.py only once the machine runs: keys typed
|
||||
# while it loads would be lost half-way through a line.
|
||||
qemu-system-x86_64 "${QEMU_ARGS[@]}" \
|
||||
-chardev "socket,id=con,path=$tmp/con.sock,server=on,wait=off" -serial chardev:con \
|
||||
-qmp "unix:$tmp/qmp.sock,server=on,wait=off" -monitor none \
|
||||
-incoming "file:$dir/state" </dev/null &
|
||||
qemu=$!
|
||||
( sleep "$limit"; kill "$qemu" 2>/dev/null ) &
|
||||
timer=$!
|
||||
# The bridge is a tracked child too: bash runs a trap only once its foreground command returns,
|
||||
# so a hang-up would otherwise wait on the bridge. `wait` returns to the trap at once.
|
||||
python3 -I "$here/console.py" "$tmp/qmp.sock" "$tmp/con.sock" 0<&0 &
|
||||
bridge=$!
|
||||
trap 'kill "$bridge" "$qemu" "$timer" 2>/dev/null; rm -rf "$tmp"' EXIT
|
||||
trap 'exit 0' HUP TERM INT
|
||||
wait "$bridge" || true
|
||||
printf '\r\n\r\nThe session has ended. Reload the page for a new one.\r\n'
|
||||
@@ -0,0 +1,28 @@
|
||||
= Try StarshipOS =
|
||||
|
||||
This is StarshipOS, running. Below is the console of a real StarshipOS machine -- the amd64 build
|
||||
from the latest version that passed its acceptance tests -- started just for you. You are logged
|
||||
in as Zuse, the superuser, of a machine that exists only for your session: there is nothing to
|
||||
break, and nothing you do is kept. Close the page and it is gone.
|
||||
|
||||
{{html clean="false"}}
|
||||
<iframe src="https://try.strshipos.com/" title="StarshipOS console"
|
||||
style="width:100%; height:34em; border:1px solid #888; border-radius:6px; background:#000;"></iframe>
|
||||
{{/html}}
|
||||
|
||||
== Things to try ==
|
||||
|
||||
StarshipOS speaks FORTH. Type a line and press Enter; ##ok## means it was done.
|
||||
|
||||
|=You type|=It answers
|
||||
|##3 4 + .##|##7 ok## -- numbers go on a stack, ##+## adds the top two, ##.## prints the result
|
||||
|##: SQUARE DUP * ;##|##ok## -- you have just defined a new word
|
||||
|##7 SQUARE .##|##49 ok## -- and used it
|
||||
|
||||
== What you are looking at ==
|
||||
|
||||
* A machine of your own, booted from the same image the project publishes every night, resumed at
|
||||
its prompt so you do not wait for it to boot.
|
||||
* It runs in an emulator on our server. It has no network, and it lasts at most 30 minutes.
|
||||
* If the console does not connect, the few machines we run are all in use -- try again in a few
|
||||
minutes.
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user