diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 00000000..e04128c4 --- /dev/null +++ b/demo/README.md @@ -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). diff --git a/demo/apache-demo.conf b/demo/apache-demo.conf new file mode 100644 index 00000000..7392b3b9 --- /dev/null +++ b/demo/apache-demo.conf @@ -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 + + 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 + diff --git a/demo/build-iso.sh b/demo/build-iso.sh new file mode 100755 index 00000000..96a73c36 --- /dev/null +++ b/demo/build-iso.sh @@ -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 [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 [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" diff --git a/demo/console.py b/demo/console.py new file mode 100755 index 00000000..4d7a4ef5 --- /dev/null +++ b/demo/console.py @@ -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 + +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) diff --git a/demo/make-snapshot.sh b/demo/make-snapshot.sh new file mode 100755 index 00000000..dc5adb5f --- /dev/null +++ b/demo/make-snapshot.sh @@ -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 [identity.img] +# +# 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 [identity.img]} +dir=$(realpath -m "${2:?usage: demo/make-snapshot.sh [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" diff --git a/demo/mint-zuse.sh b/demo/mint-zuse.sh new file mode 100755 index 00000000..f4f6be5a --- /dev/null +++ b/demo/mint-zuse.sh @@ -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 +# +# 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. +# +# 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 }") +[ ! -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 : 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" diff --git a/demo/qemu-args.sh b/demo/qemu-args.sh new file mode 100644 index 00000000..cedd1c0b --- /dev/null +++ b/demo/qemu-args.sh @@ -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 -> fills the array QEMU_ARGS +# +# /StarshipOS.iso is the image; /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 +} diff --git a/demo/starshipos-demo.service b/demo/starshipos-demo.service new file mode 100644 index 00000000..2285a281 --- /dev/null +++ b/demo/starshipos-demo.service @@ -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 diff --git a/demo/start-session.sh b/demo/start-session.sh new file mode 100755 index 00000000..09d4286f --- /dev/null +++ b/demo/start-session.sh @@ -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 ) & +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' diff --git a/demo/xwiki-try-it.xwiki b/demo/xwiki-try-it.xwiki new file mode 100644 index 00000000..45f0c280 --- /dev/null +++ b/demo/xwiki-try-it.xwiki @@ -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"}} + +{{/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. diff --git a/experiments/bare_metal/runs/doe-amd64-20261106-192626.csv b/experiments/bare_metal/runs/doe-amd64-20261106-192626.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261106-192739.csv b/experiments/bare_metal/runs/doe-amd64-20261106-192739.csv new file mode 100644 index 00000000..e69de29b diff --git a/logs/20261106-192626/amd64/qemu-amd64-20261106-192626.log b/logs/20261106-192626/amd64/qemu-amd64-20261106-192626.log new file mode 100644 index 00000000..1aeb40fd --- /dev/null +++ b/logs/20261106-192626/amd64/qemu-amd64-20261106-192626.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60e20ed87bc0f9ea1a2e0ba5cfc04a899d33b26039ddfd3669b7f00a0fc6012c +size 686495 diff --git a/logs/20261106-192739/amd64/qemu-amd64-20261106-192739.log b/logs/20261106-192739/amd64/qemu-amd64-20261106-192739.log new file mode 100644 index 00000000..fb45133c --- /dev/null +++ b/logs/20261106-192739/amd64/qemu-amd64-20261106-192739.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2345a6df917f05ab8973662b76aaa0490e06bbbf08f6dd6341e5ad87f431685 +size 686088