Publish image #15
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: Publish image | |
| # Daily, because an installation without a network installs the age of the | |
| # image it was made from, and nothing about publishing one is expensive now | |
| # that what is published is only what the end-to-end cases passed. Also on | |
| # request, for the day when something has to be looked at before tomorrow. | |
| # | |
| # At an unremarkable minute of the middle of the day, and not at the top of an | |
| # hour, because a schedule here is a request rather than an appointment: it is | |
| # run when there is room to run it, and the top of every hour is where everyone | |
| # else asked first. Nothing waits on this, so a late one costs nothing and a | |
| # skipped one is a day without a new image. | |
| on: | |
| schedule: | |
| - cron: '37 12 * * *' | |
| workflow_dispatch: | |
| # Two of these would be building the same thing and uploading to the same | |
| # place, and the second to finish would decide what the day's image is. | |
| concurrency: | |
| group: publish-image | |
| cancel-in-progress: false | |
| jobs: | |
| image: | |
| runs-on: ubuntu-latest | |
| # mkarchiso makes filesystems and mounts them, which a container cannot do | |
| # without being allowed to. | |
| container: | |
| image: archlinux:latest | |
| options: --privileged | |
| # Its own, and not the one the signing key is in. Nothing here signs | |
| # anything, so nothing here has any business reaching that key. | |
| environment: image | |
| steps: | |
| - name: Install what building an image needs | |
| run: | | |
| pacman -Syu --noconfirm --needed archiso git curl aws-cli-v2 jq \ | |
| qemu-system-x86 qemu-img edk2-ovmf libarchive util-linux procps-ng | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # The keyring has to be there before anything is verified against it, and | |
| # a container starts without one. | |
| - name: Start this machine's keyring | |
| run: | | |
| set -eu | |
| pacman-key --init | |
| pacman-key --populate archlinux | |
| - name: Build the image | |
| run: | | |
| set -eu | |
| mkdir -p out | |
| archiso/build.sh "$PWD/out" | |
| ls -lh out | |
| # Before it goes anywhere. Every image published until now became the one | |
| # people download with nothing having booted it, and four of the six built | |
| # on 2026-09-11 were broken in ways only booting them showed. A failure | |
| # here leaves the image unuploaded, which is the whole point of running it | |
| # here rather than after. | |
| # | |
| # The cases boot the image under QEMU, so this wants hardware | |
| # acceleration; the harness says which it got and goes on either way. What | |
| # each case is and what it holds the image to is | |
| # docs/development/006-end-to-end-testing.md. | |
| - name: Test the image before publishing it | |
| run: | | |
| set -eu | |
| tests/e2e/run.sh --transcripts "$PWD/transcripts" "$(ls out/*.iso)" | |
| # The harness reports that a case failed; what it failed at is in the | |
| # console of the guest that failed it, and that is written on a machine | |
| # which stops existing when this run ends. So it is taken before it goes, | |
| # and only when there is a reason to: a run where everything passed has | |
| # nothing in them anybody wants. | |
| - name: Keep what a failed case left behind | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: end-to-end-transcripts | |
| path: transcripts/ | |
| # A bucket of its own and not the one the packages are in. They are read | |
| # by different things at different rates, and this one has images | |
| # deleted out of it, which is not something to do next to a repository | |
| # whose whole property is that nothing in it disappears. | |
| - name: Upload it, and keep the last two | |
| env: | |
| # These names are the same ones the package workflow reads, and they | |
| # are not the same secrets: what they resolve to is decided by the | |
| # environment this job declares. The credentials behind them here are | |
| # scoped to this bucket alone, and the ones the packages are | |
| # published with would not write to it. | |
| AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: auto | |
| R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} | |
| R2_BUCKET: ${{ secrets.R2_BUCKET }} | |
| run: | | |
| set -eu | |
| image="$(ls out/*.iso)" | |
| name="$(basename "$image")" | |
| # Its checksum goes up beside it, because what the project says about | |
| # an image is how someone tells they have the one it published. | |
| ( cd out && sha256sum "$name" > "$name.sha256" ) | |
| endpoint="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com" | |
| for file in "$image" "$image.sha256"; do | |
| aws s3 cp --endpoint-url "$endpoint" \ | |
| "$file" "s3://${R2_BUCKET}/$(basename "$file")" | |
| done | |
| # Pruned after the new one is up and not before: an upload that fails | |
| # then leaves the two that were there rather than one. It costs a few | |
| # minutes with three of them in the bucket, which is what that is | |
| # worth. | |
| # | |
| # And the one this replaces is left alone, for the reason | |
| # docs/decisions/018-installation-iso.md gives: somebody may be half | |
| # way through downloading it. | |
| # | |
| # Images are named for the minute they were built, so what sorts last | |
| # is what was built last, and nothing has to read a date out of | |
| # anything. | |
| # | |
| # By bytes, which is what LC_ALL=C asks for. The Worker that answers | |
| # with the newest image sorts the same keys in JavaScript, which | |
| # orders by code unit and has no locale to take; a sort here that | |
| # took one would weigh punctuation differently and the two would | |
| # disagree about which image is the current one. | |
| aws s3api list-objects-v2 --endpoint-url "$endpoint" \ | |
| --bucket "$R2_BUCKET" --query 'Contents[].Key' --output json | | |
| jq -r '.[] | select(endswith(".iso"))' | LC_ALL=C sort | head -n -2 | | |
| while read -r old; do | |
| echo "removing $old" | |
| aws s3 rm --endpoint-url "$endpoint" "s3://${R2_BUCKET}/$old" | |
| aws s3 rm --endpoint-url "$endpoint" "s3://${R2_BUCKET}/$old.sha256" | |
| done |