2 Commits
Author SHA1 Message Date
rajames 4f6a267be4 Merge master into StarForth-v4.0.0: #110's merge commit, so the demo's PR is up to date
Build / Image RASPI (push) Waiting to run
Build / Image PC (push) Waiting to run
Build / Image MILKV (push) Waiting to run
Build / v4 tests (32- and 64-bit cells) (push) Waiting to run
Acceptance / Acceptance (pull_request) Canceled after 0s
master's only commit not on the branch is 82804f27, the merge of #110, whose tree is the
branch's own 810d02eb.
2026-11-06 20:34:25 +00:00
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
14 changed files with 424 additions and 0 deletions
+63
View File
@@ -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).
+22
View File
@@ -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>
+22
View File
@@ -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"
+60
View File
@@ -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)
+56
View File
@@ -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"
+55
View File
@@ -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"
+37
View File
@@ -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
}
+33
View File
@@ -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
+42
View File
@@ -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'
+28
View File
@@ -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.