From be2c204d568c3cdd7bf5830e28dde154cb6be62b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 11:38:50 +0000 Subject: [PATCH] site(com): Downloads and Documents, built from a release; the nav links; the release's SHA256SUMS covers every file (#126, #157) [skip ci] The design canvas (v17) gains the Documents artboard -- the Book of Fabric and the Dictionary of v4 first, then the book, the three volumes and the API reference, each with its SHA-256, and the SSRN paper -- and Downloads and Documents in every .com nav; Downloads' reading list becomes a pointer to Documents. site/com/canvas/ copied from it. site/com/build.py --release TAG --sums SHA256SUMS builds Downloads and Documents from the release ([TAG], [RELEASE]/, [SHA:]; a file the release's SHA256SUMS lacks fails the build). Without a release it builds Home, Try it and Inquiries exactly as before -- byte for byte -- with no link to the two pages, so they go live only with alpha.4. install.sh installs them when built. scripts/nightly.sh gitea-release: the release's SHA256SUMS now covers every file it attaches -- the images' sums, then the PDFs and the Root CA's certificate -- so `sha256sum -c SHA256SUMS` checks all of it and Documents finds each sum. The Nexus artifacts keep their own. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2 --- .../plans/2026-11-10-alpha4-documents.md | 14 +- scripts/nightly.sh | 15 +- site/com/build.py | 53 ++++++- site/com/canvas/Documents.dc.html | 109 +++++++++++++ site/com/canvas/Downloads.dc.html | 145 ++++++++++++++++++ site/com/canvas/Inquiries.dc.html | 2 + site/com/canvas/Main.dc.html | 2 + site/com/canvas/TryIt.dc.html | 2 + site/com/install.sh | 9 +- 9 files changed, 337 insertions(+), 14 deletions(-) create mode 100644 site/com/canvas/Documents.dc.html create mode 100644 site/com/canvas/Downloads.dc.html diff --git a/docs/v4.0.0/plans/2026-11-10-alpha4-documents.md b/docs/v4.0.0/plans/2026-11-10-alpha4-documents.md index 7f4dbeb2..6580aa22 100644 --- a/docs/v4.0.0/plans/2026-11-10-alpha4-documents.md +++ b/docs/v4.0.0/plans/2026-11-10-alpha4-documents.md @@ -18,9 +18,15 @@ Captain Bob, 2026-11-10: "Do it" -- the rest of alpha.4 that lives in the tree, - [x] the table in the tree, `docs/reference/v4-dictionary.tsv`: all 318 entries, written in three alphabetical batches against v4's own definitions (the source gave 54 stacks and 105 descriptions to start from); `check` passes - [x] the check: `mkdictionary.py check` -- every live word has a finished entry, every entry a live word; run by `make dictionary`, so a stale table fails the build and the nightly - [x] the PDF, alphabetical: `make dictionary` -> `build/docs/Dictionary.pdf` (20 pages; `docs/book/dictionary.tex`); the nightly's docs stage builds it, `publish` sends classifier `dictionary`, the release attaches it -- [ ] the Documents page: canvas artboard, then `site/com` +- [x] the Documents page: the canvas artboard (`project/Documents.dc.html`, canvas v17: the Book of Fabric and the Dictionary first, then the book, the volumes, the API; each with its SHA-256; the SSRN paper), then `site/com/canvas/Documents.dc.html` ## #126 -- the Downloads page live -- [ ] `site/com` builds Downloads (and Documents) from the release: its tag, `SHA256SUMS`, the - Root CA's fingerprint -- [ ] the nav links on Home, Try it, Inquiries +- [x] `site/com/build.py --release TAG --sums SHA256SUMS` builds Downloads and Documents: `[TAG]`, `[RELEASE]/` (the + Gitea release's file), `[SHA:]`; a file not in SHA256SUMS fails the build. Without a release it builds the three + pages as before, byte for byte, with no link to the two. `install.sh` installs them when they are built. The release's + SHA256SUMS now covers every file it attaches (the images, the PDFs, the Root CA's certificate): `nightly.sh gitea-release` +- [x] the nav links on Home, Try it, Inquiries (canvas v17; shown only when the two pages are built) + +**At the alpha.4 release** (after `gitea-release`): download the release's `SHA256SUMS`, then +`python3 site/com/build.py --release v4.0.0-alpha.4 --sums SHA256SUMS` and +`XWIKI_USER=rajames XWIKI_PASSWORD=... site/com/install.sh` (Captain Bob's; the .com's login). diff --git a/scripts/nightly.sh b/scripts/nightly.sh index c1b63dd1..c46890e4 100755 --- a/scripts/nightly.sh +++ b/scripts/nightly.sh @@ -195,15 +195,23 @@ gitea-release) : "${RELEASE_TOKEN:?RELEASE_TOKEN: a token that may write the repository}" api=${GITEA_API:-${GITHUB_SERVER_URL:-https://gitea.starshipos-f18.org}/api/v1/repos/${GITHUB_REPOSITORY:-rajames/LithosAnanke}} need "$OUT/StarshipOS.iso" "$OUT/pc-stick.img.xz" "$OUT/raspi5.img.xz" "$OUT/MilkV.img.xz" "$OUT/SHA256SUMS" - files=("$OUT/StarshipOS.iso" "$OUT/pc-stick.img.xz" "$OUT/raspi5.img.xz" "$OUT/MilkV.img.xz" "$OUT/SHA256SUMS") - for f in "$DOCS"/*.pdf ci/pki/starshipos-root-ca.pem; do [ -f "$f" ] && files+=("$f"); done + # The release's SHA256SUMS covers every file it attaches -- the images' sums as the images + # stage wrote them, then the documents and the Root CA's certificate -- so that + # `sha256sum -c SHA256SUMS` checks all of it, and the .com's Documents page (site/com/build.py, + # #157) finds each document's sum. The Nexus artifacts keep their own SHA256SUMS. + rel=$(mktemp -d) + cp "$OUT/SHA256SUMS" "$rel/SHA256SUMS" + extra=("$DOCS"/*.pdf ci/pki/starshipos-root-ca.pem) + for f in "${extra[@]}"; do [ -f "$f" ] && (cd "$(dirname "$f")" && sha256sum "$(basename "$f")") >> "$rel/SHA256SUMS"; done + files=("$OUT/StarshipOS.iso" "$OUT/pc-stick.img.xz" "$OUT/raspi5.img.xz" "$OUT/MilkV.img.xz" "$rel/SHA256SUMS") + for f in "${extra[@]}"; do [ -f "$f" ] && files+=("$f"); done gitea() { curl -fsS -H "Authorization: token $RELEASE_TOKEN" "$@"; } body=$(python3 -c ' import json, sys notes, sums, version = sys.argv[1], open(sys.argv[2]).read(), sys.argv[3] print(json.dumps(notes.strip() + "\n\n## Checksums (SHA-256)\n\n```\n" + sums.strip() + "\n```\n\n" "Also in Nexus: `org.starship:kernelImage:" + version + "` and `org.starship:documentation:" + version + "`.\n"))' \ - "$(git tag -l --format='%(contents)' "$tag")" "$OUT/SHA256SUMS" "$VERSION") + "$(git tag -l --format='%(contents)' "$tag")" "$rel/SHA256SUMS" "$VERSION") id=$(gitea "$api/releases/tags/$tag" 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' 2>/dev/null || true) if [ -z "$id" ]; then pre=false; case "$tag" in *-*) pre=true;; esac @@ -220,6 +228,7 @@ print(json.dumps(notes.strip() + "\n\n## Checksums (SHA-256)\n\n```\n" + sums.st case " $have " in *" $n "*) echo " $n: there"; continue;; esac gitea -o /dev/null -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$n" && echo " $n: attached" done + rm -f "$rel/SHA256SUMS" && rmdir "$rel" ;; publish-docs) url=${2:-}; [ -n "$url" ] || usage diff --git a/site/com/build.py b/site/com/build.py index 7bb4c353..14f2b198 100755 --- a/site/com/build.py +++ b/site/com/build.py @@ -3,7 +3,9 @@ # Copyright (c) 2023-2026 Robert A. James """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 + site/com/build.py [--release TAG --sums SHA256SUMS] + canvas/{Main,Inquiries,TryIt}.dc.html -> {Main,Inquiries,TryIt}.WebHome.xwiki, + and with a release, canvas/{Downloads,Documents}.dc.html too 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: @@ -15,6 +17,10 @@ one {{html}} block (which needs a page author with script rights). What changes - 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; + - Downloads and Documents (#126, #157) are built from a release: --release TAG and the release's + SHA256SUMS fill in [TAG], [RELEASE]/ (the file on the Gitea release) and [SHA:]. + Without a release they are not built, and the other pages' links to them are taken out, so + nothing on the site points at a page that is not there (they go live with alpha.4); install.sh puts the pages and the images on the wiki. """ @@ -31,6 +37,8 @@ FONTS = ('\n' '&family=Exo+2:wght@600;700&display=swap" rel="stylesheet">') ORG = "https://starshipos-f18.org/" PAGES = ["Main", "Inquiries", "TryIt"] +RELEASE_PAGES = ["Downloads", "Documents"] +RELEASES = "https://gitea.starshipos-f18.org/rajames/LithosAnanke/releases/download/" IMAGES = {"/_blob/5b564d54d98e02ad600c913a9a1888f2": "badge.jpg", "/_blob/5483451f292782a8b366e437181c4bcf": "captain-bob.jpg"} @@ -44,11 +52,15 @@ CONSOLE = '''
If the console does not connect, every machine is in use: try again in a few minutes.

''' -def body(src): +def body(src, pages): 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: + # a page not built this time is not linked to: its taken out (the nav, the footers) + for p in RELEASE_PAGES: + if p not in pages: + html = re.sub(r']*>[^<]*\n?' % p, "", html) + 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") @@ -90,14 +102,43 @@ def forms(html, page): return html +def release(html, tag, sums): + """[TAG], [RELEASE]/ and [SHA:] from the release; a file it has not is an error.""" + html = html.replace("[TAG]", tag) + html = html.replace("[RELEASE]/", RELEASES + tag + "/") + + def sha(m): + if m.group(1) not in sums: + sys.exit("build: %s is not in the release's SHA256SUMS" % m.group(1)) + return sums[m.group(1)] + return re.sub(r"\[SHA:([^\]]+)\]", sha, html) + + if __name__ == "__main__": + import argparse + ap = argparse.ArgumentParser() + ap.add_argument("--release", help="the release's tag, e.g. v4.0.0-alpha.4") + ap.add_argument("--sums", help="the release's SHA256SUMS") + a = ap.parse_args() + if bool(a.release) != bool(a.sums): + sys.exit("build: --release and --sums go together") + sums = {} + if a.sums: + for l in open(a.sums).read().split("\n"): + f = l.split() + if len(f) == 2: + sums[f[1].lstrip("*").lstrip("./")] = f[0] os.chdir(os.path.dirname(os.path.abspath(__file__))) - for p in PAGES: - html = forms(body(open("canvas/%s.dc.html" % p).read()), p) + pages = PAGES + (RELEASE_PAGES if a.release else []) + for p in pages: + html = forms(body(open("canvas/%s.dc.html" % p).read(), pages), p) + if p in RELEASE_PAGES: + html = release(html, a.release, sums) if p == "TryIt": html = try_it(html) html = colorlock.lock(html, '
+ + + +StarshipOS — Documents + + + + + + + + + +
+ +
+ +
+ +
+

ok> documents

+

How it was built, and what it says.

+

Every release builds its documents from the same tree as its images: the design record, the dictionary of every word, the book, the formal volumes. Each is a PDF on the release, with its checksum.

+ +
+ +
+

Start here

+

Two books, kept with every release.

+
+ +
+

The Book of Fabric

the design record, as it happened

+

Every ruling, the reasoning behind it, what was built and what was abandoned, written as the work went -- reproduced as written, corrections and all. The living record first, then the closed ones in order. Fabric is about steady state: this is how it was reached.

+
SHA-256 [SHA:fabric.pdf]
+fabric.pdf +
+ +
+

The Dictionary of v4

every word, alphabetical

+

Every word StarForth v4 boots with: its stack notation, what it does, and where it is defined. The names come from the booted system itself, and the build refuses a dictionary that has fallen behind it.

+
DUP      ( x -- x x )       Copy the top value.
+MINT-ZUSE ( -- )            Mint the real Zuse onto a second stick.
+
SHA-256 [SHA:dictionary.pdf]
+dictionary.pdf +
+ +
+
+ +
+

And the rest

+

The book, the formal set, the API.

+ +
+ +
+
+
+

The paper

+

The ideas behind it, on SSRN.

+

The whitepaper the design starts from.

+
+Read the paper → +
+
+ + + +
+
+ + + diff --git a/site/com/canvas/Downloads.dc.html b/site/com/canvas/Downloads.dc.html new file mode 100644 index 00000000..f87c1b63 --- /dev/null +++ b/site/com/canvas/Downloads.dc.html @@ -0,0 +1,145 @@ + + + + +StarshipOS — Downloads + + + + + + + + + +
+ +
+ +
+ +
+

ok> downloads

+

StarshipOS, on a machine of yours.

+

One image per board, straight from the release. Write it to a USB stick or an SD card, boot, and the first boot makes you Zuse, the superuser: your identity lives on the stick.

+
+[TAG] +alpha · pre-release +All releases → +
+
+ +
+
+
+

An alpha, honestly.

+

Every release is booted in QEMU on amd64, aarch64 and riscv64, passes its 538 power-on checks on each, and its proofs, before it is published. On real hardware it has not been booted yet. If you try it, tell us what happened.

+
+
+
✓ QEMU, three architectures
+
○ Real hardware: not yet
+Report how it went → +
+
+
+ +
+

The images

+

Pick your board.

+
+ +
+

PC

any x86-64 PC that boots UEFI from USB

+

A USB stick image. Its first boot makes your Zuse on the stick: no account, no password — the stick is your key.

+
Write it (it replaces everything on the stick):
+
xz -dc pc-stick.img.xz | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress
+

or balenaEtcher, which reads the .xz as it is.

+pc-stick.img.xz +
+ +
+

Raspberry Pi 5

an SD card

+

Boots straight from the Pi's own firmware. Until first-boot Zuse reaches this board, it needs a Zuse identity drive made on a PC.

+
Write it with Raspberry Pi Imager (Use custom), or:
+
xz -dc raspi5.img.xz | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress
+raspi5.img.xz +
+ +
+

Milk-V Mars

RISC-V, an SD card

+

Found by the board's own U-Boot. Until first-boot Zuse reaches this board, it needs a Zuse identity drive made on a PC.

+
Write it:
+
xz -dc MilkV.img.xz | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress
+MilkV.img.xz +
+ +
+

QEMU

for developers

+

The ISO every release's amd64 acceptance boots, byte for byte. To boot it the way we do, see the developer guide in the repository.

+The repository → +StarshipOS.iso +
+ +
+

Rather not write anything? Try it in your browser — a real machine, at its prompt in seconds.

+
+ +
+
+
+

Verify

+

Know it's ours.

+

Every file's SHA-256 is in the release's SHA256SUMS. Inside each image, every capsule the system loads is signed by StarshipOS Capsule Signing, issued by the StarshipOS Root CA, and checked at boot.

+
sha256sum -c SHA256SUMS --ignore-missing
+
+
+
StarshipOS Root CA, SHA-256
+DA:A7:40:56:65:41:1A:FE:E6:08:85:3F:6E:AD:83:3A:38:30:83:E6:F0:6D:69:A6:A8:9E:92:BD:85:93:39:81 +SHA256SUMS → +The Root CA's certificate → +
+
+
+ +
+
+
+

Read

+

The documents of this release.

+

The Book of Fabric, the Dictionary of v4, the book and the formal set -- built from the same tree as these images.

+
+Documents → +
+
+ + + +
+
+ + + diff --git a/site/com/canvas/Inquiries.dc.html b/site/com/canvas/Inquiries.dc.html index 5e483cab..9c5e8b84 100644 --- a/site/com/canvas/Inquiries.dc.html +++ b/site/com/canvas/Inquiries.dc.html @@ -23,6 +23,8 @@ a{color:#F5891F}a:hover{color:#FFB061} diff --git a/site/com/canvas/Main.dc.html b/site/com/canvas/Main.dc.html index 63283bb2..19489a63 100644 --- a/site/com/canvas/Main.dc.html +++ b/site/com/canvas/Main.dc.html @@ -25,6 +25,8 @@ a{color:#F5891F}a:hover{color:#FFB061} Research Silicon Try it +Downloads +Documents Inquiries Contributors →
diff --git a/site/com/canvas/TryIt.dc.html b/site/com/canvas/TryIt.dc.html index e6cc2522..3eea8198 100644 --- a/site/com/canvas/TryIt.dc.html +++ b/site/com/canvas/TryIt.dc.html @@ -23,6 +23,8 @@ a{color:#F5891F}a:hover{color:#FFB061} diff --git a/site/com/install.sh b/site/com/install.sh index 7187d028..695aaa1b 100755 --- a/site/com/install.sh +++ b/site/com/install.sh @@ -39,10 +39,17 @@ attach Main badge.jpg attach Main captain-bob.jpg page Inquiries "Inquiries" Inquiries.WebHome.xwiki page TryIt "Try StarshipOS" TryIt.WebHome.xwiki +# Downloads and Documents exist once build.py has been given a release (#126, #157) +pages="/bin/view/Main/ /bin/view/Inquiries/ /bin/view/TryIt/" +if [ -f Downloads.WebHome.xwiki ] && [ -f Documents.WebHome.xwiki ]; then + page Downloads "Downloads" Downloads.WebHome.xwiki + page Documents "Documents" Documents.WebHome.xwiki + pages="$pages /bin/view/Downloads/ /bin/view/Documents/" +fi echo "=== as a guest" bad= -for u in /bin/view/Main/ /bin/view/Inquiries/ /bin/view/TryIt/ \ +for u in $pages \ /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