#!/bin/bash # nightly.sh -- the nightly and release builds: the acceptance protocol, the proofs, the board # images, the documents, and Nexus. One stage per call; the workflows run each as a step # (.gitea/workflows/nightly.yml, release.yml, nightly-docs.yml). # # The images, in this order (any failure ends the run; nothing is published after it) -- except # that the nightly runs the proofs last, whatever happened before them (nightly.yml says why): # # scripts/nightly.sh accept # One boot of the acceptance protocol (.claude/CLAUDE.md), in the foreground, one QEMU at a # time, in that order: scripts/qemu_session.py zuse -- [zuse@Hera] ok>, `3 4 + .` # -> 7 ok, POST passed -- or stick_zuse amd64 --target qemu-stick-high, the PC stick loaded # above 2 GiB. amd64 keeps the ISO it booted, byte for byte, as build/nightly/StarshipOS.iso. # The last of the four (stick-high) also checks PARITY:V4_SYSTEM's dict_hash is the same # on amd64, aarch64 and riscv64. # scripts/nightly.sh test-32 # 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. # 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 # MilkV.img.xz Milk-V Mars (make boot_image TARGET=MILKV) # SHA256SUMS of StarshipOS.iso and both images, before and after compression # QEMU boots the aarch64 and riscv64 kernels, not these images: they are built from the # same sources, for hardware QEMU does not model. # scripts/nightly.sh publish # org.starship:kernelImage: to that Maven repository, in one deploy (one # timestamp): classifier pc (iso), 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). # # The documents (a separate pipeline): # # scripts/nightly.sh docs # The book (make docs), the formal volumes (make -C docs/formal vols) and the API reference # (make -C docs/formal doxygen), into build/nightly-docs/. # scripts/nightly.sh publish-docs # org.starship:documentation:: classifier book, vol1, vol2, vol3, api (pdf). # # VERSION is -SNAPSHOT, or NIGHTLY_VERSION when set (a release sets it from its # tag). NEXUS_USER (default ci-publisher) and NEXUS_PASSWORD; publishing needs mvn and a JRE. # Needs what make ARCH=amd64,aarch64,riscv64 init installs (INIT_DOCS=0 for the images, # INIT_PROOFS=0 for the documents). set -euo pipefail cd "$(dirname "$0")/.." OUT=build/nightly DOCS=build/nightly-docs LITHOS=$(sed -n 's/^LITHOS_VERSION *?= *//p' kernel/Makefile) VERSION=${NIGHTLY_VERSION:-$LITHOS-SNAPSHOT} usage() { echo "Usage: scripts/nightly.sh accept | test-32 | proofs | images" >&2 echo " | publish | docs | publish-docs " >&2 exit 2 } need() { for f in "$@"; do [ -f "$f" ] || { echo "nightly: no $f -- run the stages before this one" >&2; exit 1; }; done; } # deploy
[file ext classifier]... deploy() { local url=$1 artifact=$2 file=$3 ext=$4 cls=$5 files= exts= clss= settings shift 5 while [ $# -gt 0 ]; do files+=${files:+,}$1; exts+=${exts:+,}$2; clss+=${clss:+,}$3; shift 3; done : "${NEXUS_PASSWORD:?publishing needs NEXUS_PASSWORD}" command -v mvn >/dev/null || { echo "nightly: publishing needs mvn (apt-get install maven)" >&2; exit 1; } settings=$(mktemp) cat > "$settings" <<'EOF' starship-nexus ${env.NEXUS_USER} ${env.NEXUS_PASSWORD} EOF export NEXUS_USER=${NEXUS_USER:-ci-publisher} echo "=== nightly: publish org.starship:$artifact:$VERSION -> $url" mvn -B -q -s "$settings" org.apache.maven.plugins:maven-deploy-plugin:3.1.2:deploy-file \ -Durl="$url" -DrepositoryId=starship-nexus \ -DgroupId=org.starship -DartifactId="$artifact" -Dversion="$VERSION" \ -Dfile="$file" -Dpackaging="$ext" -Dclassifier="$cls" \ ${files:+-Dfiles="$files" -Dtypes="$exts" -Dclassifiers="$clss"} -DgeneratePom=true \ || { rm -f "$settings"; exit 1; } rm -f "$settings" echo "=== nightly: published" } case ${1:-} in accept) what=${2:-} case $what in amd64) rm -rf "$OUT"; mkdir -p "$OUT"; touch "$OUT/.started" # the first stage mode=zuse; arch=amd64; extra= ;; aarch64|riscv64) mode=zuse; arch=$what; extra= ;; stick-high) mode=stick_zuse; arch=amd64; extra="--target qemu-stick-high" ;; *) usage ;; esac need "$OUT/.started" echo "=== nightly: org.starship:kernelImage:$VERSION from $(git rev-parse --short HEAD); accept $what" git checkout -q -- disk/zuse-scratch # the committed Zuse pair, before each architecture python3 scripts/qemu_session.py $mode $arch $extra --label "nightly-$what" --make "QEMU_DISPLAY=none" \ | tee "$OUT/accept-$what.txt" [ "$what" != amd64 ] || cp build/amd64/kernel/starkernel.iso "$OUT/StarshipOS.iso" if [ "$what" = stick-high ]; then # the three architectures' boots of this run: one dict_hash hashes=$(for a in amd64 aarch64 riscv64; do log=$(sed -n 's/^LOG: //p' "$OUT/accept-$a.txt") grep -aoE "PARITY:V4_SYSTEM [^[:cntrl:]]*" "$log" | sed -n 's/.*dict_hash=//p' done | sort -u) echo "=== nightly: dict_hash on the three: $hashes" [ "$(echo "$hashes" | wc -l)" = 1 ] && [ -n "$hashes" ] \ || { echo "nightly: the three architectures do not agree on dict_hash" >&2; exit 1; } fi ;; test-32) need "$OUT/.started" make --no-print-directory -C v4 test-32 2>&1 | tee "$OUT/test-32.txt" echo "=== nightly: test-32: $(grep -cE '[0-9]+ checks, 0 failures' "$OUT/test-32.txt") programs, 0 failures" ;; proofs) need "$OUT/.started" ts=$(date -u +%Y%m%d-%H%M%S) mkdir -p "logs/$ts/isabelle" isabelle build -c -v -D proof/ 2>&1 | tee "logs/$ts/isabelle/isabelle-$ts.log" ;; images) need "$OUT/StarshipOS.iso" for b in "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" cp "build/boards/$2" "$OUT/$3" done # 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) ;; publish) url=${2:-}; [ -n "$url" ] || usage need "$OUT/StarshipOS.iso" "$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/raspi5.img.xz" img.xz raspi5 \ "$OUT/MilkV.img.xz" img.xz milkv \ "$OUT/logs.tar.gz" tar.gz logs ;; docs) rm -rf "$DOCS"; mkdir -p "$DOCS" make --no-print-directory docs make --no-print-directory -C docs/formal vols doxygen cp build/docs/LithosAnanke.pdf "$DOCS/book.pdf" for v in 1 2 3; do cp "docs/formal/build/vol$v/main.pdf" "$DOCS/vol$v.pdf"; done cp docs/formal/build/doxygen/StarForth-API-Reference.pdf "$DOCS/api.pdf" (cd "$DOCS" && sha256sum ./*.pdf | tee SHA256SUMS) ;; publish-docs) url=${2:-}; [ -n "$url" ] || usage need "$DOCS/book.pdf" "$DOCS/vol1.pdf" "$DOCS/vol2.pdf" "$DOCS/vol3.pdf" "$DOCS/api.pdf" deploy "$url" documentation "$DOCS/book.pdf" pdf book \ "$DOCS/vol1.pdf" pdf vol1 "$DOCS/vol2.pdf" pdf vol2 "$DOCS/vol3.pdf" pdf vol3 \ "$DOCS/api.pdf" pdf api ;; *) usage ;; esac