Repository navigation
Distro #455
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Distro | |
| # The whole distribution built and tested on GitHub's runners, in jobs that hand work on as artifacts, all under /mnt/kryptik. | |
| on: | |
| push: | |
| branches: [main] | |
| # A tag builds that release, tests it whole and drafts its Releases page. | |
| tags: ['v*'] | |
| paths-ignore: | |
| - 'docs/**' | |
| - '*.md' | |
| - '.github/workflows/ci.yml' | |
| # Weekly, main from nothing as a tag builds it: a step that only an empty cache breaks shows within a week. | |
| schedule: | |
| - cron: '23 4 * * 0' | |
| workflow_dispatch: | |
| inputs: | |
| role: | |
| description: "Build and test the release as a development build, or as a production one signed with a throwaway key medium: the release flow, rehearsed" | |
| type: choice | |
| options: [development, production] | |
| default: development | |
| cold: | |
| description: "Build every stage from its sources, as a tag does, restoring and saving no stage cache" | |
| type: boolean | |
| default: false | |
| # Set here, not in a repository setting; the cache and the artifacts use the runner's own credential. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| # A cold run has a group of its own, so a push to main does not cancel it. | |
| group: distro-${{ github.ref }}${{ (github.event_name == 'schedule' || inputs.cold == true) && '-cold' || '' }} | |
| cancel-in-progress: true | |
| env: | |
| # A scheduled or cold run restores and saves no stage cache: it builds what a tag would. | |
| KRYPTIK_COLD: ${{ github.event_name == 'schedule' || inputs.cold == true }} | |
| KRYPTIK_WORK: /mnt/kryptik/work | |
| KRYPTIK_SOURCES: /mnt/kryptik/sources | |
| KRYPTIK_OUT: /mnt/kryptik/out | |
| # A step whose inputs moved rebuilds instead of stopping the build. | |
| KRYPTIK_STALE: rebuild | |
| NO_COLOR: "1" | |
| HOST_PACKAGES: >- | |
| build-essential bison flex texinfo gawk m4 patch perl python3 python3-pip | |
| xz-utils bzip2 zstd cpio rsync bc curl git openssl libssl-dev libelf-dev | |
| e2fsprogs dosfstools mtools exfatprogs xorriso util-linux cryptsetup-bin sbsigntool libasound2-dev | |
| qemu-system-x86 ovmf musl-tools file | |
| # Caches are keyed on the stamps' digest and saved after a failure too; the stage 05 tree's key starts with the toolchain's. | |
| jobs: | |
| toolchain: | |
| name: Stages 01-02 (cross toolchain, temporary tools) | |
| # actions: read, for the tag preflight to read CI's verdict on the commit. | |
| permissions: | |
| contents: read | |
| actions: read | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 360 | |
| outputs: | |
| ttfp: ${{ steps.progress02.outputs.ttfp }} | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| # A release is MAJOR.MINOR.PATCH, and 0.0.0 is the build the update suite starts from. | |
| - name: The tag names the version | |
| if: github.ref_type == 'tag' | |
| run: | | |
| v="${GITHUB_REF_NAME#v}" | |
| if [[ "$GITHUB_REF_NAME" != v* || ! "$v" =~ ^(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){2}$ || "$v" == 0.0.0 ]]; then | |
| echo "tag ${GITHUB_REF_NAME} does not name a release: tags are v<MAJOR>.<MINOR>.<PATCH>, digits without leading zeros, above 0.0.0" | |
| exit 1 | |
| fi | |
| echo "release ${v}" | |
| # A release takes no held pin. | |
| - name: The release's pins are reviewed, and none is held | |
| if: github.ref_type == 'tag' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| ./tools/check-source-currency.sh --tsv > pin-survey.tsv | |
| ./tools/check-pin-reviews.sh --survey pin-survey.tsv --no-held | |
| - uses: ./.github/actions/prepare | |
| with: | |
| sources: fetch | |
| image-sources: ${{ github.ref_type == 'tag' && 'fetch' || '' }} | |
| # CI's gates once more, so a tag never outruns them. | |
| - name: The release's sources are signed, and their provenance holds | |
| if: github.ref_type == 'tag' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| ./tools/verify-signatures.sh --strict | |
| ./tools/verify-provenance.sh --strict | |
| - name: CI passed on the tagged commit | |
| if: github.ref_type == 'tag' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| sha="$(git rev-parse 'HEAD^{commit}')" | |
| status=none; conclusion=- | |
| for try in $(seq 1 40); do | |
| read -r status conclusion < <(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/ci.yml/runs?head_sha=${sha}&per_page=1" \ | |
| --jq '.workflow_runs[0] | "\(.status // "none") \(.conclusion // "-")"') | |
| case "$status" in | |
| completed) break ;; | |
| none) echo "no CI run for ${sha}: a release is cut from a commit CI has tested"; exit 1 ;; | |
| *) echo "CI is ${status} on ${sha}; waiting (${try}/40)"; sleep 60 ;; | |
| esac | |
| done | |
| [ "$status" = completed ] || { echo "CI did not finish on ${sha} in time"; exit 1; } | |
| [ "$conclusion" = success ] || { echo "CI concluded ${conclusion} on ${sha}: a release is cut only from a commit CI passed"; exit 1; } | |
| echo "CI passed on ${sha}" | |
| # A tag or a cold run restores nothing: a resumed tree keeps files a step no longer installs. | |
| - name: Stage 01-02 sysroot (cached) | |
| id: work02 | |
| if: github.ref_type != 'tag' && env.KRYPTIK_COLD != 'true' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: | | |
| /mnt/kryptik/work/sysroot | |
| /mnt/kryptik/work/.stamps | |
| key: work02- | |
| restore-keys: | | |
| work02- | |
| - name: Stage 01, the cross toolchain (unprivileged) | |
| timeout-minutes: 200 | |
| # The root's sources alone: make's own sources would fetch every image's from upstream too, | |
| # which no stage here reads and whose cache only the images' jobs restore. | |
| run: | | |
| ./tools/fetch-sources.sh --root | |
| make -o sources toolchain | |
| - name: Stage 02, the temporary tools (unprivileged) | |
| id: stage02 | |
| timeout-minutes: 120 | |
| # -o: the step before ran stage 01, which temp-tools would walk again. | |
| run: make -o toolchain temp-tools | |
| # A step past its time limit keeps building, so everything stops before the cache reads the tree. | |
| - name: Stop what the stages left running | |
| if: ${{ !cancelled() }} | |
| run: | | |
| pkill -KILL -f 'build/stages/0[12]-' || true | |
| pkill -KILL -x make || true | |
| - name: Progress of stages 01-02 | |
| id: progress02 | |
| if: ${{ !cancelled() }} | |
| env: | |
| STAGE02: ${{ steps.stage02.outcome }} | |
| # Progress is the stamps' fingerprint lines, not their times. | |
| run: | | |
| d="$( { cd "$KRYPTIK_WORK/.stamps" && find . -path ./legacy -prune -o -type f ! -name '*.tmp.*' -print0 | LC_ALL=C sort -z | xargs -0r grep -H '^fingerprint: ' | sha256sum | cut -c1-16; } 2>/dev/null || true)" | |
| if [ -n "$d" ]; then echo "prefix=work02-$d-" >> "$GITHUB_OUTPUT"; fi | |
| ttfp="$(awk '$1 == "fingerprint:" { print $2; exit }' "$KRYPTIK_WORK/.stamps/tt-gcc2" 2>/dev/null || true)" | |
| # Without it every toolchain's stage 05 tree would share one key. | |
| if [ "$STAGE02" = success ] && [ -z "$ttfp" ]; then echo "stage 02 passed and tt-gcc2 has no fingerprint"; exit 1; fi | |
| echo "ttfp=$ttfp" >> "$GITHUB_OUTPUT" | |
| # Saved from main alone, which every branch's run restores: a branch's own copy would be read | |
| # before main's, so main's went unread until the cache's size limit dropped it. | |
| - name: Save the stage 01-02 sysroot | |
| if: ${{ !cancelled() && github.ref == 'refs/heads/main' && env.KRYPTIK_COLD != 'true' && steps.progress02.outputs.prefix != '' && !startsWith(steps.work02.outputs.cache-matched-key, steps.progress02.outputs.prefix) }} | |
| continue-on-error: true | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: | | |
| /mnt/kryptik/work/sysroot | |
| /mnt/kryptik/work/.stamps | |
| key: ${{ steps.progress02.outputs.prefix }}${{ github.run_id }}-${{ github.run_attempt }} | |
| - name: Pack the sysroot for the next job | |
| run: | | |
| cd /mnt/kryptik | |
| rm -rf work/build | |
| members=(sysroot .stamps) | |
| [ -d work/logs ] && members+=(logs) | |
| tar -I 'zstd -T0' -cf work-after-02.tar.zst -C work "${members[@]}" | |
| ls -la work-after-02.tar.zst | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: work-after-02 | |
| path: /mnt/kryptik/work-after-02.tar.zst | |
| retention-days: 3 | |
| compression-level: 0 | |
| - name: Stage logs | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: logs-toolchain | |
| path: /mnt/kryptik/work/logs | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| base: | |
| name: Stages 04-05 (base system, kernel) | |
| needs: toolchain | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 360 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| - uses: ./.github/actions/prepare | |
| with: | |
| sources: fetch | |
| # One root-made tarball, keyed on the toolchain, the pins and stage 03; a change to any restarts stage 04 (about 45 minutes). | |
| - name: Stage 04-05 sysroot and kernel (cached) | |
| id: work05 | |
| if: github.ref_type != 'tag' && env.KRYPTIK_COLD != 'true' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: /mnt/kryptik/work-after-05.tar.zst | |
| key: work05-${{ needs.toolchain.outputs.ttfp }}-${{ hashFiles('build/config/versions.env', 'sources.lock', 'build/stages/03-chroot-prep.sh') }}- | |
| restore-keys: | | |
| work05-${{ needs.toolchain.outputs.ttfp }}-${{ hashFiles('build/config/versions.env', 'sources.lock', 'build/stages/03-chroot-prep.sh') }}- | |
| - name: Unpack the stage 04-05 tree | |
| if: steps.work05.outputs.cache-matched-key != '' | |
| run: | | |
| cd /mnt/kryptik | |
| echo "restored ${{ steps.work05.outputs.cache-matched-key }}" | |
| sudo tar --zstd -xf work-after-05.tar.zst -C work | |
| rm -f work-after-05.tar.zst | |
| ls work; sudo du -sh work/sysroot | |
| - if: steps.work05.outputs.cache-matched-key == '' | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: work-after-02 | |
| path: /mnt/kryptik | |
| - name: Unpack the stage 02 sysroot | |
| if: steps.work05.outputs.cache-matched-key == '' | |
| run: | | |
| cd /mnt/kryptik | |
| tar --zstd -xf work-after-02.tar.zst -C work | |
| rm -f work-after-02.tar.zst | |
| ls work; du -sh work/sysroot | |
| # Built outside the chroot, for musl, by the compiler build/config/rust.lock pins by hash. | |
| - name: The pinned Rust compiler | |
| run: | | |
| d=/mnt/kryptik/rust-dist; mkdir -p "$d" | |
| grep -v '^#' build/config/rust.lock > "$d/SHA256SUMS" | |
| while read -r _ f; do curl -fsSL --retry 3 -o "$d/$f" "https://static.rust-lang.org/dist/$f"; done < "$d/SHA256SUMS" | |
| (cd "$d" && sha256sum --check --strict SHA256SUMS) | |
| for t in "$d"/*.tar.xz; do tar -xf "$t" -C "$d"; done | |
| for i in "$d"/*/install.sh; do "$i" --prefix=/mnt/kryptik/rust --disable-ldconfig > /dev/null; done | |
| echo /mnt/kryptik/rust/bin >> "$GITHUB_PATH" | |
| - name: kryptikd, kryptik-wlproxy and kryptik-stick, static | |
| run: | | |
| want="$(sed -n 's/.* rustc-\([0-9.]*\)-x86_64-unknown-linux-gnu\.tar\.xz$/\1/p' build/config/rust.lock)" | |
| rustc --version | grep -qF "rustc ${want} " || { echo "refusing to build what ships with $(rustc --version); the pin is ${want}"; exit 1; } | |
| # The checkout's, cargo's and the target directory's paths would land in panic | |
| # locations, which then differ between two builds of one tree. | |
| home="${CARGO_HOME:-$HOME/.cargo}" | |
| export RUSTFLAGS="--remap-path-prefix=${GITHUB_WORKSPACE}=/kryptik --remap-path-prefix=${home}=/cargo --remap-path-prefix=${CARGO_TARGET_DIR}=/target" | |
| (cd compartments/kryptikd && cargo build --locked --release --target x86_64-unknown-linux-musl) | |
| (cd compositor && cargo build --locked --release --target x86_64-unknown-linux-musl -p wlproxy --bin kryptik-wlproxy) | |
| (cd tools/stick && cargo build --locked --release --target x86_64-unknown-linux-musl) | |
| cp /mnt/kryptik/cargo/x86_64-unknown-linux-musl/release/kryptikd /mnt/kryptik/kryptikd-musl | |
| cp /mnt/kryptik/cargo/x86_64-unknown-linux-musl/release/kryptik-wlproxy /mnt/kryptik/kryptik-wlproxy-musl | |
| cp /mnt/kryptik/cargo/x86_64-unknown-linux-musl/release/kryptik-stick /mnt/kryptik/kryptik-stick-musl | |
| for b in /mnt/kryptik/kryptikd-musl /mnt/kryptik/kryptik-wlproxy-musl /mnt/kryptik/kryptik-stick-musl; do | |
| ! grep -aqF -e "$GITHUB_WORKSPACE" -e "$home" -e "$CARGO_TARGET_DIR" "$b" || { echo "$b names a path of this build"; exit 1; } | |
| done | |
| file /mnt/kryptik/kryptikd-musl /mnt/kryptik/kryptik-wlproxy-musl /mnt/kryptik/kryptik-stick-musl | |
| # The licence texts build/licences ships for what they link. | |
| tools/check-rust-licences.sh /mnt/kryptik/rust-dist | |
| - name: Stage 04, the base system (in the chroot, as root) | |
| id: stage04 | |
| timeout-minutes: 180 | |
| run: make system KRYPTIK_KRYPTIKD_BIN=/mnt/kryptik/kryptikd-musl KRYPTIK_WLPROXY_BIN=/mnt/kryptik/kryptik-wlproxy-musl KRYPTIK_STICK_BIN=/mnt/kryptik/kryptik-stick-musl | |
| - name: Stage 05, the hardened kernel | |
| timeout-minutes: 90 | |
| # -o: the step before ran stage 04, which kernel would walk again. | |
| run: make -o system kernel KRYPTIK_KRYPTIKD_BIN=/mnt/kryptik/kryptikd-musl KRYPTIK_WLPROXY_BIN=/mnt/kryptik/kryptik-wlproxy-musl | |
| # Nothing is read for the cache while anything still runs or is mounted in the sysroot. | |
| - name: Stop what the stages left running | |
| id: quiesce | |
| if: ${{ !cancelled() && steps.stage04.outcome != '' && steps.stage04.outcome != 'skipped' }} | |
| run: | | |
| sudo pkill -KILL -f 'build/stages/0[345]-' || true | |
| # Pass after pass: a process not yet killed can start another. | |
| inside() { | |
| for p in /proc/[0-9]*; do | |
| case "$(sudo readlink "$p/root" 2>/dev/null)" in "$KRYPTIK_WORK/sysroot"|"$KRYPTIK_WORK/sysroot"/*) echo "${p#/proc/}" ;; esac | |
| done | |
| } | |
| for _ in 1 2 3 4 5 6 7 8 9 10; do | |
| mapfile -t left < <(inside) | |
| [ "${#left[@]}" -eq 0 ] && break | |
| sudo kill -KILL "${left[@]}" 2>/dev/null || true | |
| sleep 1 | |
| done | |
| mapfile -t left < <(inside) | |
| if [ "${#left[@]}" -gt 0 ]; then echo "still running in the sysroot: ${left[*]}"; exit 1; fi | |
| make chroot-umount || true | |
| if findmnt -rn -o TARGET | grep -F "$KRYPTIK_WORK/sysroot/"; then echo "still mounted in the sysroot"; exit 1; fi | |
| # Before stage 06, so no key or image reaches the cache; the kernel tree goes with its stamps. | |
| - name: Progress of stages 04-05 | |
| id: progress05 | |
| if: ${{ !cancelled() && steps.quiesce.outcome == 'success' }} | |
| run: | | |
| d="$( { cd "$KRYPTIK_WORK/.stamps" && find . -path ./legacy -prune -o -type f ! -name '*.tmp.*' -print0 | LC_ALL=C sort -z | xargs -0r grep -H '^fingerprint: ' | sha256sum | cut -c1-16; } 2>/dev/null || true)" | |
| if [ -n "$d" ]; then echo "prefix=work05-${{ needs.toolchain.outputs.ttfp }}-${{ hashFiles('build/config/versions.env', 'sources.lock', 'build/stages/03-chroot-prep.sh') }}-$d-" >> "$GITHUB_OUTPUT"; fi | |
| # Never the module signing key: a pull request's run can restore this cache. | |
| - name: Pack the stage 04-05 tree for the cache | |
| id: pack05 | |
| if: ${{ !cancelled() && github.ref_type != 'tag' && env.KRYPTIK_COLD != 'true' && steps.progress05.outputs.prefix != '' && !startsWith(steps.work05.outputs.cache-matched-key, steps.progress05.outputs.prefix) }} | |
| run: | | |
| cd /mnt/kryptik | |
| members=(.stamps sysroot) | |
| kernel_tree="build/linux-$(sed -n 's/^V_LINUX=//p' "$GITHUB_WORKSPACE/build/config/versions.env")" | |
| [ -d "work/$kernel_tree" ] && members+=("$kernel_tree") | |
| sudo tar -I 'zstd -T0' -cf work-after-05.tar.zst -C work --anchored \ | |
| --exclude='sysroot/proc/*' --exclude='sysroot/sys/*' --exclude='sysroot/dev/*' \ | |
| --exclude='sysroot/run/*' --exclude='sysroot/kryptik*/*' \ | |
| --exclude="$kernel_tree/certs/signing_key.pem" "${members[@]}" | |
| if sudo tar -I zstd -tf work-after-05.tar.zst | grep -E 'certs/signing_key\.pem$|keys/release/'; then echo "a private key is in the cache tarball"; exit 1; fi | |
| sudo chown "$(id -u):$(id -g)" work-after-05.tar.zst | |
| ls -la work-after-05.tar.zst; df -h /mnt | tail -1 | |
| # A cache that cannot be saved costs the next run time, not this one its verdict. | |
| - name: Save the stage 04-05 tree | |
| if: ${{ !cancelled() && steps.pack05.outcome == 'success' }} | |
| continue-on-error: true | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: /mnt/kryptik/work-after-05.tar.zst | |
| key: ${{ steps.progress05.outputs.prefix }}${{ github.run_id }}-${{ github.run_attempt }} | |
| - name: Drop the cache tarball | |
| if: always() | |
| run: rm -f /mnt/kryptik/work-after-05.tar.zst | |
| # The next jobs' tree, as the cache keeps it, with the logs. Never the module signing key, as anyone | |
| # who can read the repository can read an artifact: the stage 06 job's stage 05 makes a new one. | |
| - name: Pack the stage 04-05 tree for the next jobs | |
| run: | | |
| cd /mnt/kryptik | |
| members=(.stamps sysroot logs) | |
| kernel_tree="build/linux-$(sed -n 's/^V_LINUX=//p' "$GITHUB_WORKSPACE/build/config/versions.env")" | |
| [ -d "work/$kernel_tree" ] && members+=("$kernel_tree") | |
| sudo tar -I 'zstd -T0' -cf work-after-05.tar.zst -C work --anchored \ | |
| --exclude='sysroot/proc/*' --exclude='sysroot/sys/*' --exclude='sysroot/dev/*' \ | |
| --exclude='sysroot/run/*' --exclude='sysroot/kryptik*/*' \ | |
| --exclude="$kernel_tree/certs/signing_key.pem" "${members[@]}" | |
| if sudo tar -I zstd -tf work-after-05.tar.zst | grep -E 'certs/signing_key\.pem$|keys/release/'; then echo "a private key is in the tarball"; exit 1; fi | |
| sudo chown "$(id -u):$(id -g)" work-after-05.tar.zst | |
| ls -la work-after-05.tar.zst | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: work-after-05 | |
| path: /mnt/kryptik/work-after-05.tar.zst | |
| # A tag's stage 06 job may be re-run after its owner's approval, so it keeps a week. | |
| retention-days: ${{ github.ref_type == 'tag' && 7 || 1 }} | |
| compression-level: 0 | |
| - name: Stage logs | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: logs-base | |
| path: /mnt/kryptik/work/logs | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # The software images in a job of their own: Firefox alone takes hours, which beside a stage 04 built | |
| # from nothing (a tag's, or a new pin's) and stage 06 would not fit one job's six. | |
| images: | |
| name: Software images (the toolkit, the browser) | |
| needs: [toolchain, base] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 360 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| - uses: ./.github/actions/prepare | |
| with: | |
| sources: fetch | |
| image-sources: fetch | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: work-after-05 | |
| path: /mnt/kryptik | |
| - name: Unpack the stage 04-05 tree | |
| run: | | |
| cd /mnt/kryptik | |
| sudo tar --zstd -xf work-after-05.tar.zst -C work | |
| rm -f work-after-05.tar.zst | |
| ls work; sudo du -sh work/sysroot | |
| # The images built over this base system, up to the last step they build against (IMAGES_BASE in | |
| # 04-images.sh), from an earlier run: make images keeps each whose fingerprint still matches its | |
| # inputs and builds the rest. Not for a tag or a cold run, which build from nothing. | |
| - name: Which base system the images are built over | |
| id: images_key | |
| if: github.ref_type != 'tag' && env.KRYPTIK_COLD != 'true' | |
| run: | | |
| step="$(sed -n 's/^IMAGES_BASE="\([a-z0-9-]*\)"$/\1/p' build/stages/04-images.sh)" | |
| base="$(sudo sed -n 's/^fingerprint: //p' "$KRYPTIK_WORK/.stamps/bs-${step:-none}" | cut -c1-16)" | |
| if [ -n "$base" ]; then echo "prefix=images-v1-${{ needs.toolchain.outputs.ttfp }}-${base}-" >> "$GITHUB_OUTPUT"; fi | |
| - name: Software images from an earlier run (cached) | |
| id: images_cache | |
| if: steps.images_key.outputs.prefix != '' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: /mnt/kryptik/images-cache.tar.zst | |
| key: ${{ steps.images_key.outputs.prefix }} | |
| restore-keys: | | |
| ${{ steps.images_key.outputs.prefix }} | |
| - name: Unpack the cached images | |
| if: steps.images_cache.outputs.cache-matched-key != '' | |
| run: | | |
| cd /mnt/kryptik | |
| sudo tar --zstd -xf images-cache.tar.zst -C work | |
| rm -f images-cache.tar.zst | |
| sudo sed -n 's/^\(name\|fingerprint\): //p' work/image-build/*/record | paste - - || true | |
| # Stage 06 packs what this builds. The browser takes some three hours or more when its tree is not | |
| # cached, the toolkit some minutes. | |
| - name: Software images (over the base system, in the chroot) | |
| id: images | |
| timeout-minutes: 330 | |
| run: make -o system images | |
| # Each image's tree and record, never an overlay's upper or work directory; saved when they changed. | |
| - name: Pack the images for their cache | |
| id: images_pack | |
| if: ${{ !cancelled() && steps.images.outcome == 'success' && steps.images_key.outputs.prefix != '' }} | |
| run: | | |
| d="$(cd "$KRYPTIK_WORK" && sudo cat image-build/*/record 2>/dev/null | sha256sum | cut -c1-16)" | |
| key="${{ steps.images_key.outputs.prefix }}${d}-" | |
| case "${{ steps.images_cache.outputs.cache-matched-key }}" in "$key"*) echo "the images are the cached ones"; exit 0 ;; esac | |
| cd /mnt/kryptik | |
| sudo tar -I 'zstd -T0' -cf images-cache.tar.zst -C work --exclude='image-build/*/upper' \ | |
| --exclude='image-build/*/overlay-work' --exclude='image-build/*/sysroot' image-build | |
| sudo chown "$(id -u):$(id -g)" images-cache.tar.zst | |
| ls -la images-cache.tar.zst | |
| echo "key=${key}${{ github.run_id }}-${{ github.run_attempt }}" >> "$GITHUB_OUTPUT" | |
| # A cache that cannot be saved costs the next run time, not this one its verdict. | |
| - name: Save the images | |
| if: ${{ !cancelled() && steps.images_pack.outputs.key != '' }} | |
| continue-on-error: true | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: /mnt/kryptik/images-cache.tar.zst | |
| key: ${{ steps.images_pack.outputs.key }} | |
| - name: Drop the images' tarball | |
| if: always() | |
| run: rm -f /mnt/kryptik/images-cache.tar.zst | |
| # Each image's tree and record for stage 06, cached or not, and the stamps and logs the build left. | |
| # A layer that does not ship keeps only its record: stage 06 reads no more of it. | |
| - name: Pack the images for the next job | |
| run: | | |
| cd /mnt/kryptik | |
| layers=() | |
| for rec in work/image-build/*/record; do | |
| if [ "$(sudo sed -n 's/^ships: //p' "$rec")" = no ]; then layers+=(--exclude="image-build/$(basename "$(dirname "$rec")")/tree"); fi | |
| done | |
| sudo tar -I 'zstd -T0' -cf images-tree.tar.zst -C work --exclude='image-build/*/upper' \ | |
| --exclude='image-build/*/overlay-work' --exclude='image-build/*/sysroot' "${layers[@]}" image-build .stamps logs | |
| sudo chown "$(id -u):$(id -g)" images-tree.tar.zst | |
| ls -la images-tree.tar.zst | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: images-tree | |
| path: /mnt/kryptik/images-tree.tar.zst | |
| retention-days: ${{ github.ref_type == 'tag' && 7 || 1 }} | |
| compression-level: 0 | |
| - name: Stage logs | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: logs-images | |
| path: /mnt/kryptik/work/logs | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| system: | |
| name: Stage 06 (the media) | |
| needs: [base, images] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 360 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| - uses: ./.github/actions/prepare | |
| with: | |
| sources: restore | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: work-after-05 | |
| path: /mnt/kryptik | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: images-tree | |
| path: /mnt/kryptik | |
| - name: Unpack the stage 04-05 tree and the images | |
| run: | | |
| cd /mnt/kryptik | |
| sudo tar --zstd -xf work-after-05.tar.zst -C work | |
| sudo tar --zstd -xf images-tree.tar.zst -C work | |
| rm -f work-after-05.tar.zst images-tree.tar.zst | |
| ls work; sudo du -sh work/sysroot work/image-build | |
| # The tree came without its module signing key, so stage 05 builds and signs again with a new one, | |
| # as after a cache restore; stage 06 relinks the kernel from that tree. | |
| - name: Stage 05, keyed afresh | |
| timeout-minutes: 90 | |
| run: make -o system kernel | |
| # The throwaway production pair, then A and B; a production release is only bound here, where upstream build scripts ran as root. | |
| - name: Stage 06, the media (production 1.0.0 and 1.0.1, release A and the update B) | |
| id: media | |
| timeout-minutes: 90 | |
| run: | | |
| make production-pair | |
| ls -la "$KRYPTIK_WORK/images-production" | |
| # A tag's version decides its role; elsewhere the dispatch input does. | |
| ROLE=development | |
| if [ "$GITHUB_REF_TYPE" = tag ]; then | |
| VER_A=0.0.0; VER_B="${GITHUB_REF_NAME#v}" | |
| case "$GITHUB_REF_NAME" in v0.*) ;; *) ROLE=production ;; esac | |
| elif [ "${{ inputs.role }}" = production ]; then | |
| ROLE=production; VER_A=0.0.0; VER_B=0.0.1 | |
| else | |
| VER_A="0.1.$(date -u +%Y%m%d).${GITHUB_SHA::8}"; VER_B="${VER_A}.1" | |
| fi | |
| echo "a ${ROLE} release: ${VER_B}, updating from ${VER_A}" | |
| echo "role=${ROLE}" >> "$GITHUB_OUTPUT" | |
| if [ "$ROLE" = development ]; then | |
| for v in "$VER_A" "$VER_B"; do make media KRYPTIK_VERSION="$v"; done | |
| ls -la "$KRYPTIK_WORK/images" | grep -E 'usb.img|\.iso|payload' | |
| exit 0 | |
| fi | |
| # A tag's public half is the tree's; a rehearsal's comes from a throwaway medium the sign job gets whole. | |
| pub=/mnt/kryptik/keys-public; mkdir -p "$pub" | |
| if [ "$GITHUB_REF_TYPE" = tag ]; then | |
| cp build/config/release/release-signers build/config/release/kryptik-sb.crt "$pub/" | |
| else | |
| tools/throwaway-key-medium.sh /mnt/kryptik/rehearsal-medium | |
| cp /mnt/kryptik/rehearsal-medium/release-signers /mnt/kryptik/rehearsal-medium/kryptik-sb.crt "$pub/" | |
| tar -C /mnt/kryptik/rehearsal-medium -cz . | base64 -w0 > /mnt/kryptik/rehearsal-medium.b64 | |
| rm -rf /mnt/kryptik/rehearsal-medium | |
| fi | |
| channel="https://${GITHUB_REPOSITORY_OWNER,,}.github.io/${GITHUB_REPOSITORY#*/}/stable/" | |
| for v in "$VER_A" "$VER_B"; do | |
| sudo rm -rf "$KRYPTIK_WORK/images" | |
| sudo find "$KRYPTIK_WORK/.stamps" -maxdepth 1 -name 'img-*' -delete | |
| make media KRYPTIK_MEDIA_PHASE=bind KRYPTIK_ROLE=production KRYPTIK_KEYS="$pub" \ | |
| KRYPTIK_VERSION="$v" KRYPTIK_CHANNEL="$channel" | |
| sudo mkdir -p "$KRYPTIK_WORK/bound/$v/stamps" | |
| sudo mv "$KRYPTIK_WORK/images" "$KRYPTIK_WORK/bound/$v/images" | |
| sudo cp -a "$KRYPTIK_WORK/.stamps"/img-* "$KRYPTIK_WORK/bound/$v/stamps/" | |
| done | |
| - name: Pack the work tree for the next jobs | |
| run: | | |
| tools/image/pack-work.sh production-media | |
| if [ "${{ steps.media.outputs.role }}" = production ]; then | |
| tools/image/pack-work.sh work-bound | |
| else | |
| tools/image/pack-work.sh work-after-media | |
| fi | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| if: steps.media.outputs.role == 'development' | |
| with: | |
| name: work-after-media | |
| path: /mnt/kryptik/work-after-media.tar.zst | |
| retention-days: 3 | |
| compression-level: 0 | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| if: steps.media.outputs.role == 'production' | |
| with: | |
| name: work-bound | |
| path: /mnt/kryptik/work-bound.tar.zst | |
| # A release waits for its owner's approval; a rehearsal does not. | |
| retention-days: ${{ github.ref_type == 'tag' && 7 || 1 }} | |
| compression-level: 0 | |
| # Throwaway keys, made for this rehearsal alone. | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| if: steps.media.outputs.role == 'production' && github.ref_type != 'tag' | |
| with: | |
| name: rehearsal-medium | |
| path: /mnt/kryptik/rehearsal-medium.b64 | |
| retention-days: 1 | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: production-media | |
| path: /mnt/kryptik/production-media.tar.zst | |
| retention-days: ${{ github.ref_type == 'tag' && 7 || 3 }} | |
| compression-level: 0 | |
| - name: Stage logs | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: logs-system | |
| path: /mnt/kryptik/work/logs | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # A production release signed on a fresh runner, unprivileged, from what the build job bound: nothing it hands over runs or links here. | |
| sign: | |
| name: Stage 06, signed (a production release) | |
| needs: system | |
| # From the ref, never from the build job's outputs, which its root processes could write. | |
| if: ${{ (github.ref_type == 'tag' && !startsWith(github.ref_name, 'v0.')) || (github.ref_type != 'tag' && inputs.role == 'production') }} | |
| # A tag's keys: the release environment takes only release tags, and its owner approves each run. | |
| environment: ${{ github.ref_type == 'tag' && !startsWith(github.ref_name, 'v0.') && 'release' || '' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 120 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| - uses: ./.github/actions/prepare | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: work-bound | |
| path: /mnt/kryptik | |
| - if: github.ref_type != 'tag' | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: rehearsal-medium | |
| path: /mnt/kryptik | |
| # The bound releases and the stamps alone, as the runner user, and nothing but plain files and directories: a link could reach the keys. | |
| - name: Unpack what was bound | |
| run: | | |
| cd /mnt/kryptik | |
| mkdir bound-in | |
| tar --zstd -xf work-bound.tar.zst -C bound-in ./bound ./.stamps | |
| odd="$(find bound-in ! -type f ! -type d)" | |
| [ -z "$odd" ] || { echo "the bound tree holds more than plain files and directories:"; echo "$odd" | head; exit 1; } | |
| ls bound-in/bound | |
| - name: Sign and assemble the media | |
| id: sign | |
| env: | |
| MEDIUM: ${{ secrets.KRYPTIK_KEY_MEDIUM }} | |
| run: | | |
| # The versions and the channel from the ref, as the build job made them. | |
| if [ "$GITHUB_REF_TYPE" = tag ]; then VER_B="${GITHUB_REF_NAME#v}"; else VER_B=0.0.1; fi | |
| [[ "$VER_B" =~ ^(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){2}$ ]] || { echo "${VER_B} is not a release version"; exit 1; } | |
| channel="https://${GITHUB_REPOSITORY_OWNER,,}.github.io/${GITHUB_REPOSITORY#*/}/stable/" | |
| m="$(mktemp -d)"; lines="$(mktemp)" | |
| trap 'rm -rf "$m" "$lines"' EXIT | |
| if [ "$GITHUB_REF_TYPE" = tag ]; then | |
| [ -n "$MEDIUM" ] || { echo "the release environment has no KRYPTIK_KEY_MEDIUM (docs/release-keys.md)"; exit 1; } | |
| printf '%s' "$MEDIUM" | base64 -d | tar -xz -C "$m" | |
| cmp build/config/release/release-signers "$m/release-signers" && cmp build/config/release/kryptik-sb.crt "$m/kryptik-sb.crt" \ | |
| || { echo "the release environment's keys are not the ones build/config/release names"; exit 1; } | |
| else | |
| base64 -d /mnt/kryptik/rehearsal-medium.b64 | tar -xz -C "$m" | |
| rm -f /mnt/kryptik/rehearsal-medium.b64 | |
| fi | |
| unset MEDIUM | |
| # The secret line of each signing key, which nothing signed or logged may hold. | |
| { for k in kryptik-release kryptik-latest; do [ ! -f "$m/$k" ] || sed -n 5p "$m/$k"; done | |
| tail -n 2 "$m/kryptik-sb.key" | head -n 1; } | grep -v '^$' > "$lines" | |
| sw=/mnt/kryptik/sign-work; so=/mnt/kryptik/sign-out | |
| mkdir -p "$sw/.stamps" "$sw/logs" "$so" | |
| cp /mnt/kryptik/bound-in/.stamps/kernel-install /mnt/kryptik/bound-in/.stamps/bs-licences "$sw/.stamps/" | |
| for v in 0.0.0 "$VER_B"; do | |
| rm -rf "$sw/images"; find "$sw/.stamps" -maxdepth 1 -name 'img-*' -delete | |
| mv "/mnt/kryptik/bound-in/bound/$v/images" "$sw/images" | |
| cp "/mnt/kryptik/bound-in/bound/$v/stamps"/img-* "$sw/.stamps/" | |
| make media SUDO= KRYPTIK_WORK="$sw" KRYPTIK_OUT="$so" KRYPTIK_MEDIA_PHASE=sign KRYPTIK_ROLE=production \ | |
| KRYPTIK_KEYS="$m" KRYPTIK_VERSION="$v" KRYPTIK_CHANNEL="$channel" | |
| mv "$sw/images" "/mnt/kryptik/signed-$v" | |
| done | |
| # A rehearsal's suites sign control disks with its throwaway key; a release's get theirs from release-tests. | |
| if [ -f "$m/kryptik-testctl" ]; then mkdir /mnt/kryptik/rehearsal-testctl; cp "$m"/kryptik-testctl "$m"/kryptik-testctl.pub /mnt/kryptik/rehearsal-testctl/; fi | |
| rm -rf "$m" | |
| rc=0; grep -rlF -f "$lines" /mnt/kryptik/signed-* "$sw" "$so" || rc=$? | |
| [ "$rc" -eq 1 ] || { echo "a private key is in what was signed, or it could not be searched (grep exited ${rc})"; exit 1; } | |
| echo "versions=0.0.0 ${VER_B}" >> "$GITHUB_OUTPUT" | |
| # The rest of the tree only now, with the keys gone: the suites' tree with the signed media in it. | |
| - name: Pack the work tree for the acceptance | |
| run: | | |
| cd /mnt/kryptik | |
| sudo tar --zstd -xf work-bound.tar.zst -C work | |
| rm -f work-bound.tar.zst | |
| sudo rm -rf work/bound work/images | |
| sudo mkdir work/images | |
| for v in ${{ steps.sign.outputs.versions }}; do sudo cp -a "signed-$v/." work/images/; done | |
| if [ -d rehearsal-testctl ]; then sudo mkdir -p work/keys/medium; sudo cp rehearsal-testctl/* work/keys/medium/; fi | |
| rm -rf signed-* bound-in rehearsal-testctl | |
| ls -la work/images | grep -E 'usb.img|\.iso|payload' | |
| "$GITHUB_WORKSPACE/tools/image/pack-work.sh" work-after-media | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: work-after-media | |
| path: /mnt/kryptik/work-after-media.tar.zst | |
| retention-days: 3 | |
| compression-level: 0 | |
| # A release's logs only once the search found no key in them. | |
| - name: Stage logs | |
| if: ${{ success() || github.ref_type != 'tag' }} | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: logs-sign | |
| path: /mnt/kryptik/sign-work/logs | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # Independent VMs, one part per machine; the verdict job judges the whole run. | |
| acceptance: | |
| name: Acceptance (${{ matrix.suites }}) | |
| needs: [system, sign] | |
| if: ${{ !cancelled() && needs.system.result == 'success' && (needs.sign.result == 'success' || needs.sign.result == 'skipped') }} | |
| # A release's control-disk key, which no artifact may carry; release-tests takes only release tags. The part | |
| # that runs the build's own programs as root stays out: its runner would hold the key in a step's environment. | |
| environment: ${{ github.ref_type == 'tag' && !startsWith(github.ref_name, 'v0.') && !contains(matrix.suites, 'build') && 'release-tests' || '' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 150 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # About 18, 16, 12 and 11 minutes, and the pair's update suite beside build, the one that runs sysroot programs on the host. | |
| suites: [update, install, 'integrity,zones', 'inputs,boot,desktop', 'build,production'] | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| - uses: ./.github/actions/prepare | |
| with: | |
| vm: 'true' | |
| sources: restore | |
| # The inputs suite checks every source against both locks; the other parts read only the root's. | |
| image-sources: ${{ contains(matrix.suites, 'inputs') && 'restore' || '' }} | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: work-after-media | |
| path: /mnt/kryptik | |
| # The whole production pair, for its own part. | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| if: contains(matrix.suites, 'production') | |
| with: | |
| name: production-media | |
| path: /mnt/kryptik | |
| # As root, so the sysroot keeps its owners and modes. | |
| - name: Unpack the work tree | |
| run: | | |
| cd /mnt/kryptik | |
| sudo tar --zstd -xf work-after-media.tar.zst -C work | |
| rm -f work-after-media.tar.zst | |
| if [ -f production-media.tar.zst ]; then sudo tar --zstd -xf production-media.tar.zst -C work; rm -f production-media.tar.zst; fi | |
| ls work/images; df -h /mnt | tail -1 | |
| # The host suites' proxy-calls traces the static proxy's live tests, as CI's Compartment layer | |
| # does; root runs them with this user's cargo and rustup, which tools/acceptance.sh finds. | |
| - name: The proxy-calls suite's target and strace | |
| if: contains(matrix.suites, 'build') | |
| run: | | |
| command -v strace >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq strace; } | |
| rustup target add x86_64-unknown-linux-musl | |
| # The script, not make, which turns every failure into 2; exit 2 is a part's normal answer. | |
| - name: Acceptance of these suites | |
| env: | |
| SUITES: ${{ matrix.suites }} | |
| TESTCTL: ${{ secrets.KRYPTIK_TESTCTL_KEY }} | |
| run: | | |
| rc=0 | |
| # A release's media honour control disks signed by their medium's key: a rehearsal's copy, or a release's secret. | |
| k="$KRYPTIK_WORK/keys/medium/kryptik-testctl"; [ -f "$k" ] || k="" | |
| if [ "$GITHUB_REF_TYPE" = tag ] && [ -n "$TESTCTL" ] && [[ ",${SUITES}," != *,build,* ]]; then | |
| k="$(mktemp -d)/kryptik-testctl" | |
| (umask 077; printf '%s\n' "$TESTCTL" > "$k") | |
| fi | |
| unset TESTCTL | |
| sudo env KRYPTIK_WORK="$KRYPTIK_WORK" KRYPTIK_SOURCES="$KRYPTIK_SOURCES" NO_COLOR=1 KRYPTIK_TESTCTL_KEY="$k" \ | |
| TMPDIR="$TMPDIR" CARGO_TARGET_DIR="$CARGO_TARGET_DIR" tools/acceptance.sh --only "$SUITES" || rc=$? | |
| [ "$rc" -eq 0 ] || [ "$rc" -eq 2 ] | |
| - name: Hand the results to the runner user | |
| if: always() | |
| run: sudo chown -R "$(id -u):$(id -g)" /mnt/kryptik/work/acceptance /mnt/kryptik/work/logs 2>/dev/null || true | |
| - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| if: always() | |
| with: | |
| name: acceptance-part-${{ strategy.job-index }} | |
| path: | | |
| /mnt/kryptik/work/acceptance | |
| /mnt/kryptik/work/logs | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| verdict: | |
| name: make acceptance (the verdict over every suite) | |
| needs: [system, sign, acceptance] | |
| if: ${{ !cancelled() && needs.system.result == 'success' && (needs.sign.result == 'success' || needs.sign.result == 'skipped') }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| steps: | |
| # The whole history and its tags, for the notes' changes since the last release. | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| with: | |
| fetch-depth: 0 | |
| # The same packages as the parts; this job boots nothing. | |
| - uses: ./.github/actions/prepare | |
| with: | |
| vm: 'true' | |
| # The media, for the export and the hashes the report names. | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: work-after-media | |
| path: /mnt/kryptik | |
| - name: Unpack the work tree | |
| run: | | |
| cd /mnt/kryptik | |
| sudo tar --zstd -xf work-after-media.tar.zst -C work | |
| rm -f work-after-media.tar.zst | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| pattern: acceptance-part-* | |
| path: /mnt/kryptik/parts | |
| - name: make acceptance, merged | |
| id: acceptance | |
| continue-on-error: true | |
| run: | | |
| mkdir -p /mnt/kryptik/export | |
| sudo env KRYPTIK_WORK="$KRYPTIK_WORK" KRYPTIK_SOURCES="$KRYPTIK_SOURCES" NO_COLOR=1 TMPDIR="$TMPDIR" \ | |
| tools/acceptance.sh --merge /mnt/kryptik/parts --export /mnt/kryptik/export | |
| - name: Hand the results to the runner user | |
| if: always() | |
| run: sudo chown -R "$(id -u):$(id -g)" /mnt/kryptik/export /mnt/kryptik/work/acceptance 2>/dev/null || true | |
| - name: Acceptance report and per-item logs | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: acceptance-report | |
| path: | | |
| /mnt/kryptik/work/acceptance | |
| /mnt/kryptik/parts | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| # Only a run in which every suite passed publishes images. | |
| - name: Tested images | |
| if: steps.acceptance.outcome == 'success' | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: kryptik-release | |
| path: /mnt/kryptik/export | |
| retention-days: 14 | |
| if-no-files-found: error | |
| compression-level: 1 | |
| - name: The verdict is the job's | |
| if: steps.acceptance.outcome != 'success' | |
| run: | | |
| echo "make acceptance did not pass; see the acceptance-report artifact" | |
| exit 1 | |
| # A tag's tested export, source and acceptance logs, drafted on the Releases page (docs/releases.md); the only job that writes. | |
| release: | |
| name: Draft the release | |
| needs: [verdict] | |
| if: ${{ !cancelled() && github.ref_type == 'tag' && needs.verdict.result == 'success' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 120 | |
| permissions: | |
| contents: write | |
| steps: | |
| # The tag itself, for the check that it is at the tested revision. | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 | |
| with: | |
| fetch-depth: 0 | |
| - uses: ./.github/actions/prepare | |
| with: | |
| sources: fetch | |
| image-sources: fetch | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: kryptik-release | |
| path: /mnt/kryptik/export | |
| - name: The corresponding source | |
| run: make source-bundle | |
| - name: Draft the release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: tools/release-publish.sh /mnt/kryptik/export --source-bundle "$(ls "$KRYPTIK_OUT"/source-*.tar)" |