Repository navigation
fix(guest): apply GitHub's hosted runtime on every guest (supersedes #417, #419, #421) #28
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: official-golden | |
| # Official golden images, decoupled from releases. | |
| # | |
| # Release jobs consume published goldens; they never bake. This workflow owns | |
| # the bake side, triggered three ways: | |
| # - `pull_request` touching official-image.toml — but only a change to the | |
| # `official_runner_image_base_*` image pins starts a bake: the same file also | |
| # carries the `golden_*` launch-time runtime expectations, which are not bake | |
| # inputs, and `image-pins-changed` below skips the bake when only those moved | |
| # (the weekly runtime-drift bump PR is exactly that case); | |
| # - weekly `schedule`, which only runs the drift check below; | |
| # - manual `workflow_dispatch` with an arch selector. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| arch: | |
| description: Architecture to build | |
| required: false | |
| default: x86_64 | |
| type: choice | |
| options: | |
| - x86_64 | |
| pull_request: | |
| paths: | |
| - official-image.toml | |
| schedule: | |
| # Monday 04:00 UTC: poll the dump pipeline's floating tags; a mismatch | |
| # opens a pin-bump PR, which retriggers this workflow via the push rule. | |
| - cron: "0 4 * * 1" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| # Pushes to the same pull request must not pile up golden bakes: a newer run | |
| # cancels the older one. Manual (`workflow_dispatch`) and scheduled runs are | |
| # never cancelled — a bake that has been running for hours must not be lost | |
| # to an unrelated trigger. | |
| group: official-golden-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| check-upstream-drift: | |
| name: Open bump PR on upstream rebuild | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Compare floating dump tags against pins | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| CRANE_TAG="$(gh release view --repo google/go-containerregistry --json tagName --jq .tagName)" | |
| gh release download --repo google/go-containerregistry "$CRANE_TAG" --pattern '*Linux_x86_64.tar.gz' --dir /tmp/crane-dl --clobber | |
| sudo tar -xzf /tmp/crane-dl/go-containerregistry_Linux_x86_64.tar.gz -C /usr/local/bin crane | |
| drift_arch() { | |
| local key="$1" | |
| local pinned | |
| pinned="$(awk -F'"' -v k="^${key} = " '$0 ~ k { print $2; exit }' official-image.toml)" | |
| test -n "$pinned" | |
| local floating="${pinned%%@*}" | |
| local live | |
| live="$(crane digest "$floating")" | |
| local want="${pinned##*@}" | |
| if [ "$live" != "$want" ]; then | |
| echo "$key drifted: $want -> $live" | |
| sed -i "s|^${key} = \"${floating}@sha256:[0-9a-f]*\"$|${key} = \"${floating}@${live}\"|" official-image.toml | |
| grep -q "^${key} = \"${floating}@${live}\"$" official-image.toml | |
| return 0 | |
| fi | |
| echo "$key up to date ($want)" | |
| return 1 | |
| } | |
| CHANGED=0 | |
| if drift_arch official_runner_image_base_amd64; then CHANGED=1; fi | |
| if drift_arch official_runner_image_base_arm64; then CHANGED=1; fi | |
| if [ "$CHANGED" -eq 0 ]; then | |
| echo "pins match floating tags; nothing to do" | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| BRANCH="chore/official-image-drift-$(date -u +%F)" | |
| git checkout -b "$BRANCH" | |
| git add official-image.toml | |
| git commit -m "chore(golden): update official image pins to upstream rebuild" | |
| git push -u origin "$BRANCH" | |
| gh pr create --base main --head "$BRANCH" --title "chore(golden): update official image pins to upstream rebuild" --body "Weekly drift check found the dump pipeline's floating tags ahead of the pins. Merging rebuilds the official goldens via push trigger." | |
| image-pins-changed: | |
| name: Do the image pins change? | |
| # official-image.toml carries two classes of keys: the | |
| # `official_runner_image_base_*` image pins, which decide a golden rebuild, | |
| # and the `golden_*` launch-time runtime expectations (process limits, | |
| # sysctls, hostname contract, step-env contract), which are applied per | |
| # guest and per launch and are not baked in. Only the former may start a | |
| # ~60 GiB bake. | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| changed: ${{ steps.diff.outputs.changed }} | |
| steps: | |
| - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Diff the image pins between base and head | |
| id: diff | |
| run: | | |
| set -euo pipefail | |
| git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" | |
| before="$(git show "FETCH_HEAD:official-image.toml" | grep -E '^official_runner_image_base_(amd64|arm64) = ' || true)" | |
| after="$(grep -E '^official_runner_image_base_(amd64|arm64) = ' official-image.toml || true)" | |
| if [ "$before" = "$after" ]; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "the image pins are unchanged; the golden_* runtime keys do not need a bake" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| diff <(printf '%s\n' "$before") <(printf '%s\n' "$after") || true | |
| fi | |
| build-official-x86_64: | |
| name: Build official x86_64 golden | |
| needs: image-pins-changed | |
| # `!cancelled()` keeps the job eligible when `image-pins-changed` was | |
| # skipped (it only runs for pull requests) — a bare `if` would inherit an | |
| # implicit `success()` and skip every manual bake. The pull-request branch | |
| # then requires the gate to have run and found the pins moved. | |
| if: ${{ !cancelled() && ((github.event_name == 'workflow_dispatch' && (inputs.arch == 'both' || inputs.arch == 'x86_64')) || (github.event_name == 'pull_request' && needs.image-pins-changed.result == 'success' && needs.image-pins-changed.outputs.changed == 'true')) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 180 | |
| env: | |
| CARGO_BUILD_JOBS: "1" | |
| # Guest disk for the bake VM; the headroom gate below and | |
| # PRELOOP_RUNNER_STORAGE_GB both read it. | |
| GOLDEN_GUEST_GIB: "200" | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| packages: read | |
| steps: | |
| # The bake needs nested KVM to boot the image it packs. Self-hosted | |
| # runners (preloop smolvm guests) have no /dev/kvm, so every step is | |
| # gated on github-hosted: under preloop the job skips green instead of | |
| # burning ~20 min of compile before dying at the KVM check. | |
| - name: Check out repository | |
| if: runner.environment == 'github-hosted' | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Resolve golden identity and base | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| set -euo pipefail | |
| echo "RUNNER_TARGET=x86_64-unknown-linux-gnu" >> "$GITHUB_ENV" | |
| echo "GOLDEN_ARCH=x86_64" >> "$GITHUB_ENV" | |
| echo "GOLDEN_PLATFORM=linux/amd64" >> "$GITHUB_ENV" | |
| BASE_IMAGE="$(awk -F'"' '/^official_runner_image_base_amd64 = / { print $2; exit }' official-image.toml)" | |
| test -n "$BASE_IMAGE" | |
| echo "BASE_IMAGE=$BASE_IMAGE" >> "$GITHUB_ENV" | |
| - name: Set up Rust toolchain | |
| if: runner.environment == 'github-hosted' | |
| uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable | |
| - name: Build Preloop binaries | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| cargo build --release -p preloop-cli -p preloop-runner-server | |
| cargo build --release -p preloop-runner --target "$RUNNER_TARGET" | |
| - name: Capture pinned base provenance | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| GOLDEN_NAME="preloop-ubuntu-24.04-$GOLDEN_ARCH" | |
| python3 scripts/capture-base-provenance.py \ | |
| --image "$BASE_IMAGE" \ | |
| --platform "$GOLDEN_PLATFORM" \ | |
| --output "dist/$GOLDEN_NAME.base-provenance.json" \ | |
| --sbom-output "dist/$GOLDEN_NAME.base-sbom.spdx.json" \ | |
| --require-sbom \ | |
| --require-source-prefix \ | |
| "https://github.com/preloopdev/runner-image-blobs/tree" | |
| - name: Install configured smolvm | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| set -euo pipefail | |
| SMOLVM_VERSION="$(grep -E '^[[:space:]]*smolvm_golden_version[[:space:]]*=' versions.toml | cut -d'"' -f2 || true)" | |
| [ -n "$SMOLVM_VERSION" ] || { echo "smolvm_golden_version missing from versions.toml" >&2; exit 1; } | |
| ARCHIVE="smolvm-${SMOLVM_VERSION}-linux-x86_64.tar.gz" | |
| BASE="https://github.com/smol-machines/smolvm/releases/download/v${SMOLVM_VERSION}" | |
| curl -fsSL -o "$ARCHIVE" "$BASE/$ARCHIVE" | |
| curl -fsSL -o checksums.sha256 "$BASE/checksums.sha256" | |
| grep -F "$ARCHIVE" checksums.sha256 | sha256sum -c - | |
| tar -xzf "$ARCHIVE" | |
| SRC="smolvm-${SMOLVM_VERSION}-linux-x86_64" | |
| mkdir -p "$HOME/.smolvm" | |
| rm -rf "$HOME/.smolvm/lib" | |
| cp -R "$SRC/lib" "$HOME/.smolvm/lib" | |
| rm -f "$HOME/.smolvm"/storage-template.ext4 "$HOME/.smolvm"/overlay-template.ext4 \ | |
| "$HOME/.smolvm"/storage-template.ext4.zst "$HOME/.smolvm"/overlay-template.ext4.zst | |
| for f in smolvm smolvm-bin storage-template.ext4 overlay-template.ext4 \ | |
| storage-template.ext4.zst overlay-template.ext4.zst; do | |
| [ -f "$SRC/$f" ] && cp "$SRC/$f" "$HOME/.smolvm/" | |
| done | |
| echo "$SMOLVM_VERSION" > "$HOME/.smolvm/.version" | |
| DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/smolvm" | |
| mkdir -p "$DATA_DIR" | |
| rm -rf "$DATA_DIR/agent-rootfs" | |
| cp -R "$SRC/agent-rootfs" "$DATA_DIR/agent-rootfs" | |
| rm -f "$HOME/.smolvm/storage-template.ext4" "$HOME/.smolvm/overlay-template.ext4" | |
| # Guest disk must hold extracted layers + merged layer + pack | |
| # staging. 120G died at `pack binary: tar error: No space left | |
| # on device` (35267336773, 35279487232); sparse on host, so | |
| # 200G is safe. | |
| truncate -s "${GOLDEN_GUEST_GIB}G" "$HOME/.smolvm/storage-template.ext4" | |
| mkfs.ext4 -F -q -O ^has_journal -b 4096 "$HOME/.smolvm/storage-template.ext4" | |
| truncate -s "${GOLDEN_GUEST_GIB}G" "$HOME/.smolvm/overlay-template.ext4" | |
| mkfs.ext4 -F -q -O ^has_journal -b 4096 -L smolvm-overlay "$HOME/.smolvm/overlay-template.ext4" | |
| ln -sf /usr/sbin/resize2fs "$DATA_DIR/agent-rootfs/bin/resize2fs" || true | |
| ln -sf /sbin/e2fsck "$DATA_DIR/agent-rootfs/bin/e2fsck" || true | |
| ln -sf /usr/sbin/resize2fs "$DATA_DIR/agent-rootfs/usr/bin/resize2fs" || true | |
| ln -sf /sbin/e2fsck "$DATA_DIR/agent-rootfs/usr/bin/e2fsck" || true | |
| [ -f "$SRC/init.krun" ] && cp "$SRC/init.krun" "$DATA_DIR/init.krun" | |
| mkdir -p "$HOME/.local/bin" | |
| ln -sf "$HOME/.smolvm/smolvm" "$HOME/.local/bin/smolvm" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Grant hosted runner KVM access | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| set -euo pipefail | |
| test -e /dev/kvm | |
| sudo chmod a+rw /dev/kvm | |
| test -r /dev/kvm | |
| test -w /dev/kvm | |
| - name: Reclaim runner disk for layer staging | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| set -euo pipefail | |
| # `pack create` stages the extracted layers, the merged layer, the | |
| # agent rootfs and an ext4 template next to the output — tens of | |
| # GiB for a runner-scale base. release-golden #73 died there with | |
| # `flushing extracted layers to disk failed (out of space?)`. | |
| # Hosted images ship ~30 GiB of toolchains this bake never touches. | |
| df -h / | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ | |
| /usr/local/share/boost /usr/share/swift "$AGENT_TOOLSDIRECTORY" || true | |
| sudo docker image prune --all --force >/dev/null 2>&1 || true | |
| # Release build finished above; build-golden only needs the | |
| # binaries and the runner bundle. Drop rebuild cache and registry | |
| # downloads: 35257438708 died writing flat-export.tmp (ENOSPC) | |
| # with 105G free — pack staging needs every GiB. | |
| rm -rf target/release/deps target/release/build \ | |
| target/release/.fingerprint target/release/incremental \ | |
| "$HOME/.cargo/registry/cache" "$HOME/.cargo/registry/src" || true | |
| sudo rm -rf /usr/lib/jvm /opt/google/chrome \ | |
| /usr/share/miniconda || true | |
| df -h / | |
| env: | |
| AGENT_TOOLSDIRECTORY: ${{ env.AGENT_TOOLSDIRECTORY }} | |
| - name: Require disk headroom for pack staging | |
| if: runner.environment == 'github-hosted' | |
| run: | | |
| set -euo pipefail | |
| # `pack create` writes the export's layers and an ext4 template the | |
| # size of the guest disk next to the output, so the host needs at | |
| # least the guest's own size free. 35257438708 burned three attempts | |
| # inside `smolvm pack` with 110G free for a ${GOLDEN_GUEST_GIB}G | |
| # guest; fail fast here instead. | |
| need_gib=$((GOLDEN_GUEST_GIB + 20)) | |
| avail_gib=$(df --output=avail -BG / | tail -n1 | tr -dc '0-9') | |
| echo "pack staging: ${avail_gib}G free, needs ~${need_gib}G (guest ${GOLDEN_GUEST_GIB}G + staging and output)" | |
| if [ "$avail_gib" -lt "$need_gib" ]; then | |
| echo "::error::not enough disk to pack a ${GOLDEN_GUEST_GIB}G guest: ${avail_gib}G free, need ~${need_gib}G" | |
| cat >&2 <<MSG | |
| This runner's filesystem cannot hold the pack staging, so the bake | |
| would fail every attempt with ENOSPC after an hour of backoff. | |
| Bake on a host with the space (scripts/build-golden-x86_64.sh on the | |
| x86_64+KVM host) or raise this pool's PRELOOP_RUNNER_STORAGE_GB. | |
| MSG | |
| exit 1 | |
| fi | |
| - name: Pack Golden MicroVM Artifact | |
| if: runner.environment == 'github-hosted' | |
| env: | |
| GOLDEN_NAME: preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }} | |
| GODEBUG: http2client=0 | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| # Wipe pack staging from any earlier attempt; keep the pinned | |
| # base evidence captured above (the provenance manifest needs it). | |
| clear_staging() { | |
| for entry in dist/*; do | |
| [ -e "$entry" ] || continue | |
| case "$entry" in | |
| *.base-provenance.json|*.base-sbom.spdx.json) ;; | |
| *) rm -rf "$entry" ;; | |
| esac | |
| done | |
| } | |
| clear_staging | |
| # Stream the base from the registry inside the bake VM. Do NOT go | |
| # back to a host `docker save` tar: the 17 GiB export does not fit | |
| # the hosted runner's disk (release-golden #66-rerun/#71: `docker | |
| # save` dies with `no space left on device` after a successful | |
| # pull). Streaming keeps every copy off the host filesystem. | |
| export PRELOOP_REQUIRE_BASE_DIGEST=1 | |
| export PRELOOP_RUNNER_BASE_IMAGE="$BASE_IMAGE" | |
| # Guest disk must hold extracted layers + merged layer + pack | |
| # staging. 120G died at `pack binary: tar error: No space left on | |
| # device` (35267336773), and 200G died the same way on a host with | |
| # 110G free (35257438708): the pack materialises the template on the | |
| # *host*, so "sparse on host" does not make 200G safe — the headroom | |
| # gate above requires host free space to cover it before starting. | |
| export PRELOOP_RUNNER_STORAGE_GB="$GOLDEN_GUEST_GIB" | |
| export PRELOOP_RUNNER_OVERLAY_GB="$GOLDEN_GUEST_GIB" | |
| # The runner-large base archive is ~17 GiB uncompressed, over | |
| # smolvm's 8 GiB default local-archive cap. Without this the bake | |
| # fails every attempt at `smolvm create` before extracting a byte. | |
| export SMOLVM_MAX_IMAGE_BYTES=34359738368 | |
| # `pack create --from-vm` boots an export helper whose disk defaults | |
| # to 3x the source. Growing the template filesystem to that size at | |
| # helper boot (~420 ms) delays the agent's accept loop; its control | |
| # listener has a backlog of 1, so the host's other boot connections | |
| # are reset and libkrun abandons them after 10000 back-to-back | |
| # retries (~350 ms on a fast KVM host), failing the pack with | |
| # `Connection reset by peer`. A helper disk the template's size boots | |
| # without the grow. | |
| export SMOLVM_EXPORT_HELPER_STORAGE_GIB="$GOLDEN_GUEST_GIB" | |
| # SmolVM machine names allow only ASCII alphanumerics and hyphens. | |
| export PRELOOP_GOLDEN_NAME_PREFIX="preloop-release-golden-${GITHUB_RUN_ID}-x86-64" | |
| builder="${PRELOOP_GOLDEN_NAME_PREFIX}-builder" | |
| # Answer the guest directly instead of through the NAT DNS proxy | |
| # (100.96.0.1), which has never resolved ghcr.io from a bake VM on | |
| # a hosted runner: release-golden #68/#69/#70 burned 9/9 attempts | |
| # on `crane manifest ... lookup ghcr.io ... i/o timeout`, and #73 | |
| # proved the same run pulls fine the moment this is set. Trying the | |
| # broken default first only costs an attempt and a backoff. | |
| export PRELOOP_RUNNER_DNS=1.1.1.1 | |
| need_mib=6144 | |
| attempts=3 | |
| headroom() { awk '/^MemAvailable:/ { print int($2 / 1024) }' /proc/meminfo; } | |
| for attempt in $(seq 1 "$attempts"); do | |
| echo "attempt ${attempt}/${attempts}: guest DNS $PRELOOP_RUNNER_DNS" | |
| for _ in $(seq 1 45); do | |
| waiting="$(headroom)" | |
| if [ "$waiting" -ge "$need_mib" ]; then break; fi | |
| echo "waiting for host headroom: ${waiting} MiB < ${need_mib} MiB" | |
| sleep 60 | |
| done | |
| available="$(headroom)" | |
| if [ "$available" -lt "$need_mib" ]; then | |
| echo "host never freed ${need_mib} MiB (last ${available} MiB)" >&2 | |
| exit 1 | |
| fi | |
| echo "attempt ${attempt}/${attempts} with ${available} MiB available" | |
| smolvm machine delete --name "$builder" \ | |
| --force --cascade >/dev/null 2>&1 || true | |
| if ./target/release/preloop build-golden \ | |
| --runner-bundle "target/$RUNNER_TARGET/release" \ | |
| --output "dist/$GOLDEN_NAME"; then | |
| break | |
| fi | |
| if [ "$attempt" -eq "$attempts" ]; then | |
| echo "build-golden failed after ${attempts} attempts" >&2 | |
| exit 1 | |
| fi | |
| echo "build-golden failed; backing off before retry" | |
| clear_staging | |
| sleep $((attempt * 120)) | |
| done | |
| test -s "dist/$GOLDEN_NAME" | |
| sha256sum "dist/$GOLDEN_NAME" > "dist/$GOLDEN_NAME.sha256" | |
| - name: Write golden provenance manifest | |
| if: runner.environment == 'github-hosted' | |
| env: | |
| GOLDEN_NAME: preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }} | |
| run: | | |
| MAX_AGE_DAYS="$(grep -E '^[[:space:]]*apt_indices_max_age_days[[:space:]]*=' versions.toml | cut -d'"' -f2 || true)" | |
| test -n "$MAX_AGE_DAYS" | |
| python3 scripts/write-golden-provenance.py \ | |
| --golden "dist/$GOLDEN_NAME" \ | |
| --base-evidence "dist/$GOLDEN_NAME.base-provenance.json" \ | |
| --base-sbom "dist/$GOLDEN_NAME.base-sbom.spdx.json" \ | |
| --apt-indices-max-age-days "$MAX_AGE_DAYS" \ | |
| --output "dist/$GOLDEN_NAME.provenance.json" | |
| - name: Install cosign | |
| if: runner.environment == 'github-hosted' | |
| uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 | |
| - name: Sign golden (cosign keyless) | |
| if: runner.environment == 'github-hosted' | |
| env: | |
| GOLDEN_NAME: preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }} | |
| run: | | |
| set -euo pipefail | |
| cosign sign-blob --yes --bundle "dist/$GOLDEN_NAME.bundle" "dist/$GOLDEN_NAME" | |
| cosign verify-blob \ | |
| --bundle "dist/$GOLDEN_NAME.bundle" \ | |
| --certificate-identity-regexp \ | |
| "^https://github.com/$GITHUB_REPOSITORY/.github/workflows/release-golden.yml@refs/(heads/main|tags/)" \ | |
| --certificate-oidc-issuer https://token.actions.githubusercontent.com \ | |
| "dist/$GOLDEN_NAME" | |
| - name: Attest golden (GitHub provenance) | |
| if: runner.environment == 'github-hosted' | |
| uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 | |
| with: | |
| subject-path: | | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }} | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.base-provenance.json | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.base-sbom.spdx.json | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.provenance.json | |
| - name: Retain Golden Artifact | |
| if: runner.environment == 'github-hosted' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: golden-${{ env.GOLDEN_ARCH }} | |
| path: | | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }} | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.part.* | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.parts.sha256 | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.sha256 | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.bundle | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.base-provenance.json | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.base-sbom.spdx.json | |
| dist/preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}.provenance.json | |
| retention-days: 90 | |
| if-no-files-found: error | |
| - name: Split golden for artifact retention | |
| if: runner.environment == 'github-hosted' | |
| env: | |
| GOLDEN_NAME: preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }} | |
| run: | | |
| set -euo pipefail | |
| rm -f "dist/$GOLDEN_NAME.part."* | |
| split -b 4G -d "dist/$GOLDEN_NAME" "dist/$GOLDEN_NAME.part." | |
| sha256sum "dist/$GOLDEN_NAME.part."* > "dist/$GOLDEN_NAME.parts.sha256" | |
| # aarch64 golden: built once on macstudio (hosted macOS EINVAL blocks the | |
| # bake), published to ghcr.io/preloopdev/preloop-arm64-smolvm-golden | |
| # and consumed via PRELOOP_GOLDEN_OCI_REF. No CI bake — the artifact is a release input. |