diff --git a/.gitea/workflows/acceptance.yml b/.gitea/workflows/acceptance.yml index 592adb28..c0e2a3e9 100644 --- a/.gitea/workflows/acceptance.yml +++ b/.gitea/workflows/acceptance.yml @@ -18,7 +18,8 @@ on: workflow_dispatch: concurrency: - group: qemu # one QEMU at a time on the runner: shared with nightly.yml and release.yml + group: qemu # every workflow shares this one group: one run at a time, + # a runner-image rebuild included (Captain Bob, 2026-11-07) cancel-in-progress: false jobs: @@ -37,6 +38,18 @@ jobs: fetch-depth: 0 # the SBOM's version is git describe lfs: false # only the logs and DoE CSVs are in LFS; the build needs neither + - name: The capsule-signing key + env: + KEY: ${{ secrets.CAPSULE_SIGN_KEY }} + run: | # ci/pki/make-ca.sh; kernel/Makefile signs with SIGN_KEY + if [ -n "$KEY" ]; then + f="${RUNNER_TEMP:-/tmp}/capsule-signing.key" + (umask 077; printf '%s\n' "$KEY" > "$f") + echo "SIGN_KEY=$f" >> "$GITHUB_ENV"; echo "The capsules will be signed." + else + echo "::warning::No CAPSULE_SIGN_KEY secret: the capsules are built unsigned." + fi + - name: Acceptance amd64 run: scripts/nightly.sh accept amd64 - name: Acceptance aarch64 diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 1b8fc6aa..07711d7f 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -9,8 +9,14 @@ name: Build on: push: branches: [StarForth-v4.0.0] # not master: what reaches it has just passed acceptance (#119) + paths-ignore: ['site/**'] # the websites (site/) build nothing here; each run is ~17 min on + # the server the wikis share (Captain Bob, 2026-11-07) workflow_dispatch: +concurrency: + group: qemu # every workflow shares this one group (Captain Bob, 2026-11-07: a + cancel-in-progress: false # runner-image rebuild blocks other jobs, always): one run at a time + jobs: image: name: Image ${{ matrix.board }} @@ -34,6 +40,18 @@ jobs: fetch-depth: 0 # the SBOM's version is git describe lfs: false # only the logs and DoE CSVs are in LFS; the build needs neither + - name: The capsule-signing key + env: + KEY: ${{ secrets.CAPSULE_SIGN_KEY }} + run: | # ci/pki/make-ca.sh; kernel/Makefile signs with SIGN_KEY + if [ -n "$KEY" ]; then + f="${RUNNER_TEMP:-/tmp}/capsule-signing.key" + (umask 077; printf '%s\n' "$KEY" > "$f") + echo "SIGN_KEY=$f" >> "$GITHUB_ENV"; echo "The capsules will be signed." + else + echo "::warning::No CAPSULE_SIGN_KEY secret: the capsules are built unsigned." + fi + - name: Boot image run: make boot_image TARGET=${{ matrix.board }} @@ -62,5 +80,17 @@ jobs: with: lfs: false + - name: The capsule-signing key + env: + KEY: ${{ secrets.CAPSULE_SIGN_KEY }} + run: | # ci/pki/make-ca.sh; kernel/Makefile signs with SIGN_KEY + if [ -n "$KEY" ]; then + f="${RUNNER_TEMP:-/tmp}/capsule-signing.key" + (umask 077; printf '%s\n' "$KEY" > "$f") + echo "SIGN_KEY=$f" >> "$GITHUB_ENV"; echo "The capsules will be signed." + else + echo "::warning::No CAPSULE_SIGN_KEY secret: the capsules are built unsigned." + fi + - name: make -C v4 test run: make -C v4 test diff --git a/.gitea/workflows/nightly-docs.yml b/.gitea/workflows/nightly-docs.yml index ef75f63f..d2410264 100644 --- a/.gitea/workflows/nightly-docs.yml +++ b/.gitea/workflows/nightly-docs.yml @@ -14,6 +14,10 @@ on: - cron: '0 4 * * *' # 04:00 UTC, after the images (nightly.yml, 03:00) workflow_dispatch: +concurrency: + group: qemu # every workflow shares this one group (Captain Bob, 2026-11-07: a + cancel-in-progress: false # runner-image rebuild blocks other jobs, always): one run at a time + jobs: docs: name: Book, volumes, API reference diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml index 7a3d89d3..1588ff39 100644 --- a/.gitea/workflows/nightly.yml +++ b/.gitea/workflows/nightly.yml @@ -20,7 +20,8 @@ on: workflow_dispatch: concurrency: - group: qemu # one QEMU at a time on the runner: shared with acceptance.yml + group: qemu # every workflow shares this one group: one run at a time, + # a runner-image rebuild included (Captain Bob, 2026-11-07) cancel-in-progress: false jobs: @@ -39,6 +40,18 @@ jobs: fetch-depth: 0 # the SBOM's version is git describe lfs: false # only the logs and DoE CSVs are in LFS; the build needs neither + - name: The capsule-signing key + env: + KEY: ${{ secrets.CAPSULE_SIGN_KEY }} + run: | # ci/pki/make-ca.sh; kernel/Makefile signs with SIGN_KEY + if [ -n "$KEY" ]; then + f="${RUNNER_TEMP:-/tmp}/capsule-signing.key" + (umask 077; printf '%s\n' "$KEY" > "$f") + echo "SIGN_KEY=$f" >> "$GITHUB_ENV"; echo "The capsules will be signed." + else + echo "::warning::No CAPSULE_SIGN_KEY secret: the capsules are built unsigned." + fi + - name: Acceptance amd64 run: scripts/nightly.sh accept amd64 - name: Acceptance aarch64 diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 141ae3ae..404bebd6 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -14,7 +14,8 @@ on: tags: ['v*'] concurrency: - group: qemu # one QEMU at a time on the runner: shared with acceptance.yml + group: qemu # every workflow shares this one group: one run at a time, + # a runner-image rebuild included (Captain Bob, 2026-11-07) cancel-in-progress: false jobs: @@ -33,6 +34,18 @@ jobs: fetch-depth: 0 lfs: false + - name: The capsule-signing key + env: + KEY: ${{ secrets.CAPSULE_SIGN_KEY }} + run: | # ci/pki/make-ca.sh; kernel/Makefile signs with SIGN_KEY + if [ -n "$KEY" ]; then + f="${RUNNER_TEMP:-/tmp}/capsule-signing.key" + (umask 077; printf '%s\n' "$KEY" > "$f") + echo "SIGN_KEY=$f" >> "$GITHUB_ENV"; echo "The capsules will be signed." + else + echo "::warning::No CAPSULE_SIGN_KEY secret: the capsules are built unsigned." + fi + - name: The tag names the tree's version run: | tree="v$(sed -n 's/^LITHOS_VERSION *?= *//p' kernel/Makefile)" diff --git a/.gitea/workflows/runner-image.yml b/.gitea/workflows/runner-image.yml index 9987174e..485eaee2 100644 --- a/.gitea/workflows/runner-image.yml +++ b/.gitea/workflows/runner-image.yml @@ -17,6 +17,10 @@ on: paths: ['ci/runner-image/**', 'Makefile'] workflow_dispatch: +concurrency: + group: qemu # every workflow shares this one group (Captain Bob, 2026-11-07: a + cancel-in-progress: false # runner-image rebuild blocks other jobs, always): one run at a time + jobs: image: name: Build and publish the runner image @@ -44,7 +48,9 @@ jobs: tag=$(git rev-parse --short=12 HEAD) ctx=$(mktemp -d) # not the repository: 7 GB of context cp -r Makefile boards ci/runner-image/Dockerfile "$ctx/" - docker build -t "$img:$tag" "$ctx" + tok=$(mktemp); printf '%s' "$TOKEN" > "$tok" # the HOL-Library heap's download (Dockerfile) + docker build --secret id=pkgtoken,src="$tok" -t "$img:$tag" "$ctx" + rm -f "$tok" echo "$TOKEN" | docker login gitea.starshipos-f18.org -u rajames --password-stdin docker push "$img:$tag" echo "Published $img:$tag" diff --git a/CHANGELOG.md b/CHANGELOG.md index 881f49fc..a8ad0b7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 2026-11-07 — LithosAnanke v4.0.0-alpha.1: the first alpha release + +The first alpha of the kernel, `v4.0.0-alpha.1`: v4, StarForth's F18 engine, is the system every +board boots by default, with v3 a boot argument away. The kernel's version goes from 2.1.0 to the +4.x working line (Captain Bob, 2026-11-07); the engine stays 3.2.0. Accepted in full before the tag: +amd64, aarch64 and riscv64 to `[zuse@Hera] ok>` with POST 538/538 and one `dict_hash` +(`0xfec8c47cfc6f437f`), the PC stick loaded high, `test-32` (39 programs, 0 failures), and the +Isabelle/HOL proofs. An alpha: the v4.0.0 milestone's own definition -- parity with v3's vocabulary, +K=1.0 measured on v4, the hosted mesh, Verilator -- is still open, and the capsules are unsigned until +the StarshipOS CA is made (FABRIC-3.md XLVI). Also today: CI in a prebuilt runner image, the +nightly's proofs no longer able to hold back its images, and the .com and .org websites. Full record: +`docs/fabric/FABRIC-3.md` XLVII. + ## 2026-11-07 — CI jobs start ready: the runner image The CI jobs now run in `lithosananke-runner` (`ci/runner-image`, #120), an image with everything diff --git a/capsules/pki/snakeoil-intermediate.der b/capsules/pki/snakeoil-intermediate.der deleted file mode 100644 index cc4623c8..00000000 Binary files a/capsules/pki/snakeoil-intermediate.der and /dev/null differ diff --git a/capsules/pki/starshipos-intermediate.der b/capsules/pki/starshipos-intermediate.der new file mode 100644 index 00000000..e4c6ee34 Binary files /dev/null and b/capsules/pki/starshipos-intermediate.der differ diff --git a/ci/pki/make-ca.sh b/ci/pki/make-ca.sh new file mode 100755 index 00000000..a1331597 --- /dev/null +++ b/ci/pki/make-ca.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# make-ca.sh -- the StarshipOS capsule-signing PKI, made once, on the CI box (Captain Bob, +# 2026-11-07: "New root CA on the CI box. Valid StarshipOS rather than snake oil intermediate"). +# +# StarshipOS Root CA Ed25519, 20 years, self-signed. Its key never leaves this box: +# not in the repository, not in Gitea, not in any image. +# StarshipOS Capsule Signing Ed25519, 10 years, signed by the root; CA:TRUE, pathlen:0. +# Its certificate is built into every image (capsules/pki/ +# starshipos-intermediate.der) and checks each capsule at boot; +# its key signs the capsules (mkcapsule --sign-key), in CI from +# the repository secret CAPSULE_SIGN_KEY. +# +# Replaces the offline LithosAnanke Root CA and its snakeoil intermediate (FABRIC-2.md, +# Milestone 6) for every build. Run as root: +# +# sudo ci/pki/make-ca.sh [dir] # dir defaults to /var/lib/starshipos-ca +# +# It refuses to overwrite an existing CA. When it is done it prints the two certificates (public: +# paste them where they are wanted) and says how to put the signing key into Gitea. With +# GITEA_TOKEN set (a token that may administer the repository), it stores the secret itself. +set -euo pipefail +umask 077 +dir=${1:-/var/lib/starshipos-ca} +repo=${GITEA_REPO:-rajames/LithosAnanke} +api=${GITEA_API:-https://gitea.starshipos-f18.org/api/v1} + +command -v openssl >/dev/null || { echo "make-ca: needs openssl" >&2; exit 1; } +[ -e "$dir/root/root.key" ] && { echo "make-ca: $dir already holds a CA; not overwriting it" >&2; exit 1; } +mkdir -p "$dir/root" "$dir/signing" +cd "$dir" + +cat > ca.cnf <<'CNF' +[ req ] +distinguished_name = dn +prompt = no +[ dn ] +[ root_ext ] +basicConstraints = critical, CA:TRUE +keyUsage = critical, keyCertSign, cRLSign +subjectKeyIdentifier = hash +[ signing_ext ] +basicConstraints = critical, CA:TRUE, pathlen:0 +keyUsage = critical, keyCertSign, cRLSign, digitalSignature +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +CNF + +# the root +openssl genpkey -algorithm ed25519 -out root/root.key +openssl req -new -x509 -key root/root.key -days 7305 -config ca.cnf -extensions root_ext \ + -subj "/O=StarshipOS/CN=StarshipOS Root CA" -out root/root.pem + +# the capsule-signing intermediate, signed by the root +openssl genpkey -algorithm ed25519 -out signing/signing.key +openssl req -new -key signing/signing.key -config ca.cnf \ + -subj "/O=StarshipOS/CN=StarshipOS Capsule Signing" -out signing/signing.csr +openssl x509 -req -in signing/signing.csr -CA root/root.pem -CAkey root/root.key \ + -CAcreateserial -days 3652 -extfile ca.cnf -extensions signing_ext -out signing/signing.pem +openssl x509 -in signing/signing.pem -outform DER -out signing/starshipos-intermediate.der +openssl verify -CAfile root/root.pem signing/signing.pem + +chmod 700 root signing; chmod 600 root/* signing/* + +if [ -n "${GITEA_TOKEN:-}" ]; then + python3 -c 'import json,sys; print(json.dumps({"data": open(sys.argv[1]).read()}))' signing/signing.key | + curl -fsS -o /dev/null -X PUT -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \ + "$api/repos/$repo/actions/secrets/CAPSULE_SIGN_KEY" -d @- && + echo "make-ca: stored the signing key as $repo's secret CAPSULE_SIGN_KEY" +else + echo "make-ca: put $dir/signing/signing.key, whole, into $repo's Actions secret CAPSULE_SIGN_KEY" + echo " (Settings -> Actions -> Secrets), or run again with GITEA_TOKEN set -- after" + echo " removing $dir, since it refuses to overwrite a CA." +fi +echo +echo "=== The root certificate (public) -- $dir/root/root.pem" +cat root/root.pem +echo "=== The capsule-signing certificate (public) -- $dir/signing/signing.pem" +cat signing/signing.pem +echo "=== Fingerprints" +openssl x509 -in root/root.pem -noout -fingerprint -sha256 -subject -enddate +openssl x509 -in signing/signing.pem -noout -fingerprint -sha256 -subject -issuer -enddate diff --git a/ci/runner-image/Dockerfile b/ci/runner-image/Dockerfile index 542b73de..4bb14ff2 100644 --- a/ci/runner-image/Dockerfile +++ b/ci/runner-image/Dockerfile @@ -1,7 +1,8 @@ # The CI runner image (#120): gitea/runner-images' Ubuntu 24.04 with Node -- which # actions/checkout and upload-artifact run on -- plus everything the jobs install at their start: # what `make ARCH=amd64,aarch64,riscv64 init INIT_DOCS=0` installs (taken from the Makefile -# itself, not copied, so the two cannot drift), and Maven and a JRE for the publish steps. +# itself, not copied, so the two cannot drift), Maven and a JRE for the publish steps, and +# Isabelle's HOL-Library already built, for the proofs. # # Built by .gitea/workflows/runner-image.yml, from a context of the Makefile, boards/ and this file. FROM gitea/runner-images:ubuntu-24.04-v26.11.03 @@ -11,4 +12,19 @@ WORKDIR /opt/starship-init RUN make --no-print-directory ARCH=amd64,aarch64,riscv64 init INIT_DOCS=0 \ && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq maven default-jre-headless \ && rm -rf /var/lib/apt/lists/* /opt/starship-init/build/cache/downloads +# HOL-Library, prebuilt, into Isabelle's own heaps, so the proofs build only the StarForth session +# (~2.2 GiB, under a minute). Built from scratch it takes ~5.3 GiB (nightly 174 was killed doing it), +# and capped it still needs ~2.6-3.3 GiB: more than the server has. So it is built once, elsewhere, +# by the same Isabelle2025-2, and fetched from this repository's package registry, its checksum +# pinned here (FABRIC-3.md XLVII). Isabelle ships its own HOL heap the same way. The token is a +# build secret (runner-image.yml): it is in neither the image nor its history. +ARG HOL_LIBRARY_URL=https://gitea.starshipos-f18.org/api/packages/rajames/generic/isabelle-hol-library/Isabelle2025-2/hol-library-Isabelle2025-2.tar.xz +ARG HOL_LIBRARY_SHA256=bb24784664def9379e5db27f88802e3679b8e1c628177f6fbe7084e999cb339e +RUN --mount=type=secret,id=pkgtoken \ + curl -fsSL -H "Authorization: token $(cat /run/secrets/pkgtoken)" -o /tmp/hol-library.tar.xz "$HOL_LIBRARY_URL" \ + && echo "$HOL_LIBRARY_SHA256 /tmp/hol-library.tar.xz" | sha256sum -c - \ + && heaps=$(isabelle getenv -b ISABELLE_HEAPS_SYSTEM) && mkdir -p "$heaps" \ + && xz -dc /tmp/hol-library.tar.xz | tar -C "$heaps" -xf - \ + && rm /tmp/hol-library.tar.xz \ + && test -s "$heaps/polyml-5.9.2_x86_64_32-linux/HOL-Library" WORKDIR / diff --git a/docs/fabric/FABRIC-3.md b/docs/fabric/FABRIC-3.md index d41d95c9..82ca875d 100644 --- a/docs/fabric/FABRIC-3.md +++ b/docs/fabric/FABRIC-3.md @@ -6061,3 +6061,107 @@ four conflicts resolved to the branch's files (`92ca805e`). - The first scheduled nightly's publish to Nexus (#17). - The Pi 5 and Mars images booted on their boards (#27, #28); a Pi 4 port (#26). - Revoke the access token `claude-session-20261106` (Settings, Applications). + +## XLVI. The StarshipOS CA: capsules signed in CI, by a root on the CI box (2026-11-07) + +### 1. Found + +Every image built anywhere but Captain Bob's machine carried unsigned capsules: CI, the +acceptance runs, the nightly bound for Nexus, a release, the demo. The capsule-signing key -- the +snakeoil intermediate of the offline LithosAnanke Root CA (FABRIC-2.md, Milestone 6) -- lived only +there, `kernel/Makefile`'s `SIGN_KEY` named its path, and a missing signature is a warning by +design (2026-08-26), so they booted: `V4: capsule v4:sys:core.f18 signature: missing (unsigned)` +in every accepted log (`logs/20261106-004933`). And `mkcapsule` signed with whatever key it was +given: a key that is not the embedded certificate's makes every signature INVALID, which refuses +Mama. + +### 2. Ruled + +Captain Bob, 2026-11-07: "New root CA on the CI box. Valid StarshipOS rather than snake oil +intermediate." + +### 3. Built + +- `ci/pki/make-ca.sh`, run once on the CI box: **StarshipOS Root CA** (Ed25519, 20 years, + self-signed; its key stays on that box, in `/var/lib/starshipos-ca`) and **StarshipOS Capsule + Signing** (Ed25519, 10 years, issued by the root, `CA:TRUE, pathlen:0`). It refuses to + overwrite a CA, prints both certificates, and stores the signing key as the repository secret + `CAPSULE_SIGN_KEY` (or says how). +- The image carries `capsules/pki/starshipos-intermediate.der` in place of the snakeoil + intermediate; the kernel finds it by one name, `CAPSULE_SIGNING_CERT_NAME` (`capsule_sig.h`). +- `mkcapsule` checks the signing key against that certificate before it writes anything, and + refuses a key that is not its, or a key with no certificate to check. +- `SIGN_KEY` has no default (`kernel/Makefile`, `v4/Makefile`): CI sets it from the secret (a step + in `acceptance.yml`, `build.yml`, `nightly.yml`, `release.yml`); unset, the capsules are built + unsigned, as before. + +What it is not: the kernel still does not check the intermediate against a root at boot +(`x509_ed25519.h`; `ACL.4th`'s `ACL-CA-KEY` is still 0). A signature proves a capsule matches the +certificate in the same image. That is unchanged by this, and a ruling of its own. + +### 4. Verified + +With a CA made by `make-ca.sh` in a scratch directory (not the real one): `openssl verify` of the +chain OK; `make-ca.sh` refuses a second run; `mkcapsule` with the signing key -- "signing key +matches"; with the root's key -- refused; with a key and no certificate -- refused; with no key -- +unsigned, as before. The acceptance with the real certificate is the PR's. + +## XLVII. LithosAnanke v4.0.0-alpha.1: the first alpha release (2026-11-07) + +### 1. Ruled + +Captain Bob, 2026-11-07: "next on the list is do a tag/release of v4.0.0" -- then, on what the number +names: "StarForth version stays the same ... Kernel is 4.0.0. This is our first alpha release." The +tag names the kernel (`release.yml`: `v`), so: + +- `LITHOS_VERSION` 2.1.0 -> **4.0.0-alpha.1**; the tag is `v4.0.0-alpha.1`; Nexus takes + `org.starship:kernelImage:4.0.0-alpha.1` and `org.starship:documentation:4.0.0-alpha.1`. +- The engine's `VERSION` stays **3.2.0**. +- The versioning policy (`kernel/Makefile`, FABRIC-3.5.md XXX): 4.x is a working line (even major). + v3.0.0, the first LTS, is no longer the next number; an LTS line takes the next odd major when + XXX.5's criteria are met. Alphas are numbered: a release repository takes each version once. +- The v4.0.0 milestone is unchanged: its definition (v4 equivalent to v3, K=1.0 on all three host + ISAs, a hosted mesh, Verilator co-simulation) is what 4.0.0 itself will be; nine of its issues + are open. This is an alpha of it. +- The release is built and proved here first (this session, 15 GiB), its logs committed; the + server's release run follows once it can pass -- the proofs need HOL-Library in the runner image. + The capsules are unsigned for this release. + +### 2. Verified (this session, `b7534107`, before the tag) + +- amd64 (`logs/20261107-150809`), aarch64 (`-151023`), riscv64 (`-151359`): `LithosAnanke + v4.0.0-alpha.1 `, `[zuse@Hera] ok>`, `PARITY:V4_POST tests=538 pass=538 fail=0`, + `POST: PASSED`, Zuse identity confirmed, `PARITY:V4_SYSTEM word_count=317 + dict_hash=0xfec8c47cfc6f437f` on all three. +- The PC stick loaded high (`logs/20261107-151539`): the same, from the stick, no fault. +- `make -C v4 test-32`: 39 programs, 0 failures. +- The Isabelle/HOL proofs (`logs/20261107-152312/isabelle`). +- Each v4 capsule logs `signature: no root key (cert not found/parseable)`: the kernel looks for the + StarshipOS Capsule Signing certificate (XLVI), which is not in the tree until `ci/pki/make-ca.sh` + has run. A warning, as "missing (unsigned)" was before; nothing is refused. + +### 3. The StarshipOS CA, and acceptance again (`4ca935d8`) + +`ci/pki/make-ca.sh` ran on the CI box (2026-11-07 16:50 UTC): the StarshipOS Root CA there, the +signing key in the secret `CAPSULE_SIGN_KEY`, and the certificate -- O=StarshipOS, CN=StarshipOS +Capsule Signing, issued by the root, 2026-11-07 to 2036-11-06, `CA:TRUE, pathlen:0`, SHA-256 of the +DER `13a28dda...029b9a6c` -- in the tree as `capsules/pki/starshipos-intermediate.der`, the snakeoil +intermediate out of it. The tree changed, so the protocol ran again here: amd64 +(`logs/20261107-165611`), aarch64 (`-165817`), riscv64 (`-170149`) -- POST 538/538, +`dict_hash=0xfec8c47cfc6f437f` on all three; the stick loaded high (`-170326`); `test-32`, 39 +programs, 0 failures; the proofs (`-171020`). Each capsule now logs `signature: missing (unsigned)`: +the kernel found and read the StarshipOS certificate (it said "no root key" without it), and this +session has no key to sign with. CI signs with the secret: the PR's acceptance and the release are +where every capsule first verifies. + +### 4. HOL-Library, prebuilt + +The runner image was to build HOL-Library itself (XLVI's companion change, `4ae1f759`); the server +cannot: from scratch it peaks at ~5.3 GiB, and with Poly/ML capped at 1.6 GiB the build still needs +Isabelle's Java side at ~1 GiB (at 384 MiB it ran out and hung) -- 2.6-3.3 GiB against the server's +3.8 GiB in all, ~2.3 GiB free with both wikis stopped. So the heap built here by the same +Isabelle2025-2 (HOL-Library and its build database) is a package in this repository's registry, +`generic/isabelle-hol-library/Isabelle2025-2/hol-library-Isabelle2025-2.tar.xz` (45 MB, SHA-256 +`bb247846...e999cb339e`), and the image fetches it into Isabelle's system heaps, checksum pinned, +its token a Docker build secret. Shown portable first: a fresh Isabelle home holding only that heap +checks the StarForth session without rebuilding HOL-Library (24 s). diff --git a/experiments/bare_metal/runs/doe-aarch64-20261107-151023.csv b/experiments/bare_metal/runs/doe-aarch64-20261107-151023.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-aarch64-20261107-165817.csv b/experiments/bare_metal/runs/doe-aarch64-20261107-165817.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261107-150809.csv b/experiments/bare_metal/runs/doe-amd64-20261107-150809.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261107-151539.csv b/experiments/bare_metal/runs/doe-amd64-20261107-151539.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261107-165611.csv b/experiments/bare_metal/runs/doe-amd64-20261107-165611.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261107-170326.csv b/experiments/bare_metal/runs/doe-amd64-20261107-170326.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-riscv64-20261107-151359.csv b/experiments/bare_metal/runs/doe-riscv64-20261107-151359.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-riscv64-20261107-170149.csv b/experiments/bare_metal/runs/doe-riscv64-20261107-170149.csv new file mode 100644 index 00000000..e69de29b diff --git a/kernel/Makefile b/kernel/Makefile index ddbead77..81bcbd7b 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -89,6 +89,12 @@ endif # v2.2.0 — amd64 bare-metal bring-up (Beelink SER5) # v2.x — further working releases; riscv64 / aarch64 board bring-up # v3.0.0 — FIRST LTS, when and only when FABRIC-3.5.md §XXX.5's 5 criteria are met +# v4.0.0-alpha.1 — the first alpha release (Captain Bob, 2026-11-07: "Kernel is 4.0.0. This is +# our first alpha release"): v4, the F18 engine, the default system. The kernel goes +# from v2.1.0 straight to the 4.x working line (even: breaking changes land here); +# v3.0.0, the first LTS, is no longer the next number -- an LTS line takes the next +# odd major when §XXX.5's criteria are met. The engine stays 3.2.0. Alphas are +# numbered (-alpha.N); a release repository takes each version once. # LITHOS_VERSION (kernel) and VERSION (embedded StarForth engine) are independent # and do not auto-sync (§XXX.4) — do not infer a relationship if they ever coincide. # Engine VERSION bumps at tag time by what actually changed (§XXX.6): major = any @@ -97,7 +103,7 @@ endif # the messaging layer was replaced by kernel-Hermes, PLOT/FB-* relocated to Hestia — # all dictionary-visible, no FORTH-79 word semantics changed. VERSION ?= 3.2.0 -LITHOS_VERSION ?= 2.1.0 +LITHOS_VERSION ?= 4.0.0-alpha.1 # ============================================================================== # BUILD PATHS @@ -207,7 +213,8 @@ MKCAPSULE_SRC = tools/mkcapsule.c tools/pkcs8_ed25519.c \ kernel/src/crypto/ed25519.c \ kernel/src/crypto/fe25519.c \ kernel/src/crypto/scalar25519.c \ - kernel/src/crypto/sha512.c + kernel/src/crypto/sha512.c \ + kernel/src/crypto/x509_ed25519.c MKCAPSULE_BIN = $(BUILD_DIR)/tools/mkcapsule # mkartemis: formats and signs an Artemis partition at build with the kernel's own # block-subsystem and artemis_sig code (tools/mkartemis.c), built as v4/Makefile builds them. @@ -215,12 +222,11 @@ MKARTEMIS_SRC = tools/mkartemis.c kernel/src/artemis_sig.c \ $(addprefix v3/src/,block_subsystem.c blkio_ram.c blkio_file.c log.c \ platform/platform_init.c platform/linux/time.c) MKARTEMIS_BIN = $(BUILD_DIR)/tools/mkartemis -# Milestone 6 (Phase 8): the snakeoil intermediate's private key, generated -# offline outside this repo entirely (see FABRIC-2.md's Phase 8 §Milestone 6 -# writeup) -- not present in CI or a fresh checkout, so signing is skipped -# gracefully ($(wildcard ...) below) rather than failing the build. Override -# with `make SIGN_KEY=/path/to/key.pem ...` on a machine that holds it. -SIGN_KEY ?= /home/rajames/CLionProjects/lithosananke-ca/intermediate/snakeoil-intermediate.key +# The capsule-signing key: "StarshipOS Capsule Signing"'s private key (ci/pki/make-ca.sh, +# FABRIC-3.md XLVI). CI sets SIGN_KEY from the secret CAPSULE_SIGN_KEY; elsewhere set it by hand +# (`make SIGN_KEY=/path/to/signing.key ...`). Unset or absent, the capsules are built unsigned, +# which boots with a warning per capsule. mkcapsule refuses a key that is not the certificate's. +SIGN_KEY ?= SIGN_KEY_ARGS = $(if $(wildcard $(SIGN_KEY)),--sign-key $(SIGN_KEY),) CAPSULE_GENERATED = $(BUILD_DIR)/capsule_generated.c CAPSULE_GENERATED_OBJ = $(BUILD_DIR)/capsule_generated.o diff --git a/kernel/include/starkernel/capsule.h b/kernel/include/starkernel/capsule.h index 883c85e5..1122ade0 100644 --- a/kernel/include/starkernel/capsule.h +++ b/kernel/include/starkernel/capsule.h @@ -171,7 +171,8 @@ typedef struct { * * Milestone 6 (Phase 8): each capsule's payload bytes (the same bytes * content_hash already covers), signed by mkcapsule at build time with - * the snakeoil intermediate's private key. has_sig=0 for a capsule built + * the capsule-signing key (StarshipOS Capsule Signing, capsule_sig.h; + * the snakeoil intermediate's until 2026-11-07). has_sig=0 for a capsule built * before this feature existed or otherwise unsigned -- a real, distinct * state, not "signature is all-zero bytes" (which sig[64] full of 0x00 * would otherwise look ambiguous with). Indexed 1:1 with diff --git a/kernel/include/starkernel/capsule_sig.h b/kernel/include/starkernel/capsule_sig.h index 32ae68b1..b2bb3fff 100644 --- a/kernel/include/starkernel/capsule_sig.h +++ b/kernel/include/starkernel/capsule_sig.h @@ -22,6 +22,12 @@ #include "starkernel/capsule.h" +/* The certificate whose key the capsules are signed with: "StarshipOS + * Capsule Signing", issued by the StarshipOS Root CA (ci/pki/make-ca.sh; + * FABRIC-3.md XLVI), replacing the snakeoil intermediate of Milestone 6. + * mkcapsule refuses to sign with a key that is not this certificate's. */ +#define CAPSULE_SIGNING_CERT_NAME "pki:starshipos-intermediate.der" + typedef enum { CAPSULE_SIG_OK = 0, /* has_sig=1, and it verifies */ CAPSULE_SIG_MISSING, /* has_sig=0 -- not signed at all */ @@ -31,9 +37,9 @@ typedef enum { /* * Verify capsule descs[index]'s Ed25519 signature against the embedded - * snakeoil intermediate cert's public key (capsule name - * "pki:snakeoil-intermediate.der", found and parsed once, cached for - * every later call this boot -- the cert doesn't change mid-boot). + * capsule-signing certificate's public key (CAPSULE_SIGNING_CERT_NAME, + * found and parsed once, cached for every later call this boot -- the + * cert doesn't change mid-boot). * * descs/names/sigs must be the same three parallel arrays * (capsule_get_descriptors()/capsule_get_names()/capsule_get_signatures()), diff --git a/kernel/include/starkernel/x509_ed25519.h b/kernel/include/starkernel/x509_ed25519.h index 1aa96b5f..a2f0521d 100644 --- a/kernel/include/starkernel/x509_ed25519.h +++ b/kernel/include/starkernel/x509_ed25519.h @@ -6,9 +6,10 @@ * * x509_extract_ed25519_pubkey() only ever reads SubjectPublicKeyInfo -- * no signature verification, no chain validation, no extension parsing. - * That's the capsule-PKI use case (Milestone 6): the embedded snakeoil - * intermediate cert is trusted because it's baked into the trusted build, - * never re-verified against the offline root CA at boot. + * That's the capsule-PKI use case (Milestone 6): the embedded capsule- + * signing cert (StarshipOS Capsule Signing since 2026-11-07, the snakeoil + * intermediate before it) is trusted because it's baked into the trusted + * build, never re-verified against its root CA at boot. * * x509_verify_signature()/x509_extract_serial() (added 2026-08-28, * FABRIC-2.md §F.7/§F.17) are for CERTVERIFY -- a regular user's cert, diff --git a/kernel/src/capsule/capsule_sig.c b/kernel/src/capsule/capsule_sig.c index 6d46f7f1..759decb5 100644 --- a/kernel/src/capsule/capsule_sig.c +++ b/kernel/src/capsule/capsule_sig.c @@ -4,8 +4,6 @@ #include "starkernel/ed25519.h" #include -#define SNAKEOIL_CERT_NAME "pki:snakeoil-intermediate.der" - /* Cached once per boot: the cert doesn't change mid-boot, and every * capsule birth (Mama, then every child VM) would otherwise re-find and * re-parse the same cert. -1 = not yet attempted, 0 = failed, 1 = ready. */ @@ -20,7 +18,7 @@ static int find_and_cache_pubkey(const CapsuleDesc *descs, pubkey_state = 0; /* assume failure unless every step below succeeds */ for (uint32_t i = 0; i < desc_count; i++) { - if (strcmp(names[i].name, SNAKEOIL_CERT_NAME) == 0) { + if (strcmp(names[i].name, CAPSULE_SIGNING_CERT_NAME) == 0) { const CapsuleDesc *cert_desc = &descs[i]; if (x509_extract_ed25519_pubkey(arena_base + cert_desc->offset, (size_t)cert_desc->length, diff --git a/logs/20261107-150809/amd64/qemu-amd64-20261107-150809.log b/logs/20261107-150809/amd64/qemu-amd64-20261107-150809.log new file mode 100644 index 00000000..66c847cc --- /dev/null +++ b/logs/20261107-150809/amd64/qemu-amd64-20261107-150809.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df3373b59cfc1642da45167ad4f4b4fd01cacd8a435c86484e00bace0849e869 +size 6977 diff --git a/logs/20261107-151023/aarch64/qemu-aarch64-20261107-151023.log b/logs/20261107-151023/aarch64/qemu-aarch64-20261107-151023.log new file mode 100644 index 00000000..91da68a0 --- /dev/null +++ b/logs/20261107-151023/aarch64/qemu-aarch64-20261107-151023.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1de946e8022a449b4b86de7ff7b50be97866f7bb4e54b7ffeaa51300b805569a +size 5942 diff --git a/logs/20261107-151359/riscv64/qemu-riscv64-20261107-151359.log b/logs/20261107-151359/riscv64/qemu-riscv64-20261107-151359.log new file mode 100644 index 00000000..b33fd44a --- /dev/null +++ b/logs/20261107-151359/riscv64/qemu-riscv64-20261107-151359.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83dec58338917c986abb3a7a30034ff673bb6823e3ea031e04fd685698b1bbc3 +size 8333 diff --git a/logs/20261107-151539/amd64/qemu-amd64-20261107-151539.log b/logs/20261107-151539/amd64/qemu-amd64-20261107-151539.log new file mode 100644 index 00000000..c826b361 --- /dev/null +++ b/logs/20261107-151539/amd64/qemu-amd64-20261107-151539.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:902cd9ecaa671eadab2d3606ee8190ca0fc8f8a73811764b076ee102e0337a5f +size 7045 diff --git a/logs/20261107-152312/isabelle/isabelle-20261107-152312.log b/logs/20261107-152312/isabelle/isabelle-20261107-152312.log new file mode 100644 index 00000000..37e94b3f --- /dev/null +++ b/logs/20261107-152312/isabelle/isabelle-20261107-152312.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:082d76caba0474063ffbbf4fdb93be7ba2730ac4b92c4d3a101634818b6d5b26 +size 7783 diff --git a/logs/20261107-165611/amd64/qemu-amd64-20261107-165611.log b/logs/20261107-165611/amd64/qemu-amd64-20261107-165611.log new file mode 100644 index 00000000..67e838a0 --- /dev/null +++ b/logs/20261107-165611/amd64/qemu-amd64-20261107-165611.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f0b918ad976bfb73db05123c487b373cd574119f89570ee77ea56978ada78bb +size 6634 diff --git a/logs/20261107-165817/aarch64/qemu-aarch64-20261107-165817.log b/logs/20261107-165817/aarch64/qemu-aarch64-20261107-165817.log new file mode 100644 index 00000000..c4e48347 --- /dev/null +++ b/logs/20261107-165817/aarch64/qemu-aarch64-20261107-165817.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b32cdad9ba553bfcc98fb2ed27eb8f16490cfd7f261f65682c51945d509d378 +size 5602 diff --git a/logs/20261107-170149/riscv64/qemu-riscv64-20261107-170149.log b/logs/20261107-170149/riscv64/qemu-riscv64-20261107-170149.log new file mode 100644 index 00000000..26a51d3c --- /dev/null +++ b/logs/20261107-170149/riscv64/qemu-riscv64-20261107-170149.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a3a6599cd7f2a32ef4a0d9e6365ae20e27772ed0bee013a36fa4331f6df8b86 +size 7993 diff --git a/logs/20261107-170326/amd64/qemu-amd64-20261107-170326.log b/logs/20261107-170326/amd64/qemu-amd64-20261107-170326.log new file mode 100644 index 00000000..f0a72b02 --- /dev/null +++ b/logs/20261107-170326/amd64/qemu-amd64-20261107-170326.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd28cfe6b02b1306491bc8b70b6cd25d4ce17a124ef0388928ac61d4e0d85a4a +size 6708 diff --git a/logs/20261107-171020/isabelle/isabelle-20261107-171020.log b/logs/20261107-171020/isabelle/isabelle-20261107-171020.log new file mode 100644 index 00000000..eab46bd5 --- /dev/null +++ b/logs/20261107-171020/isabelle/isabelle-20261107-171020.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:749586aec05a93d552a14461ed6ee85c6e9ff0dce3d9a28482a2d692a62503c8 +size 7783 diff --git a/scripts/nightly.sh b/scripts/nightly.sh index 02d4ceeb..0b51dd10 100755 --- a/scripts/nightly.sh +++ b/scripts/nightly.sh @@ -17,7 +17,8 @@ # make -C v4 test-32: v4 with 32-bit cells, every test (the acceptance's last part). # scripts/nightly.sh proofs # isabelle build -c -D proof/: the StarForth session checked afresh every run, whatever is -# cached (HOL-Library is reused); its log is logs//isabelle/, as a boot's is. +# cached (HOL-Library is reused: the CI image has it built, ci/runner-image); its log is +# logs//isabelle/, as a boot's is. # scripts/nightly.sh images # The board images, into build/nightly/: # raspi5.img.xz Raspberry Pi 5 (make boot_image TARGET=RASPI), for Raspberry Pi Imager diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 00000000..c18dd8d8 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/site/brand/README.md b/site/brand/README.md new file mode 100644 index 00000000..2e81c18c --- /dev/null +++ b/site/brand/README.md @@ -0,0 +1,14 @@ +# site/brand -- the wikis' banner + +The top bar of both XWikis shows the starshipOS wordmark, not XWiki's logo, and the browser tab's +title ends "- StarshipOS" (Captain Bob, 2026-11-07). `starshipos-com.png` and `starshipos-org.png` +are the wordmark (Exo 2, white with an orange "OS", transparent), rendered at 2x. + +``` +XWIKI_USER=rajames XWIKI_PASSWORD=... site/brand/install.sh https://starshipos-f18.com starshipos-com.png +XWIKI_USER=rajames XWIKI_PASSWORD=... site/brand/install.sh https://starshipos-f18.org starshipos-org.png +``` + +The logo belongs to the colour theme, so this makes one of our own, `FlamingoThemes.StarshipOS` (a +copy of the stock Iceberg with the wordmark), and makes it the wiki's. To undo: set the wiki's +colour theme back to Iceberg (Administration, Look & Feel, Themes). diff --git a/site/brand/install.sh b/site/brand/install.sh new file mode 100755 index 00000000..d0eaa39f --- /dev/null +++ b/site/brand/install.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# install.sh -- the wiki's banner says StarshipOS, not XWiki (Captain Bob, 2026-11-07: "The banner +# XWiki should not say that"). +# +# site/brand/install.sh https://starshipos-f18.com starshipos-com.png +# site/brand/install.sh https://starshipos-f18.org starshipos-org.png +# +# In XWiki 17 the top bar's logo is the colour theme's. This makes a colour theme of our own, +# FlamingoThemes.StarshipOS -- a copy of the stock Iceberg with our wordmark as its logo -- and +# makes it the wiki's; the stock theme is not touched (set colorTheme back to +# FlamingoThemes.Iceberg to undo). It also makes the browser tab's title " - StarshipOS". +# Run again after changing the wordmark: the logo is replaced. XWIKI_USER and XWIKI_PASSWORD as for +# site/*/install.sh. Everything goes as XML: XWiki refuses form and plain-text writes without its +# form token. +set -euo pipefail +cd "$(dirname "$0")" +base=${1:?usage: install.sh } +logo=${2:?usage: install.sh } +rest="$base/rest/wikis/xwiki/spaces" +auth=(-u "${XWIKI_USER:?set XWIKI_USER}:${XWIKI_PASSWORD:?set XWIKI_PASSWORD}") +theme="$rest/FlamingoThemes/pages/StarshipOS" +x() { curl -fsS -m 60 "${auth[@]}" "$@"; } +prop() { # prop : set one property + x -o /dev/null -w " $2: %{http_code}\n" -X PUT -H 'Content-Type: application/xml' \ + --data-binary "$(python3 -c ' +import sys +from xml.sax.saxutils import escape +print("%s" % (escape(sys.argv[1]), escape(sys.argv[2])))' "$2" "$3")" \ + "$1/properties/$2" +} + +echo "=== $base: the colour theme FlamingoThemes.StarshipOS" +x -o /dev/null -w " page: %{http_code}\n" -X PUT -H 'Content-Type: application/xml' --data-binary \ + 'StarshipOSxwiki/2.1{{include reference="FlamingoThemesCode.ThemeSheet"/}}' \ + "$theme" +x -o /dev/null -w " logo $logo: %{http_code}\n" -X PUT -H 'Content-Type: image/png' --data-binary "@$logo" \ + "$theme/attachments/$logo" +have=$(x -H 'Accept: application/json' "$theme/objects/FlamingoThemesCode.ThemeClass" | + python3 -c 'import json,sys; print(len(json.load(sys.stdin).get("objectSummaries") or []))') +if [ "$have" = 0 ]; then + # Iceberg's theme object, every property as it is, posted to our page + x -H 'Accept: application/xml' "$rest/FlamingoThemes/pages/Iceberg/objects/FlamingoThemesCode.ThemeClass/0" | + x -o /dev/null -w " theme object, from Iceberg: %{http_code}\n" -X POST -H 'Content-Type: application/xml' \ + --data-binary @- "$theme/objects" +fi +prop "$theme/objects/FlamingoThemesCode.ThemeClass/0" logo "$logo" +prop "$theme/objects/FlamingoThemesCode.ThemeClass/0" logo-description "StarshipOS" + +echo "=== the wiki's preferences" +prefs="$rest/XWiki/pages/XWikiPreferences/objects/XWiki.XWikiPreferences/0" +prop "$prefs" colorTheme "FlamingoThemes.StarshipOS" +prop "$prefs" title "\$tdoc.getRenderedTitle('plain/1.0') - StarshipOS" + +echo "=== check, as a guest" +page=$(curl -sS -m 60 "$base/bin/login/XWiki/XWikiLogin") # a page that shows the chrome to a guest +case "$page" in *"FlamingoThemes/StarshipOS/$logo"*) echo " the banner's logo: ours";; *) echo " the banner's logo: NOT ours" >&2; exit 1;; esac +case "$page" in *"- StarshipOS"*) echo " the tab's title: ends - StarshipOS";; *) echo " the tab's title: NOT ours" >&2; exit 1;; esac diff --git a/site/brand/starshipos-com.png b/site/brand/starshipos-com.png new file mode 100644 index 00000000..c4c32a1e Binary files /dev/null and b/site/brand/starshipos-com.png differ diff --git a/site/brand/starshipos-org.png b/site/brand/starshipos-org.png new file mode 100644 index 00000000..2ebd2089 Binary files /dev/null and b/site/brand/starshipos-org.png differ diff --git a/site/colorlock.py b/site/colorlock.py new file mode 100644 index 00000000..f4ecd313 --- /dev/null +++ b/site/colorlock.py @@ -0,0 +1,64 @@ +"""colorlock.py -- keep a page's own colours inside a wiki whose theme forces its own. + +Both XWikis carry a high-contrast theme (skins/flamingo/cssVariablesInit.css on the server) that sets +every element black on white with !important. On our pages the design's colours win instead +(Captain Bob, 2026-11-07), and on those pages only; the rest of the wiki keeps the theme: + + - every colour an element states in its style="" is made !important -- an inline !important beats + any stylesheet's; + - a stylesheet scoped to the page's root (class "ssos") covers the elements that state no colour: + ".ssos *" outranks the theme's "*" (both !important, the more specific wins). +""" +import re + +ROOT_CLASS = "ssos" +STYLE = """""" +COLOUR_PROPS = re.compile(r"^\s*(color|background|background-color|border|border-(top|right|bottom|left)|" + r"border-color|border-(top|right|bottom|left)-color|accent-color|fill)\s*:", re.I) + + +def _lock_style(m): + decls = [] + for d in m.group(1).split(";"): + if d.strip() and COLOUR_PROPS.match(d) and "!important" not in d: + d = d.rstrip() + " !important" + decls.append(d) + return 'style="%s"' % ";".join(decls) + + +def lock(html, root_marker): + """root_marker: the opening text of the page's root element, e.g. '
= 1, root_marker + html = html.replace(root_marker, root_marker.replace("
+#menuview, #hierarchy_breadcrumb, .document-header, #xdocFooter, #footerglobal, #leftPanels, #rightPanels, +#leftPanelsToggle, #rightPanelsToggle, .tour, .tour-backdrop, #bootstrap_tour_restart, .xtour-restart, +#xwikimaincontainerinner > a.btn { display: none !important; } +html, body, #xwikimaincontainer, #xwikimaincontainerinner, #contentcontainer, #contentcolumn, .main, +#mainContentArea, #xwikicontent, #mainContentArea .row { background-color: #0A1222 !important; } +#contentcontainer, #contentcolumn, .main, #mainContentArea, #xwikicontent, #mainContentArea .row { + width: 100% !important; max-width: none !important; margin: 0 !important; padding: 0 !important; border: 0 !important; float: none !important; } +#xwikicontent > .ssos { border: 0 !important; } +#xwikimaincontainer { padding: 0 !important; } +.main hr:not(.ssos hr), #xwikicontent > :not(.ssos) { display: none !important; } +{{/html}}]]# +#end +{{/velocity}} +""" diff --git a/site/com/Inquiries.WebHome.xwiki b/site/com/Inquiries.WebHome.xwiki new file mode 100644 index 00000000..2e017896 --- /dev/null +++ b/site/com/Inquiries.WebHome.xwiki @@ -0,0 +1,120 @@ +{{velocity}} +#if ($isGuest) +#[[{{html clean="false"}}{{/html}}]]# +#end +{{/velocity}} +{{html clean="false"}} + + + +
+ +
+ +
+ +
+

Inquiries

+

Talk to the bridge.

+

Licensing, research collaboration, press or plain curiosity — every message is read by a human. Want to contribute code? Head to starshipos-f18.org.

+
+ +
+ +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +Replies usually within a few days. +
+
+ + + +
+ + + +
+{{/html}} diff --git a/site/com/Main.WebHome.xwiki b/site/com/Main.WebHome.xwiki new file mode 100644 index 00000000..c6b97f8e --- /dev/null +++ b/site/com/Main.WebHome.xwiki @@ -0,0 +1,152 @@ +{{velocity}} +#if ($isGuest) +#[[{{html clean="false"}}{{/html}}]]# +#end +{{/velocity}} +{{html clean="false"}} + + + +
+ +
+ +
+ +
+
+

ok> bare metal · FORTH · C99

+

An operating system that obeys a conservation law.

+

StarshipOS is a bare-metal operating system built from the ground up in StarForth and C99. One compudynamic engine governs every word, message, block and virtual machine — and holds K≡1.0 on every architecture it runs on.

+ +
+
+StarshipOS badge: Santino the mascot inside a ring on a starfield +
+
+ +
+
+
38,400+
experimental runs validating K≡1.0
+
3 ISAs
amd64 · aarch64 · riscv64
+
FORTH-79
compliant core, live at the ok> prompt
+
Patent pending
LithosAnanke kernel architecture
+
+
+ +
+

The system

+

One engine. One fabric. Everything in it obeys the same physics.

+
+
+

StarForth

+

the language and the VM

+

A FORTH-79 compliant adaptive virtual machine written in C99. Words execute by heat: what the system uses, it keeps close. Everything a user touches is StarForth — there is no compiler inside the box.

+
+
+

LithosAnanke

+

the microkernel

+

A bare-metal microkernel built around the Stadium — a single fabric where words, messages, blocks and VMs live side by side, all governed by one compudynamic engine.

+
+
+

The fleet

+

VMs that are born, not spawned

+

Hera births and governs the fleet. Artemis keeps the blocks. Console draws a vector framebuffer. Hermes, in the kernel, carries every message — and every message is checked against its ACL.

+
+
+
+ +
+
+

Phenomenon first

Observe the behaviour, then write the mathematics. Document before code.

+

Parity first

The same machine on every ISA, proven by reproducible dictionary hashes.

+

Identity on a key

Your certificate lives on a thumb drive. No key, no session.

+

Minimal by design

A small core of primitives; everything else composed in FORTH.

+
+
+ +
+
+
+

Research

+

James Law: K≡1.0

+

A conservation invariant that holds regardless of instruction set. Validated across 38,400+ runs in 128 configurations under QEMU on amd64, aarch64 and riscv64, and published on SSRN.

+
+ +
+
+ +
+

Next: silicon

+

From virtual machine to fabric.

+

StarForth V4 is being brought up as a mesh of tiny stack-machine cores on FPGA — words as instructions, messaging in hardware, and the compudynamic engine steering what runs and when. Same machine, same parity, now in logic.

+
+ +
+
+Painted portrait of Captain Bob at a keyboard with a mug of coffee, wearing a StarshipOS jacket +
+

From the bridge

+

Built by one engineer, in the open.

+

StarshipOS is designed and built by Captain Bob — Robert Allan James — drawing on roughly fifty years of embedded-systems work. Development happens in Ashland, Ohio, and in public on starshipos-f18.org.

+Get in touch +
+
+
+ +
+
+
+

Launch announcements

+

Milestones, releases and papers. Low volume. Unsubscribe anytime.

+
+
+ + + +
+
+
+ + + +
+{{/html}} diff --git a/site/com/README.md b/site/com/README.md new file mode 100644 index 00000000..782c042a --- /dev/null +++ b/site/com/README.md @@ -0,0 +1,18 @@ +# site/com -- the starshipos-f18.com XWiki's pages + +The public face of StarshipOS, made from the "StarshipOS.com" design canvas (claude.ai), whose +artboards are copied into `canvas/` as they are: Home (`Main.WebHome`), `Inquiries.WebHome`, +`TryIt.WebHome`. `build.py` turns each into one `{{html}}` block (a page author with script rights) +and says what changes on the way: the wiki's links and domains, the images as attachments +(`badge.jpg`, `captain-bob.jpg`, web-size copies of the canvas's), the real console in Try it, the +forms by e-mail. `install.sh` puts them on the wiki and checks a guest can read them: + +``` +site/com/build.py +XWIKI_USER=rajames XWIKI_PASSWORD=... site/com/install.sh +``` + +Change the design on the canvas, copy the artboard into `canvas/`, and run both again. + +Installed 2026-11-07. Try it is on the wiki but not in the navigation (#117): its console, +`try.starshipos-f18.com`, is not served yet (#115). diff --git a/site/com/TryIt.WebHome.xwiki b/site/com/TryIt.WebHome.xwiki new file mode 100644 index 00000000..cf231494 --- /dev/null +++ b/site/com/TryIt.WebHome.xwiki @@ -0,0 +1,128 @@ +{{velocity}} +#if ($isGuest) +#[[{{html clean="false"}}{{/html}}]]# +#end +{{/velocity}} +{{html clean="false"}} + + + +
+ +
+ +
+ +
+

ok> try it

+

A StarshipOS machine of your own.

+

Not a recording, not a simulation of StarshipOS: the console below is a real machine, started just for you and already at its prompt. You are Zuse, the superuser — of a machine that exists only for your session. There is nothing to break, and nothing you do is kept.

+
+ +
+
+
+StarshipOS · amd64 · your session +try.starshipos-f18.com +
+ +
+

If the console does not connect, every machine is in use: try again in a few minutes.

+
+ +
+
+
Zuse
the superuser, of this machine only
+
amd64
LithosAnanke v2.1.0 · StarForth v4
+
30 min
then it is gone; reload for a new one
+
Offline
no network inside the machine
+
+
+ +
+

Things to try

+

StarshipOS speaks FORTH.

+

Type a line and press Enter. When the machine has done it, it answers ok.

+
+
+

You type

+

3 4 + .

+

It answers

+

7 ok

+

Numbers go on a stack; + adds the top two; . prints the result.

+
+
+

You type

+

: SQUARE DUP * ;

+

It answers

+

ok

+

You have just taught the machine a new word, SQUARE.

+
+
+

You type

+

7 SQUARE .

+

It answers

+

49 ok

+

…and used it, as if it had always been there.

+
+
+
+ +
+
+

A machine of your own

Resumed at its prompt from one that booted, passed its 538 power-on self-tests and logged Zuse in — so you never wait for a boot.

+

The latest green build

Always the most recent build that booted on amd64, aarch64 and riscv64 and passed every acceptance check.

+

A Zuse of its own

Zuse is whoever holds the identity drive. Here it is a demo Zuse minted for this page alone — not anyone's real identity.

+

Gone when you go

Close the page, or let 30 minutes pass, and the machine is stopped and discarded. The next visitor starts clean.

+
+
+ +
+
+
+

A small server

+

Emulated, so a little slower.

+

The demo runs in an emulator on a modest server: slower than StarshipOS on real hardware, and only a few machines at once. The real thing boots straight from firmware, with nothing beneath it.

+
+ +
+
+ + + +
+{{/html}} diff --git a/site/com/badge.jpg b/site/com/badge.jpg new file mode 100644 index 00000000..fe2b6bd1 Binary files /dev/null and b/site/com/badge.jpg differ diff --git a/site/com/build.py b/site/com/build.py new file mode 100755 index 00000000..85c8d122 --- /dev/null +++ b/site/com/build.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""build.py -- the starshipos-f18.com XWiki pages, made from the "StarshipOS.com" design canvas. + + site/com/build.py canvas/{Main,Inquiries,TryIt}.dc.html -> {Main,Inquiries,TryIt}.WebHome.xwiki + +The canvas is the source: its artboards are copied into canvas/ as they are, and this turns each into +one {{html}} block (which needs a page author with script rights). What changes on the way: + + - the accent hole becomes its default colour, and the canvas's links become the wiki's pages + (Main.dc.html -> /bin/view/Main/, ...) and the real domains (starshipos.org -> starshipos-f18.org); + - the two images become attachments of Main.WebHome (badge.jpg, captain-bob.jpg -- web-size copies + of the canvas's uploads); + - Try it: the drawn console becomes the real one, an iframe of try.starshipos-f18.com; + - the inquiry form posts to the .org's PublicInquiry, which files it under Inquiries there + (admin-only, site/org/inquiries); the announcements form, which has no server, sends by e-mail; + - a link the canvas left as a placeholder ([SSRN PAPER URL]) points at the .org until it is given. + +install.sh puts the pages and the images on the wiki. +""" +import os +import re +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) +import colorlock # noqa: E402 the design's colours over the wiki's high-contrast theme + +ACCENT = "#F5891F" +FONTS = ('\n' + '') +ORG = "https://starshipos-f18.org/" +PAGES = ["Main", "Inquiries", "TryIt"] +IMAGES = {"/_blob/5b564d54d98e02ad600c913a9a1888f2": "badge.jpg", + "/_blob/5483451f292782a8b366e437181c4bcf": "captain-bob.jpg"} + +CONSOLE = '''
+
+StarshipOS · amd64 · your session +try.starshipos-f18.com +
+ +
+

If the console does not connect, every machine is in use: try again in a few minutes.

''' + + +def body(src): + m = re.search(r"(.*)", src, re.S) + html = re.sub(r".*?", "", m.group(1), flags=re.S).strip() + html = html.replace("{{accent}}", ACCENT) + for p in PAGES: + html = html.replace('href="%s.dc.html"' % p, 'href="/bin/view/%s/"' % p) + html = html.replace('href="https://starshipos.org"', 'href="%s"' % ORG) + html = html.replace(">starshipos.org", ">starshipos-f18.org") + html = html.replace("on starshipos.org.", "on starshipos-f18.org.") + html = html.replace('href="[SSRN PAPER URL]"', 'href="%s"' % ORG) + for blob, name in IMAGES.items(): + html = html.replace('src="%s"' % blob, 'src="/bin/download/Main/WebHome/%s"' % name) + html = html.replace("min-height: 100vh", "min-height: 0") # inside the wiki's page, not a screen + return html + + +def try_it(html): + start = html.index('
", html.index("[LIVE CONSOLE]")) + len("

") + return html[:start] + CONSOLE + html[end:] + + +def forms(html, page): + if page == "Main": + html = html.replace('
', + '') + html = html.replace('', + '\n' + '
', 1) + for i, n in (("q-name", "name"), ("q-email", "email"), ("q-org", "organization"), ("q-topic", "topic"), + ("q-msg", "message"), ("q-list", "announcements")): + html = html.replace('id="%s"' % i, 'id="%s" name="%s"' % (i, n), 1) + html = html.replace(' +Replies usually within a few days. +
+
+ + + + + +
+
+© 2026 Robert Allan James · A Starship Foundation project (in formation) +Homestarshipos.org +
+
+ +
+ + + + diff --git a/site/com/canvas/Main.dc.html b/site/com/canvas/Main.dc.html new file mode 100644 index 00000000..9bb787ce --- /dev/null +++ b/site/com/canvas/Main.dc.html @@ -0,0 +1,151 @@ + + + + +StarshipOS — Home + + + + + + + + + +
+ +
+ +
+ +
+
+

ok> bare metal · FORTH · C99

+

An operating system that obeys a conservation law.

+

StarshipOS is a bare-metal operating system built from the ground up in StarForth and C99. One compudynamic engine governs every word, message, block and virtual machine — and holds K≡1.0 on every architecture it runs on.

+ +
+
+StarshipOS badge: Santino the mascot inside a ring on a starfield +
+
+ +
+
+
38,400+
experimental runs validating K≡1.0
+
3 ISAs
amd64 · aarch64 · riscv64
+
FORTH-79
compliant core, live at the ok> prompt
+
Patent pending
LithosAnanke kernel architecture
+
+
+ +
+

The system

+

One engine. One fabric. Everything in it obeys the same physics.

+
+
+

StarForth

+

the language and the VM

+

A FORTH-79 compliant adaptive virtual machine written in C99. Words execute by heat: what the system uses, it keeps close. Everything a user touches is StarForth — there is no compiler inside the box.

+
+
+

LithosAnanke

+

the microkernel

+

A bare-metal microkernel built around the Stadium — a single fabric where words, messages, blocks and VMs live side by side, all governed by one compudynamic engine.

+
+
+

The fleet

+

VMs that are born, not spawned

+

Hera births and governs the fleet. Artemis keeps the blocks. Console draws a vector framebuffer. Hermes, in the kernel, carries every message — and every message is checked against its ACL.

+
+
+
+ +
+
+

Phenomenon first

Observe the behaviour, then write the mathematics. Document before code.

+

Parity first

The same machine on every ISA, proven by reproducible dictionary hashes.

+

Identity on a key

Your certificate lives on a thumb drive. No key, no session.

+

Minimal by design

A small core of primitives; everything else composed in FORTH.

+
+
+ +
+
+
+

Research

+

James Law: K≡1.0

+

A conservation invariant that holds regardless of instruction set. Validated across 38,400+ runs in 128 configurations under QEMU on amd64, aarch64 and riscv64, and published on SSRN.

+
+ +
+
+ +
+

Next: silicon

+

From virtual machine to fabric.

+

StarForth V4 is being brought up as a mesh of tiny stack-machine cores on FPGA — words as instructions, messaging in hardware, and the compudynamic engine steering what runs and when. Same machine, same parity, now in logic.

+
+ +
+
+Painted portrait of Captain Bob at a keyboard with a mug of coffee, wearing a StarshipOS jacket +
+

From the bridge

+

Built by one engineer, in the open.

+

StarshipOS is designed and built by Captain Bob — Robert Allan James — drawing on roughly fifty years of embedded-systems work. Development happens in Ashland, Ohio, and in public on starshipos.org.

+Get in touch +
+
+
+ +
+
+
+

Launch announcements

+

Milestones, releases and papers. Low volume. Unsubscribe anytime.

+
+
+ + + +
+
+
+ + + +
+
+ + + diff --git a/site/com/canvas/TryIt.dc.html b/site/com/canvas/TryIt.dc.html new file mode 100644 index 00000000..e6cc2522 --- /dev/null +++ b/site/com/canvas/TryIt.dc.html @@ -0,0 +1,136 @@ + + + + +StarshipOS — Try it + + + + + + + + + +
+ +
+ +
+ +
+

ok> try it

+

A StarshipOS machine of your own.

+

Not a recording, not a simulation of StarshipOS: the console below is a real machine, started just for you and already at its prompt. You are Zuse, the superuser — of a machine that exists only for your session. There is nothing to break, and nothing you do is kept.

+
+ +
+
+
+StarshipOS · amd64 · your session +try.starshipos-f18.com +
+
+
StarshipOS -- starting a throwaway machine, yours for up to 30 minutes. Nothing you do is kept.
+
Try: 3 4 + . : SQUARE DUP * ; 7 SQUARE .
+
Ready -- type a line and press Enter.
+
[zuse@Hera] ok> 3 4 + .
+
[zuse@Hera] 7 ok
+
[zuse@Hera] ok> : SQUARE DUP * ; 7 SQUARE .
+
[zuse@Hera] 49 ok
+
[zuse@Hera] ok>
+
+
+

[LIVE CONSOLE] On the site this panel is the machine itself — the console of try.starshipos-f18.com, embedded. If it does not connect, every machine is in use; try again in a few minutes.

+
+ +
+
+
Zuse
the superuser, of this machine only
+
amd64
LithosAnanke v2.1.0 · StarForth v4
+
30 min
then it is gone; reload for a new one
+
Offline
no network inside the machine
+
+
+ +
+

Things to try

+

StarshipOS speaks FORTH.

+

Type a line and press Enter. When the machine has done it, it answers ok.

+
+
+

You type

+

3 4 + .

+

It answers

+

7 ok

+

Numbers go on a stack; + adds the top two; . prints the result.

+
+
+

You type

+

: SQUARE DUP * ;

+

It answers

+

ok

+

You have just taught the machine a new word, SQUARE.

+
+
+

You type

+

7 SQUARE .

+

It answers

+

49 ok

+

…and used it, as if it had always been there.

+
+
+
+ +
+
+

A machine of your own

Resumed at its prompt from one that booted, passed its 538 power-on self-tests and logged Zuse in — so you never wait for a boot.

+

The latest green build

Always the most recent build that booted on amd64, aarch64 and riscv64 and passed every acceptance check.

+

A Zuse of its own

Zuse is whoever holds the identity drive. Here it is a demo Zuse minted for this page alone — not anyone's real identity.

+

Gone when you go

Close the page, or let 30 minutes pass, and the machine is stopped and discarded. The next visitor starts clean.

+
+
+ +
+
+
+

A small server

+

Emulated, so a little slower.

+

The demo runs in an emulator on a modest server: slower than StarshipOS on real hardware, and only a few machines at once. The real thing boots straight from firmware, with nothing beneath it.

+
+ +
+
+ + + +
+
+ + + diff --git a/site/com/captain-bob.jpg b/site/com/captain-bob.jpg new file mode 100644 index 00000000..5e421e16 Binary files /dev/null and b/site/com/captain-bob.jpg differ diff --git a/site/com/install.sh b/site/com/install.sh new file mode 100755 index 00000000..bc9a3d0f --- /dev/null +++ b/site/com/install.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# install.sh -- the starshipos-f18.com XWiki's pages, from the "StarshipOS.com" design canvas +# (build.py makes them): Main.WebHome (Home), Inquiries.WebHome, TryIt.WebHome, and the two +# images, attachments of Main.WebHome. All readable by everyone; the wiki's own rights decide the +# rest. Through XWiki's REST API: +# +# XWIKI_USER=rajames XWIKI_PASSWORD=... site/com/install.sh [https://starshipos-f18.com] +# +# Run it again after build.py: pages and images are replaced. It ends by checking, without +# logging in, that every page and image is there. +set -euo pipefail +cd "$(dirname "$0")" +base=${1:-https://starshipos-f18.com} +rest="$base/rest/wikis/xwiki" +user=${XWIKI_USER:?set XWIKI_USER} +pass=${XWIKI_PASSWORD:?set XWIKI_PASSWORD} +auth=(-u "$user:$pass") + +page() { # page <file>: create or replace <space>.WebHome + local body + body=$(python3 -c ' +import sys +from xml.sax.saxutils import escape +print("<page xmlns=\"http://www.xwiki.org\"><title>%sxwiki/2.1" + "%s" % (escape(sys.argv[1]), escape(open(sys.argv[2]).read())))' "$2" "$3") + curl -fsS -o /dev/null -w " page $1.WebHome: %{http_code}\n" "${auth[@]}" -X PUT \ + -H 'Content-Type: application/xml' --data-binary "$body" "$rest/spaces/$1/pages/WebHome" +} +attach() { # attach : create or replace an attachment of .WebHome + curl -fsS -o /dev/null -w " attachment $1.WebHome/$2: %{http_code}\n" "${auth[@]}" -X PUT \ + -H 'Content-Type: image/jpeg' --data-binary "@$2" "$rest/spaces/$1/pages/WebHome/attachments/$2" +} + +echo "=== $base, as $user" +page Main "StarshipOS" Main.WebHome.xwiki +attach Main badge.jpg +attach Main captain-bob.jpg +page Inquiries "Inquiries" Inquiries.WebHome.xwiki +page TryIt "Try StarshipOS" TryIt.WebHome.xwiki + +echo "=== as a guest" +bad= +for u in /bin/view/Main/ /bin/view/Inquiries/ /bin/view/TryIt/ \ + /bin/download/Main/WebHome/badge.jpg /bin/download/Main/WebHome/captain-bob.jpg; do + code=$(curl -s -o /dev/null -w '%{http_code}' "$base$u") + if [ "$code" = 200 ]; then echo " $u: 200"; else echo " $u: $code -- NOT readable"; bad=1; fi +done +[ -z "$bad" ] || { echo "install: a guest cannot read everything; see above" >&2; exit 1; } +echo "=== done: $base/bin/view/Main/" diff --git a/site/org/Admin.WebHome.xwiki b/site/org/Admin.WebHome.xwiki new file mode 100644 index 00000000..d53e7409 --- /dev/null +++ b/site/org/Admin.WebHome.xwiki @@ -0,0 +1,62 @@ +{{html clean="false"}} + + + +
+
+ +
+
+

Administrator only

+

Admin dashboard.

+

Seen by XWiki.rajames alone: the Admin space allows view and edit to that account and nobody else.

+
+
+

CI

+

Every workflow, live.

+
Acceptance -- the gate on every PR into masteracceptance.yml status runs
+
Build -- the quick check on StarForth-v4.0.0build.yml status runs
+
Nightly -- acceptance, images to Nexus, proofs (03:00 UTC)nightly.yml status runs
+
Nightly documents (04:00 UTC)nightly-docs.yml status runs
+
Runner imagerunner-image.yml status runs
+
Release -- a v<version> tagrelease.yml status runs
+
+
+

Run the project

+

Work, artifacts, sites.

+
+ +
+

Artifacts

+

Nexus and packages

+ +
+ +
+
+ +
+{{/html}} diff --git a/site/org/Contributors.WebHome.xwiki b/site/org/Contributors.WebHome.xwiki new file mode 100644 index 00000000..f92e7b57 --- /dev/null +++ b/site/org/Contributors.WebHome.xwiki @@ -0,0 +1,72 @@ +{{html clean="false"}} + + + +
+
+ +
+
+

Contributors

+

Contributors' dashboard.

+

For the members of XWiki.StarshipContributors and the administrator. Ask Captain Bob to be added.

+
+
+

How we work

+

The Captain Bob method.

+

Neither agile nor waterfall: one process from the developer's desk to master.

+
+
1

Found

An issue says what is wrong or missing, with the evidence: a log, a line of code.

+
2

Ruled

Captain Bob decides what is to be done. Nothing is built without a ruling, and no fix nobody asked for.

+
3

Built

On the working branch, StarForth-v4.0.0. C99, no GNU extensions; compose in FORTH before adding a C word.

+
4

Verified

The acceptance protocol, below; its logs are committed under logs/.

+
5

Merged

A pull request into master, which runs acceptance by itself. master takes nothing else.

+
+
+
+

The acceptance protocol

+

In this order, one QEMU at a time, in the foreground.

+
make -f kernel/Makefile ARCH=amd64   clean qemu
+make -f kernel/Makefile ARCH=aarch64 clean qemu
+make -f kernel/Makefile ARCH=riscv64 clean qemu
+make -f kernel/Makefile ARCH=amd64   clean qemu-stick-high
+make -C v4 test-32
+

Each boot reaches [zuse@Hera] ok> with POST: PASSED and every POST check passing, and the three architectures report the same dict_hash. make ARCH=amd64,aarch64,riscv64 init installs everything this needs.

+
Acceptance -- the gate on every PR into masteracceptance.yml status runs
+
Build -- the quick check on StarForth-v4.0.0build.yml status runs
+
Nightly -- acceptance, images to Nexus, proofs (03:00 UTC)nightly.yml status runs
+
+
+

Reference

+

Where things are, and what not to do.

+
+
+

Where things are

+

start here

+
  • The repository — README.md, docs/README.md; the design record is docs/fabric/FABRIC-3.md
  • New issue — the forms: bug, work, ruling, board
  • Open features and bugs
  • Board bring-up — boards/BRINGUP.md
  • Before touching a capsule: read experiments/bare_metal/README.md — the block ranges are shared by every capsule
+
+
+

Rules that bite

+

learned the hard way

+
  • No new branch, no git stash, without Captain Bob's say-so.
  • Never modify a registered, tested FORTH word to fix it: the problem is in the caller.
  • ACL policy lives in ACL.4th, not in C.
  • Tags are sacred: each has the logs proving its state.
+
+
+
+ +
+{{/html}} diff --git a/site/org/Main.WebHome.xwiki b/site/org/Main.WebHome.xwiki new file mode 100644 index 00000000..3a0e3184 --- /dev/null +++ b/site/org/Main.WebHome.xwiki @@ -0,0 +1,93 @@ +{{velocity}} +#if ($isGuest) +#[[{{html clean="false"}}{{/html}}]]# +#end +{{/velocity}} +{{html clean="false"}} + + + +
+
+ +
+
+

ok> the working home

+

Where StarshipOS is built.

+

StarshipOS is an operating system with no other operating system underneath it. LithosAnanke, a UEFI microkernel, boots straight from the firmware and runs StarForth, a FORTH virtual machine, as its only runtime. This wiki holds its design, its record and its dashboards; the public face is starshipos-f18.com.

+ +
+
+
v2.1.0
LithosAnanke, the UEFI microkernel
v4
StarForth, the F18 engine
3 ISAs
amd64 · aarch64 · riscv64
538
POST checks on every boot
+
+
+

The project

+

One repository, one process, one line to master.

+
+
+

The source

+

kernel, StarForth, proofs, documents

+

Everything in one repository: LithosAnanke, StarForth v3 and v4, the Isabelle/HOL proofs and the books.

Browse it → +
+
+

Issues

+

found, ruled, built, verified

+

Every piece of work starts as an issue with its evidence, and is ruled before it is built.

Open issues → +
+
+

Pull requests

+

the only way into master

+

master takes nothing but a pull request that passed the acceptance protocol.

Pull requests → +
+
+
+
+

How a change is accepted

+

Booted on three architectures, every time.

+

The kernel boots in QEMU on amd64, aarch64 and riscv64 to its prompt, its power-on self-test passing every check; the PC stick boots loaded the way a PC loads it; the 32-bit build passes its tests. A developer runs it by hand, a pull request runs it by itself, and the nightly runs it again before it publishes.

+
Acceptance -- the gate on every PR into masteracceptance.yml status
+
Nightly -- acceptance, images to Nexus, proofs (03:00 UTC)nightly.yml status
+
Nightly documents (04:00 UTC)nightly-docs.yml status
+
+
+
+
+

Contributors

+

Working on StarshipOS?

+

Contributors have a dashboard of their own: the method, the acceptance commands, the open work. Log in to see it.

+
+ +
+
+ +
+{{/html}} diff --git a/site/org/README.md b/site/org/README.md new file mode 100644 index 00000000..17c9a674 --- /dev/null +++ b/site/org/README.md @@ -0,0 +1,27 @@ +# site/org -- the .org XWiki's pages + +The project's working wiki at `starshipos-f18.org` (the public face is the `.com`; `demo/` has its +"Try it" page). Three pages, ruled by Captain Bob on 2026-11-07: + +| Page | File | Who sees it | +|---|---|---| +| `Main.WebHome` -- the landing page | `Main.WebHome.xwiki` | everyone, guests included | +| `Admin.WebHome` -- the admin dashboard | `Admin.WebHome.xwiki` | the administrator (`XWiki.rajames`) alone | +| `Contributors.WebHome` -- the contributors' dashboard | `Contributors.WebHome.xwiki` | the group `XWiki.StarshipContributors`, and the administrator | + +They are in the starshipos-f18.com look -- the "StarshipOS.com" design canvas (navy `#0A1222`, accent +`#F5891F`, JetBrains Mono with Exo 2 headings), where they are also drawn as the `.org` artboards. +`build.py` writes the three `.xwiki` files (and, with `--canvas `, the artboards) from one +source: edit it, not the `.xwiki` files. Each page is one `{{html}}` block, which needs a page author +with script rights (the administrator has them). CI is shown as Gitea's live workflow badges, which +need no login. + +`install.sh` puts them on the wiki through its REST API, makes the group and the rights, and checks +as a guest that the landing page is readable and the dashboards are not: + +``` +XWIKI_USER=rajames XWIKI_PASSWORD=... site/org/install.sh +``` + +Contributors are added to `XWiki.StarshipContributors` in the wiki's administration (Users and +Groups). The wiki-wide rights -- what guests may do anywhere else -- are the wiki's own, not this. diff --git a/site/org/build.py b/site/org/build.py new file mode 100755 index 00000000..dc37a069 --- /dev/null +++ b/site/org/build.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +"""build.py -- the .org XWiki's three pages in the starshipos-f18.com look (the "StarshipOS.com" +design canvas: navy #0A1222, accent #F5891F, JetBrains Mono, Exo 2 headings), from one source. + + site/org/build.py writes Main.WebHome.xwiki, Admin.WebHome.xwiki, + Contributors.WebHome.xwiki (each one {{html}} block) + site/org/build.py --canvas + also writes /project/Org*.dc.html, the same pages as + design-canvas artboards (status badges drawn as placeholders: + a canvas loads nothing from Gitea) + +The {{html}} macro needs a page author with script rights. install.sh puts the pages on the wiki. +""" +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) +import colorlock # noqa: E402 the design's colours over the wiki's high-contrast theme + +GITEA = "https://gitea.starshipos-f18.org/rajames/LithosAnanke" +COM = "https://starshipos-f18.com/" +ISSUES = GITEA + "/issues?state=open" +ACCENT = "#F5891F" +TEAL = "#4FD1B0" + +FONTS = ('\n' + '') + +H = "font-family: 'Exo 2', sans-serif" +CARD = "background: #111C30; border: 1px solid #22324D; border-radius: 6px; padding: 28px" +EYEBROW = ("margin: 0 0 12px; font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase; " + "color: {accent}") +BTN = ("display: inline-block; padding: 14px 24px; background: {accent}; color: #0A1222; " + "font-weight: 700; text-decoration: none; border-radius: 4px") +BTN2 = ("display: inline-block; padding: 14px 24px; border: 1px solid #34486A; color: #EEF2F7; " + "text-decoration: none; border-radius: 4px") +WRAP = "max-width: 1120px; margin: 0 auto" + +WORKFLOWS = { # file -> (branch for the badge, what it is) + "acceptance.yml": (None, "Acceptance -- the gate on every PR into master"), + "build.yml": ("StarForth-v4.0.0", "Build -- the quick check on StarForth-v4.0.0"), + "nightly.yml": ("master", "Nightly -- acceptance, images to Nexus, proofs (03:00 UTC)"), + "nightly-docs.yml": ("master", "Nightly documents (04:00 UTC)"), + "runner-image.yml": ("master", "Runner image"), + "release.yml": (None, "Release -- a v<version> tag"), +} + + +def badge(wf, canvas): + branch = WORKFLOWS[wf][0] + if canvas: + return ('[LIVE STATUS]') + q = "?branch=" + branch if branch else "" + return ('%s status' % (GITEA, wf, q, wf)) + + +def status_rows(wfs, canvas, runs=True): + rows = [] + for wf in wfs: + link = (' runs' % (GITEA, wf) + if runs else "") + rows.append('
' + '%s' + '%s%s
' + % (WORKFLOWS[wf][1], badge(wf, canvas), link)) + return "\n".join(rows) + + +def nav(here, links): + items = [] + for label, href, key in links: + cur = ' aria-current="page"' if key == here else "" + style = ("color: #EEF2F7; text-decoration: none; border-bottom: 2px solid {accent}" + if key == here else "color: #C8D1DE; text-decoration: none") + items.append('%s' % (href, cur, style, label)) + items.append('starshipos-f18.com →' + % COM) + return ('
\n' + '\n
' % (WRAP, links[0][1], H, "".join(items))) + + +FOOTER = ('
\n' + '
\n' + '© 2026 Robert Allan James · A Starship Foundation project (in formation)\n' + 'Source' + 'starshipos-f18.com\n
\n
' % (WRAP, GITEA, COM)) + + +def hero(eyebrow, title, text, buttons=""): + return ('
\n' + '

%s

\n' + '

%s

\n' + '

%s

\n%s' + '
' % (WRAP, EYEBROW.replace("{accent}", TEAL), eyebrow, H, title, text, + ('
%s
\n' % buttons) + if buttons else "")) + + +def section(eyebrow, title, body, pad="48px 24px"): + return ('
\n

%s

\n' + '

%s

\n' + '%s\n
' % (WRAP, pad, EYEBROW, eyebrow, H, title, body)) + + +def card(title, sub, body): + return ('
\n

%s

\n' + '

%s

\n%s\n
' + % (CARD, H, title, sub, body)) + + +def grid(items, min_w=280): + return ('
\n%s\n
' % (min_w, "\n".join(items))) + + +def links(items): + return ('
    %s
' + % "".join("
  • %s
  • " % i for i in items)) + + +def a(href, text): + return '%s' % (href, text) + + +def code(s): + return '%s' % s + + +def page_main(canvas, url): + links_ = [("Home", url("Main"), "Main"), ("Contributors", url("Contributors"), "Contributors")] + glance = [("v2.1.0", "LithosAnanke, the UEFI microkernel"), ("v4", "StarForth, the F18 engine"), + ("3 ISAs", "amd64 · aarch64 · riscv64"), ("538", "POST checks on every boot")] + strip = ('
    \n
    %s
    \n
    ' + % (WRAP, "".join('
    %s
    ' + '
    %s
    ' % (H, n, t) + for n, t in glance))) + project = section("The project", "One repository, one process, one line to master.", grid([ + card("The source", "kernel, StarForth, proofs, documents", + '

    Everything in one repository: LithosAnanke, ' + 'StarForth v3 and v4, the Isabelle/HOL proofs and the books.

    ' + a(GITEA, "Browse it →")), + card("Issues", "found, ruled, built, verified", + '

    Every piece of work starts as an issue with its ' + 'evidence, and is ruled before it is built.

    ' + a(GITEA + "/issues", "Open issues →")), + card("Pull requests", "the only way into master", + '

    master takes nothing but a pull request that ' + 'passed the acceptance protocol.

    ' + a(GITEA + "/pulls", "Pull requests →")), + ]), pad="96px 24px 48px") + accept = section("How a change is accepted", "Booted on three architectures, every time.", + '

    The kernel boots in QEMU ' + 'on amd64, aarch64 and riscv64 to its prompt, its power-on self-test passing every check; ' + 'the PC stick boots loaded the way a PC loads it; the 32-bit build passes its tests. A ' + 'developer runs it by hand, a pull request runs it by itself, and the nightly runs it ' + 'again before it publishes.

    \n
    %s
    ' + % (CARD, status_rows(["acceptance.yml", "nightly.yml", "nightly-docs.yml"], canvas, False))) + contrib = ('
    \n
    \n
    \n' + '

    Contributors

    \n

    ' + 'Working on StarshipOS?

    \n

    Contributors have a dashboard ' + 'of their own: the method, the acceptance commands, the open work. Log in to see it.

    \n
    \n' + '\n
    \n
    ' + % (WRAP, CARD, EYEBROW, H, url("login"), BTN, url("Contributors"), BTN2)) + return "\n".join([ + nav("Main", links_), + hero("ok> the working home", "Where StarshipOS is built.", + "StarshipOS is an operating system with no other operating system underneath it. LithosAnanke, a " + "UEFI microkernel, boots straight from the firmware and runs StarForth, a FORTH virtual machine, " + "as its only runtime. This wiki holds its design, its record and its dashboards; the public face " + "is " + a(COM, "starshipos-f18.com") + ".", + 'Browse the sourceTry it in a browser' + % (GITEA, BTN, COM + "bin/view/TryIt/", BTN2)), + strip, project, accept, contrib, FOOTER]) + + +def page_admin(canvas, url): + links_ = [("Home", url("Main"), "Main"), ("Contributors", url("Contributors"), "Contributors"), + ("Admin", url("Admin"), "Admin")] + ci = section("CI", "Every workflow, live.", '
    %s
    ' % (CARD, status_rows(list(WORKFLOWS), canvas)), + pad="0 24px 48px") + work = card("Work", "Gitea", links([ + a(GITEA + "/pulls?state=open", "Open pull requests"), + a(ISSUES, "Open issues") + " — " + a(ISSUES + "&labels=23", "in progress") + ", " + + a(ISSUES + "&labels=20", "blocked") + ", " + a(ISSUES + "&labels=21", "needs acceptance"), + a(GITEA + "/milestones", "Milestones") + " and " + a(GITEA + "/projects", "the project board"), + a(GITEA + "/settings/branches", "Branch protection") + " — master: no pushes, merges only after Acceptance", + a(GITEA + "/settings/actions/secrets", "Actions secrets") + " — " + code("NEXUS_CI_PASSWORD") + ", " + + code("PACKAGES_TOKEN") + ", " + code("CAPSULE_SIGN_KEY"), + a(GITEA + "/settings/actions/runners", "Runners"), + a("https://gitea.starshipos-f18.org/user/settings/applications", "Access tokens") + + " — revoke what a session no longer needs"])) + arts = card("Artifacts", "Nexus and packages", links([ + a("https://nexus.starshipos-f18.org/#browse/browse:starshipos-snapshots:org%2Fstarship", "Snapshots") + + " — " + code("kernelImage") + " (pc, raspi5, milkv, logs), " + code("documentation") + + " (book, vol1–vol3, api)", + a("https://nexus.starshipos-f18.org/#browse/browse:starshipos-releases:org%2Fstarship", "Releases"), + a("https://gitea.starshipos-f18.org/rajames/-/packages", "Container images") + " — " + + code("lithosananke-runner"), ])) + sites = card("Sites", "and the wiki itself", links([ + a(COM, "starshipos-f18.com") + " and " + a(COM + "bin/view/TryIt/", "Try it"), + a(url("Contributors"), "Contributors' dashboard"), + a(url("admin"), "Wiki administration") + " — users, the group " + code("XWiki.StarshipContributors") + + ", rights"])) + return "\n".join([ + nav("Admin", links_), + hero("Administrator only", "Admin dashboard.", + "Seen by " + code("XWiki.rajames") + " alone: the Admin space allows view and edit to that " + "account and nobody else."), + ci, section("Run the project", "Work, artifacts, sites.", grid([work, arts, sites], 300), pad="0 24px 96px"), + FOOTER]) + + +def page_contrib(canvas, url): + links_ = [("Home", url("Main"), "Main"), ("Contributors", url("Contributors"), "Contributors")] + steps = [("Found", "An issue says what is wrong or missing, with the evidence: a log, a line of code."), + ("Ruled", "Captain Bob decides what is to be done. Nothing is built without a ruling, and no fix " + "nobody asked for."), + ("Built", "On the working branch, " + code("StarForth-v4.0.0") + ". C99, no GNU extensions; compose " + "in FORTH before adding a C word."), + ("Verified", "The acceptance protocol, below; its logs are committed under " + code("logs/") + "."), + ("Merged", "A pull request into " + code("master") + ", which runs acceptance by itself. master takes " + "nothing else.")] + method = section("How we work", "The Captain Bob method.", + '

    Neither agile nor waterfall: ' + 'one process from the developer\'s desk to master.

    \n' + grid( + ['
    ' + '%d

    %s

    ' + '

    %s

    ' + % (CARD, H, i + 1, H, t, d) for i, (t, d) in enumerate(steps)], 190), pad="0 24px 48px") + cmds = ("make -f kernel/Makefile ARCH=amd64 clean qemu\n" + "make -f kernel/Makefile ARCH=aarch64 clean qemu\n" + "make -f kernel/Makefile ARCH=riscv64 clean qemu\n" + "make -f kernel/Makefile ARCH=amd64 clean qemu-stick-high\n" + "make -C v4 test-32") + accept = section("The acceptance protocol", "In this order, one QEMU at a time, in the foreground.", + '
    '
    +                     '%s
    \n

    Each boot reaches ' + '%s with %s and every POST check passing, and the three architectures report the same ' + '%s. %s installs everything this needs.

    \n
    %s
    ' + % (cmds, code("[zuse@Hera] ok>"), code("POST: PASSED"), code("dict_hash"), + code("make ARCH=amd64,aarch64,riscv64 init"), CARD, + status_rows(["acceptance.yml", "build.yml", "nightly.yml"], canvas))) + where = card("Where things are", "start here", links([ + a(GITEA, "The repository") + " — " + code("README.md") + ", " + code("docs/README.md") + + "; the design record is " + code("docs/fabric/FABRIC-3.md"), + a(GITEA + "/issues/new/choose", "New issue") + " — the forms: bug, work, ruling, board", + a(ISSUES + "&labels=2", "Open features") + " and " + a(ISSUES + "&labels=1", "bugs"), + a(ISSUES + "&milestone=2", "Board bring-up") + " — " + code("boards/BRINGUP.md"), + "Before touching a capsule: read " + code("experiments/bare_metal/README.md") + + " — the block ranges are shared by every capsule"])) + rules = card("Rules that bite", "learned the hard way", links([ + "No new branch, no " + code("git stash") + ", without Captain Bob's say-so.", + "Never modify a registered, tested FORTH word to fix it: the problem is in the caller.", + "ACL policy lives in " + code("ACL.4th") + ", not in C.", + "Tags are sacred: each has the logs proving its state."])) + return "\n".join([ + nav("Contributors", links_), + hero("Contributors", "Contributors' dashboard.", + "For the members of " + code("XWiki.StarshipContributors") + " and the administrator. " + "Ask Captain Bob to be added."), + method, accept, section("Reference", "Where things are, and what not to do.", + grid([where, rules], 360), pad="0 24px 96px"), + FOOTER]) + + +PAGES = { # name -> (builder, title, canvas file, canvas height) + "Main": (page_main, "StarshipOS", "OrgHome.dc.html", 2300), + "Admin": (page_admin, "Admin Dashboard", "OrgAdmin.dc.html", 1900), + "Contributors": (page_contrib, "Contributors' Dashboard", "OrgContributors.dc.html", 2500), +} + + +def wiki_url(key): + return {"login": "/bin/login/XWiki/XWikiLogin?xredirect=/bin/view/Contributors/", + "admin": "/bin/admin/XWiki/XWikiPreferences"}.get(key, "/bin/view/%s/" % key) + + +def canvas_url(key): + files = {k: v[2] for k, v in PAGES.items()} + return files.get(key, "https://starshipos-f18.org" + wiki_url(key)) + + +def body(name, canvas): + html = PAGES[name][0](canvas, canvas_url if canvas else wiki_url) + return ('
    \n%s\n
    ' % html) + + +def write_wiki(name): + html = colorlock.lock(body(name, False).replace("{accent}", ACCENT), "
    \n\n\n\nStarshipOS.org — %s' + '\n\n\n\n\n\n%s\n\n\n%s\n' + '\n\n\n\n' % (title, FONTS, html, h)) + os.makedirs(os.path.join(root, "project"), exist_ok=True) + with open(os.path.join(root, "project", file), "w") as f: + f.write(doc) + + +if __name__ == "__main__": + os.chdir(os.path.dirname(os.path.abspath(__file__))) + canvas_root = sys.argv[2] if len(sys.argv) > 2 and sys.argv[1] == "--canvas" else None + for n in PAGES: + write_wiki(n) + if canvas_root: + write_canvas(n, canvas_root) + print("wrote", ", ".join("%s.WebHome.xwiki" % n for n in PAGES), + "and the canvas artboards" if canvas_root else "") diff --git a/site/org/inquiries/ClassSetup.xwiki b/site/org/inquiries/ClassSetup.xwiki new file mode 100644 index 00000000..13863aec --- /dev/null +++ b/site/org/inquiries/ClassSetup.xwiki @@ -0,0 +1,26 @@ +{{velocity}} +## Makes the inquiry class's fields, once. Run it by viewing this page as an administrator +## (programming right); install.sh does. Viewing it again changes nothing. +#if (!$hasProgramming) +Needs the programming right. +#else +#set ($xdoc = $xwiki.getDocument('Inquiries.Code.InquiryClass').getDocument().clone()) +#set ($c = $xdoc.getXClass()) +#set ($changed = false) +#if (!$c.get('name'))#set ($d = $c.addTextField('name', 'Name', 60))#set ($changed = true)#end +#if (!$c.get('email'))#set ($d = $c.addTextField('email', 'Email', 60))#set ($changed = true)#end +#if (!$c.get('organization'))#set ($d = $c.addTextField('organization', 'Organization', 60))#set ($changed = true)#end +#if (!$c.get('topic'))#set ($d = $c.addTextField('topic', 'Topic', 60))#set ($changed = true)#end +#if (!$c.get('message'))#set ($d = $c.addTextAreaField('message', 'Message', 80, 12))#set ($d = $c.get('message').setStringValue('contenttype', 'PureText'))#set ($changed = true)#end +#if (!$c.get('announcements'))#set ($d = $c.addBooleanField('announcements', 'Wants announcements', 'yesno'))#set ($changed = true)#end +#if (!$c.get('source'))#set ($d = $c.addTextField('source', 'Source', 60))#set ($changed = true)#end +#if (!$c.get('status'))#set ($d = $c.addStaticListField('status', 'Status', 1, false, 'new|read|answered|closed'))#set ($changed = true)#end +#if ($changed) +#set ($d = $xdoc.setTitle('Inquiry class')) +#set ($d = $xwiki.getXWiki().saveDocument($xdoc, 'The inquiry fields', $xcontext.getContext())) +CLASS-SAVED +#else +CLASS-UNCHANGED +#end +#end +{{/velocity}} diff --git a/site/org/inquiries/Inquiries.WebHome.xwiki b/site/org/inquiries/Inquiries.WebHome.xwiki new file mode 100644 index 00000000..466efdc1 --- /dev/null +++ b/site/org/inquiries/Inquiries.WebHome.xwiki @@ -0,0 +1,3 @@ +Every inquiry sent from the form on [[starshipos-f18.com>>https://starshipos-f18.com/bin/view/Inquiries/]], newest first. Open one for the whole message and to set its status. Only the administrator can see this space. + +{{liveData id="inquiries" source="liveTable" sourceParameters="className=Inquiries.Code.InquiryClass&translationPrefix=" properties="doc.title,doc.creationDate,name,email,topic,status" sort="doc.creationDate:desc" limit="25"}}{{/liveData}} diff --git a/site/org/inquiries/InquirySheet.xwiki b/site/org/inquiries/InquirySheet.xwiki new file mode 100644 index 00000000..7a78e4a1 --- /dev/null +++ b/site/org/inquiries/InquirySheet.xwiki @@ -0,0 +1,26 @@ +{{velocity}} +#set ($o = $doc.getObject('Inquiries.Code.InquiryClass')) +#if (!$o) +This page holds no inquiry. +#else +[[← All inquiries>>Inquiries.WebHome]] + +|=Received|$escapetool.xml($xwiki.formatDate($doc.creationDate)) +|=Status|$doc.display('status', $o) +|=Name|$doc.display('name', $o) +|=Email|$doc.display('email', $o) +|=Organization|$doc.display('organization', $o) +|=Topic|$doc.display('topic', $o) +|=Announcements|$doc.display('announcements', $o) +|=Source|$doc.display('source', $o) + +== Message == + +$doc.display('message', $o) + +#if ($xcontext.action == 'view') +## the e-mail is checked as plain characters when it arrives (PublicInquiry), and escaped here too +{{html}}

    Set its status#if ("$!o.getProperty('email').value" != '') · Reply by e-mail#end

    {{/html}} +#end +#end +{{/velocity}} diff --git a/site/org/inquiries/PublicInquiry.WebHome.xwiki b/site/org/inquiries/PublicInquiry.WebHome.xwiki new file mode 100644 index 00000000..0ff60d79 --- /dev/null +++ b/site/org/inquiries/PublicInquiry.WebHome.xwiki @@ -0,0 +1,41 @@ +{{velocity}} +## Receives the inquiry form on starshipos-f18.com (POST) and files it as a page under +## Inquiries.Entries, which only the administrator can read. It runs with this page's author's +## rights (getDocumentAsAuthor, saveAsAuthor): a guest can reach nothing but this. Nothing a visitor +## sends is ever written into this page's output or an entry's title: with this author's rights, +## text that reached the wiki markup could run a macro. +#macro (field $n $max)#set ($v = "$!request.getParameter($n)")#set ($v = $v.trim())#if ($v.length() > $max)#set ($v = $v.substring(0, $max))#end#end +#if ($request.method != 'POST') +This page receives the inquiry form on [[starshipos-f18.com>>https://starshipos-f18.com/bin/view/Inquiries/]]. +#else +#field('website' 200)#set ($trap = $v) +#field('name' 200)#set ($name = $v) +#field('email' 200)#set ($email = $v) +#field('organization' 200)#set ($org = $v) +#field('topic' 100)#set ($topic = $v) +#field('message' 10000)#set ($message = $v) +#set ($wants = "$!request.getParameter('announcements')" != '') +#if ($trap != '') +## a robot filled the field people cannot see: say thanks, keep nothing +{{html}}

    Thank you. Your inquiry has been received.

    {{/html}} +#elseif ($name == '' || $message == '' || !$email.matches('^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')) +{{html}}

    Your inquiry was not sent: a name, a valid e-mail address and a message are needed. Back to the form.

    {{/html}} +#else +#set ($stamp = $datetool.format('yyyyMMdd-HHmmss', $datetool.date)) +#set ($ref = "Inquiries.Entries.I${stamp}-$mathtool.random(1000, 9999)") +#set ($entry = $xwiki.getDocumentAsAuthor($ref)) +#set ($d = $entry.setTitle("Inquiry $stamp")) +#set ($o = $entry.newObject('Inquiries.Code.InquiryClass')) +#set ($d = $o.set('name', $name)) +#set ($d = $o.set('email', $email)) +#set ($d = $o.set('organization', $org)) +#set ($d = $o.set('topic', $topic)) +#set ($d = $o.set('message', $message)) +#if ($wants)#set ($d = $o.set('announcements', 1))#else#set ($d = $o.set('announcements', 0))#end +#set ($d = $o.set('source', 'starshipos-f18.com')) +#set ($d = $o.set('status', 'new')) +#set ($d = $entry.saveAsAuthor('An inquiry from the .com form')) +{{html}}

    Thank you. Your inquiry has been received; every message is read by a human, and replies usually come within a few days.

    Back to starshipos-f18.com

    {{/html}} +#end +#end +{{/velocity}} diff --git a/site/org/inquiries/install.sh b/site/org/inquiries/install.sh new file mode 100755 index 00000000..bbdb2e76 --- /dev/null +++ b/site/org/inquiries/install.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# install.sh -- inquiries on the .org XWiki (Captain Bob, 2026-11-07: the .com's inquiries go to an +# admin-only page on the .org, master/detail): +# +# Inquiries.WebPreferences view, edit, comment, delete: $ADMIN_USER alone -- made first +# Inquiries.Code.InquiryClass the fields (made by Inquiries.Code.ClassSetup, viewed once as the +# administrator: the class has no REST API), bound to the sheet +# Inquiries.Code.InquirySheet the detail: one inquiry, its status set by editing the page +# Inquiries.WebHome the master: every inquiry, newest first (a Live Data table) +# Inquiries.Entries.I