Release machinery for Downloads, the root CA, demo and site updates (#126) #154

Merged
rajames merged 7 commits from StarForth-v4.0.0 into master 2026-11-08 03:20:43 +00:00
11 changed files with 106 additions and 19 deletions
+1
View File
@@ -6,6 +6,7 @@
#
# org.starship:kernelImage:<LITHOS_VERSION>-SNAPSHOT, starshipos-snapshots
# classifier pc StarshipOS.iso amd64, UEFI -- the ISO the amd64 acceptance boot booted
# classifier pc-stick pc-stick.img.xz a PC's USB stick, for any UEFI PC (make boot_image TARGET=PC)
# classifier raspi5 raspi5.img.xz Raspberry Pi 5, for Raspberry Pi Imager
# classifier milkv MilkV.img.xz Milk-V Mars
# classifier logs logs.tar.gz this run's serial logs and DoE CSVs (the proof log is in
+11 -1
View File
@@ -2,9 +2,14 @@
# acceptance protocol, the proofs, the board images -- and the documents, and publishes both to
# starshipos-releases at that version:
#
# org.starship:kernelImage:<version> classifiers pc, raspi5, milkv, logs (nightly.yml)
# org.starship:kernelImage:<version> classifiers pc, pc-stick, raspi5, milkv, logs (nightly.yml)
# org.starship:documentation:<version> classifiers book, vol1, vol2, vol3, api (nightly-docs.yml)
#
# and makes the tag a Gitea release -- a pre-release for v<x.y.z>-alpha.N and the like -- with the
# images, SHA256SUMS, the documents and the StarshipOS Root CA's certificate attached, for anyone
# to download (#126; scripts/nightly.sh gitea-release). Needs the secret RELEASE_TOKEN: a token
# that may write the repository.
#
# The tag must name the version the tree says it is: v<LITHOS_VERSION> (kernel/Makefile). A tag
# that does not is refused before anything is built. A release repository takes a version once.
name: Release
@@ -84,6 +89,11 @@ jobs:
scripts/nightly.sh publish https://nexus.starshipos-f18.org/repository/starshipos-releases
scripts/nightly.sh publish-docs https://nexus.starshipos-f18.org/repository/starshipos-releases
- name: Gitea release # the downloads anyone can fetch (#126): no Nexus login
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} # a token that may write the repository
run: scripts/nightly.sh gitea-release "${{ github.ref_name }}"
- name: Keep the release
uses: actions/upload-artifact@v3
with:
+4 -1
View File
@@ -3,7 +3,10 @@
# 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.
# not in the repository, not in Gitea, not in any image. Its
# certificate (public) is ci/pki/starshipos-root-ca.pem, made on
# 2026-11-07, SHA-256 in starshipos-root-ca.sha256; every release
# attaches it.
# 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;
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBaDCCARqgAwIBAgIUCwt1JkPxCfwr9PTCOadol/m9PCQwBQYDK2VwMDIxEzAR
BgNVBAoMClN0YXJzaGlwT1MxGzAZBgNVBAMMElN0YXJzaGlwT1MgUm9vdCBDQTAe
Fw0yNjExMDcxNjUwNTZaFw00NjExMDcxNjUwNTZaMDIxEzARBgNVBAoMClN0YXJz
aGlwT1MxGzAZBgNVBAMMElN0YXJzaGlwT1MgUm9vdCBDQTAqMAUGAytlcAMhANcn
fPpowbNgzMPtDEA7EYJoSkBLPljM6dEBFONfrhHso0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU5eU4Iy/Al5g9KZI5+u4jKllG
shswBQYDK2VwA0EAjRK1ybZE/VaH31dt3sc8DgqJVTPB6CYovdqWKcJMXC8UKfYJ
co9o8MhiIauvSCgZZA2+5VMv4zlke7Lu5TNLDg==
-----END CERTIFICATE-----
+1
View File
@@ -0,0 +1 @@
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
+7 -6
View File
@@ -30,13 +30,14 @@ ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ReadOnlyPaths=/var/lib/starshipos-demo
# Sized for this server (3.8 GiB, both wikis, Gitea, Nexus, CI): an idle StarshipOS guest keeps a
# host core busy -- the kernel's idle loop polls (measured 2026-11-08: 100% of a core at the
# prompt, 148 MiB resident) -- so all sessions together get one core at the lowest priority, and
# the wikis and Gitea never starve; with two visitors each runs at half speed. 1 GiB holds two
# sessions with room. Raise both with the server (8 GB: MemoryMax=2G, CPUQuota=200%).
# Sized for this server (3.8 GiB, two cores; both wikis, Gitea, Nexus, CI). Since #151 an idle guest
# halts (1% of a core at the prompt, was 100%), so a session costs CPU only while its visitor runs
# something. All sessions together get 1.5 cores at the lowest priority (Captain Bob, 2026-11-08):
# one busy visitor a full core, two busy at once three-quarters each, and half a core always left
# to the wikis and Gitea. 1 GiB holds two sessions (~150 MB resident each) with room. Raise both
# with the server (8 GB: MemoryMax=2G, CPUQuota=200% or more).
MemoryMax=1G
CPUQuota=100%
CPUQuota=150%
Nice=19
TasksMax=200
+24
View File
@@ -6279,3 +6279,27 @@ protocol passes -- amd64 `logs/20261108-005539`, aarch64 `-005753`, riscv64 `-01
538/538, `dict_hash 0xfec8c47cfc6f437f` on all three, `7 ok` typed and answered; the stick loaded
high; `test-32` (39 programs, 0 failures). The demo's CPU cap (`CPUQuota=100%`) can be relaxed
once this reaches the server.
### 6. #131 closed: QEMU's capture, not our kernel (2026-11-08)
Run 197's riscv64 serial log, read from Gitea's artifact store on the server (`acceptance-logs-185`),
had the line with one character doubled -- "Boot diisk: not a StarshipOS stick" -- and a doubled
byte in OpenSBI's banner too, printed before our kernel runs. So the byte was doubled in QEMU's
serial capture under the server's load, not by the kernel (Captain Bob closed #131). The check that
failed was right to: it refuses a line that is not what was printed. The capture itself (the log is
`logfile=` on the socket chardev the acceptance types through) is what is left to harden.
### 7. Downloads (#126): what a release publishes, and what a download must boot to (2026-11-08)
- **Ruled** (Captain Bob): the Downloads page goes live with alpha.4, filled by the release itself;
the StarshipOS Root CA's certificate is published with it.
- **Built:** the release builds the PC's USB stick too (`scripts/nightly.sh images`, classifier
`pc-stick`; `pc` stays the ISO QEMU boots), and makes the tag a Gitea release -- a pre-release for
an alpha -- with the images, `SHA256SUMS`, the documents and the Root CA's certificate attached
for anyone to download (`scripts/nightly.sh gitea-release`, run by `release.yml` with the secret
`RELEASE_TOKEN`). Tested against a stand-in Gitea API: the release, its notes and ten files; a
second run adds nothing.
- **Found:** a downloaded image boots headless today -- "Zuse: no genesis on this disk -- the system
stays headless"; minting Zuse on a stick's first boot (`docs/v4.0.0/STICK.md` section 6) is #92.
**Ruled:** #92 into alpha.4, so the PC stick downloaded from alpha.4 on boots to
`[zuse@Hera] ok>` at its first boot.
+46 -7
View File
@@ -22,6 +22,7 @@
# logs/<TS>/isabelle/, as a boot's is.
# scripts/nightly.sh images
# The board images, into build/nightly/:
# pc-stick.img.xz a PC's USB stick (make boot_image TARGET=PC): GPT, one ESP, any UEFI PC
# raspi5.img.xz Raspberry Pi 5 (make boot_image TARGET=RASPI), for Raspberry Pi Imager
# MilkV.img.xz Milk-V Mars (make boot_image TARGET=MILKV)
# SHA256SUMS of StarshipOS.iso and both images, before and after compression
@@ -29,7 +30,7 @@
# same sources, for hardware QEMU does not model.
# scripts/nightly.sh publish <URL>
# org.starship:kernelImage:<VERSION> to that Maven repository, in one deploy (one
# timestamp): classifier pc (iso), raspi5 and milkv (img.xz), and logs (tar.gz: this
# timestamp): classifier pc (iso, for QEMU), pc-stick, raspi5 and milkv (img.xz), and logs (tar.gz: this
# run's serial logs and DoE CSVs -- the evidence for the rest; and the proof log, when the
# proofs ran before it, as a release runs them).
#
@@ -54,7 +55,7 @@ LITHOS=$(sed -n 's/^LITHOS_VERSION *?= *//p' kernel/Makefile)
VERSION=${NIGHTLY_VERSION:-$LITHOS-SNAPSHOT}
usage() {
echo "Usage: scripts/nightly.sh accept <amd64|aarch64|riscv64|stick-high> | test-32 | proofs | images" >&2
echo " | publish <URL> | docs | publish-docs <URL>" >&2
echo " | publish <URL> | docs | publish-docs <URL> | gitea-release <tag>" >&2
exit 2
}
need() { for f in "$@"; do [ -f "$f" ] || { echo "nightly: no $f -- run the stages before this one" >&2; exit 1; }; done; }
@@ -138,7 +139,7 @@ proofs)
;;
images)
need "$OUT/StarshipOS.iso"
for b in "RASPI raspi/lithos-raspi.img raspi5.img" "MILKV milkv/lithos-milkv.img MilkV.img"; do
for b in "PC pc/lithos-pc.img pc-stick.img" "RASPI raspi/lithos-raspi.img raspi5.img" "MILKV milkv/lithos-milkv.img MilkV.img"; do
set -- $b
echo "=== nightly: image $1"
make --no-print-directory boot_image TARGET="$1"
@@ -147,18 +148,20 @@ images)
# boot_image regenerates these (the source SBOM, and what qemu_session.py also puts back);
# the nightly does not change the tree
git checkout -q -- sbom.spdx sbom.spdx.json capsules/BLOCK_MAP.md v3/include/version.h
(cd "$OUT" && rm -f raspi5.img.xz MilkV.img.xz && sha256sum StarshipOS.iso raspi5.img MilkV.img > SHA256SUMS)
xz -T0 -6 "$OUT/raspi5.img" "$OUT/MilkV.img"
(cd "$OUT" && sha256sum raspi5.img.xz MilkV.img.xz >> SHA256SUMS && cat SHA256SUMS)
(cd "$OUT" && rm -f pc-stick.img.xz raspi5.img.xz MilkV.img.xz &&
sha256sum StarshipOS.iso pc-stick.img raspi5.img MilkV.img > SHA256SUMS)
xz -T0 -6 "$OUT/pc-stick.img" "$OUT/raspi5.img" "$OUT/MilkV.img"
(cd "$OUT" && sha256sum pc-stick.img.xz raspi5.img.xz MilkV.img.xz >> SHA256SUMS && cat SHA256SUMS)
;;
publish)
url=${2:-}; [ -n "$url" ] || usage
need "$OUT/StarshipOS.iso" "$OUT/raspi5.img.xz" "$OUT/MilkV.img.xz"
need "$OUT/StarshipOS.iso" "$OUT/pc-stick.img.xz" "$OUT/raspi5.img.xz" "$OUT/MilkV.img.xz"
# the evidence: what this run wrote under logs/ and experiments/bare_metal/runs/
find logs experiments/bare_metal/runs -type f -newer "$OUT/.started" -print0 \
| tar --null -czf "$OUT/logs.tar.gz" -T -
echo "=== nightly: logs.tar.gz: $(tar -tzf "$OUT/logs.tar.gz" | wc -l) files"
deploy "$url" kernelImage "$OUT/StarshipOS.iso" iso pc \
"$OUT/pc-stick.img.xz" img.xz pc-stick \
"$OUT/raspi5.img.xz" img.xz raspi5 \
"$OUT/MilkV.img.xz" img.xz milkv \
"$OUT/logs.tar.gz" tar.gz logs
@@ -172,6 +175,42 @@ docs)
cp docs/formal/build/doxygen/StarForth-API-Reference.pdf "$DOCS/api.pdf"
(cd "$DOCS" && sha256sum ./*.pdf | tee SHA256SUMS)
;;
gitea-release)
# The release as Gitea's own (#126): a release for <tag> -- a pre-release when the tag names
# one (v4.0.0-alpha.3) -- its notes the tag's message and the checksums, its files everyone's
# to download from the public repository, no Nexus login: the images, SHA256SUMS, the
# documents, and the StarshipOS Root CA's certificate (FABRIC-3 XLVI). Run again, it adds what
# is missing and changes nothing else. RELEASE_TOKEN: a token that may write the repository.
tag=${2:-}; [ -n "$tag" ] || usage
: "${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
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")
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
id=$(gitea -X POST -H 'Content-Type: application/json' "$api/releases" \
-d "{\"tag_name\": \"$tag\", \"name\": \"$tag\", \"prerelease\": $pre, \"body\": $body}" |
python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
echo "=== nightly: Gitea release $tag made (id $id, pre-release $pre)"
else
echo "=== nightly: Gitea release $tag is there (id $id)"
fi
have=$(gitea "$api/releases/$id/assets" | python3 -c 'import json,sys; print(" ".join(a["name"] for a in json.load(sys.stdin)))')
for f in "${files[@]}"; do
n=$(basename "$f")
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
;;
publish-docs)
url=${2:-}; [ -n "$url" ] || usage
need "$DOCS/book.pdf" "$DOCS/vol1.pdf" "$DOCS/vol2.pdf" "$DOCS/vol3.pdf" "$DOCS/api.pdf"
+1 -1
View File
@@ -104,7 +104,7 @@ html, body, #xwikimaincontainer, #xwikimaincontainerinner, #contentcontainer, #c
<p style="margin: 0; color: #C8D1DE !important">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.</p>
</div>
<div style="flex: 1 1 220px; display: flex; flex-direction: column; gap: 12px">
<a href="https://starshipos-f18.org/" style="display: inline-block; padding: 14px 20px; background: #F5891F !important; color: #0A1222 !important; font-weight: 700; text-decoration: none; border-radius: 4px; text-align: center">Read the paper</a>
<a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5930134" style="display: inline-block; padding: 14px 20px; background: #F5891F !important; color: #0A1222 !important; font-weight: 700; text-decoration: none; border-radius: 4px; text-align: center">Read the paper</a>
<a href="https://starshipos-f18.org/" style="display: inline-block; padding: 14px 20px; border: 1px solid #34486A !important; color: #EEF2F7 !important; text-decoration: none; border-radius: 4px; text-align: center">Design docs on the .org</a>
</div>
</div>
-2
View File
@@ -13,7 +13,6 @@ 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;
- 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.
"""
@@ -52,7 +51,6 @@ def body(src):
html = html.replace('href="https://starshipos.org"', 'href="%s"' % ORG)
html = html.replace(">starshipos.org</a>", ">starshipos-f18.org</a>")
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
+1 -1
View File
@@ -94,7 +94,7 @@ a{color:#F5891F}a:hover{color:#FFB061}
<p style="margin: 0; color: #C8D1DE">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.</p>
</div>
<div style="flex: 1 1 220px; display: flex; flex-direction: column; gap: 12px">
<a href="[SSRN PAPER URL]" style="display: inline-block; padding: 14px 20px; background: {{accent}}; color: #0A1222; font-weight: 700; text-decoration: none; border-radius: 4px; text-align: center">Read the paper</a>
<a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5930134" style="display: inline-block; padding: 14px 20px; background: {{accent}}; color: #0A1222; font-weight: 700; text-decoration: none; border-radius: 4px; text-align: center">Read the paper</a>
<a href="https://starshipos.org" style="display: inline-block; padding: 14px 20px; border: 1px solid #34486A; color: #EEF2F7; text-decoration: none; border-radius: 4px; text-align: center">Design docs on the .org</a>
</div>
</div>