Boot Test dakota (aarch64) #55
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: Boot Test dakota (aarch64) | |
| # M0 gate for the aarch64 (and eventually Apple Silicon / Asahi) effort: | |
| # prove the published aarch64 image actually reaches multi-user.target in a VM. | |
| # No aarch64 image has ever been boot-verified — x13s-era assumptions die here. | |
| # | |
| # Same decoupling invariants as build-aarch64.yml: this NEVER blocks x86_64 | |
| # builds, publishes, promotes, or releases. Observational smoke lives in its | |
| # own workflow (see publish-smoke.yml for the rationale). | |
| # | |
| # Boots via bcvk ephemeral (qemu + virtiofs root, direct kernel boot from the | |
| # container — same mechanism as `just boot-test`), with serial console and | |
| # journal captured as artifacts so a failed boot is diagnosable, not just red. | |
| # Note this exercises kernel/initramfs/systemd bring-up, NOT the bootc | |
| # install-to-disk bootloader path; a disk-install test is a follow-up. | |
| on: | |
| # Fires after an aarch64 build completes (which itself runs after x86 publish | |
| # on testing). Conclusion is checked in the job: build-aarch64's job-level | |
| # continue-on-error means the workflow_run conclusion reads success even when | |
| # the build failed, so we verify the image tag exists instead. | |
| workflow_run: | |
| workflows: ["Build Bluefin dakota (aarch64)"] | |
| types: [completed] | |
| branches: [testing] | |
| workflow_dispatch: | |
| inputs: | |
| image: | |
| description: "Image ref to boot-test (empty = this repo owner's :aarch64 tag)" | |
| default: "" | |
| type: string | |
| permissions: | |
| contents: read | |
| packages: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| BCVK_VERSION: v0.18.0 | |
| jobs: | |
| boot-test: | |
| name: Boot aarch64 image in VM | |
| runs-on: ubuntu-24.04-arm | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Resolve image ref | |
| id: image | |
| env: | |
| DISPATCH_IMAGE: ${{ inputs.image }} | |
| run: | | |
| if [ -n "${DISPATCH_IMAGE:-}" ]; then | |
| echo "ref=${DISPATCH_IMAGE}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "ref=ghcr.io/${GITHUB_REPOSITORY_OWNER}/dakota:aarch64" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Verify image exists (build-aarch64 conclusions are unreliable) | |
| env: | |
| GH_ACTOR: ${{ github.actor }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| IMAGE: ${{ steps.image.outputs.ref }} | |
| run: | | |
| set -euo pipefail | |
| for attempt in $(seq 1 6); do | |
| if skopeo inspect --no-tags --creds "$GH_ACTOR:$GH_TOKEN" \ | |
| "docker://${IMAGE}" > /dev/null 2>&1; then | |
| echo "Image ${IMAGE} is readable (attempt ${attempt})" | |
| exit 0 | |
| fi | |
| echo "Waiting for ${IMAGE} (attempt ${attempt}/6)..." | |
| sleep 10 | |
| done | |
| echo "ERROR: ${IMAGE} not found — nothing to boot-test (did the aarch64 publish fail?)" | |
| exit 1 | |
| - name: Install virtualization stack | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| qemu-system-arm qemu-utils virtiofsd | |
| if [ -e /dev/kvm ]; then | |
| echo "KVM available: $(ls -l /dev/kvm)" | |
| else | |
| echo "WARNING: /dev/kvm absent — qemu will fall back to TCG (slow)" | |
| fi | |
| - name: Restore cached bcvk | |
| id: bcvk-cache | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: ~/.cargo/bin/bcvk | |
| key: bcvk-${{ env.BCVK_VERSION }}-aarch64 | |
| - name: Build bcvk (no aarch64 release binaries upstream) | |
| if: steps.bcvk-cache.outputs.cache-hit != 'true' | |
| run: | | |
| set -euo pipefail | |
| command -v cargo || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y | |
| source "$HOME/.cargo/env" 2>/dev/null || true | |
| cargo install --locked --git https://github.com/bootc-dev/bcvk --tag "${BCVK_VERSION}" bcvk | |
| - name: Pull image | |
| env: | |
| GH_ACTOR: ${{ github.actor }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| IMAGE: ${{ steps.image.outputs.ref }} | |
| run: | | |
| set -euo pipefail | |
| echo "$GH_TOKEN" | sudo podman login ghcr.io --username "$GH_ACTOR" --password-stdin | |
| sudo podman pull "${IMAGE}" | |
| - name: Boot and verify | |
| id: boot | |
| env: | |
| IMAGE: ${{ steps.image.outputs.ref }} | |
| SSH_TIMEOUT: "600" | |
| run: | | |
| set -uo pipefail | |
| # bcvk is KVM-only (no TCG mode). GitHub-hosted arm64 runners expose | |
| # no /dev/kvm (verified 2026-07-23) — skip rather than fail. This job | |
| # does real work on KVM-capable (self-hosted) runners. | |
| if [ ! -e /dev/kvm ]; then | |
| echo "SKIP: no /dev/kvm on this runner — bcvk requires KVM." | |
| exit 0 | |
| fi | |
| export PATH="$HOME/.cargo/bin:$PATH" | |
| VM_NAME="dakota-m0-boot" | |
| ART_DIR="${RUNNER_TEMP}/boot-artifacts" | |
| mkdir -p "$ART_DIR" | |
| echo "artifacts=${ART_DIR}" >> "$GITHUB_OUTPUT" | |
| cleanup() { | |
| sudo podman logs "$VM_NAME" > "${ART_DIR}/serial.log" 2>/dev/null || true | |
| sudo "$HOME/.cargo/bin/bcvk" ephemeral rm -f "$VM_NAME" 2>/dev/null || true | |
| } | |
| trap cleanup EXIT | |
| echo "==> Launching ephemeral VM from ${IMAGE}..." | |
| # No --rm: if the VM exits early (the exact failure we want to | |
| # diagnose), autoremoval would race away the container before | |
| # cleanup() captures serial.log. cleanup() does the rm -f. | |
| sudo "$HOME/.cargo/bin/bcvk" ephemeral run -d -K --console \ | |
| --memory 8192M --vcpus 4 --name "$VM_NAME" "${IMAGE}" | |
| echo "==> Waiting for SSH (timeout ${SSH_TIMEOUT}s)..." | |
| ELAPSED=0 | |
| while [ "$ELAPSED" -lt "$SSH_TIMEOUT" ]; do | |
| if sudo "$HOME/.cargo/bin/bcvk" ephemeral ssh "$VM_NAME" -- true 2>/dev/null; then | |
| break | |
| fi | |
| sleep 10; ELAPSED=$((ELAPSED + 10)) | |
| done | |
| if [ "$ELAPSED" -ge "$SSH_TIMEOUT" ]; then | |
| echo "FAIL: SSH not reachable within ${SSH_TIMEOUT}s — see serial.log artifact" | |
| exit 1 | |
| fi | |
| echo "==> SSH up after ~${ELAPSED}s" | |
| sudo "$HOME/.cargo/bin/bcvk" ephemeral ssh "$VM_NAME" -- \ | |
| journalctl -b --no-pager > "${ART_DIR}/journal.log" 2>/dev/null || true | |
| sudo "$HOME/.cargo/bin/bcvk" ephemeral ssh "$VM_NAME" -- \ | |
| 'uname -a; systemctl list-units --state=failed --no-legend' \ | |
| > "${ART_DIR}/summary.txt" 2>/dev/null || true | |
| # Core gate: the system must reach multi-user.target. | |
| if ! sudo "$HOME/.cargo/bin/bcvk" ephemeral ssh "$VM_NAME" -- \ | |
| systemctl is-active multi-user.target; then | |
| echo "FAIL: multi-user.target not active" | |
| exit 1 | |
| fi | |
| echo "PASS: multi-user.target active" | |
| # Informational (never gate M0 on the desktop stack): | |
| for unit in graphical.target gdm; do | |
| if sudo "$HOME/.cargo/bin/bcvk" ephemeral ssh "$VM_NAME" -- \ | |
| systemctl is-active "$unit" >/dev/null 2>&1; then | |
| echo " info: ${unit} active" | |
| else | |
| echo " info: ${unit} NOT active (not gating)" | |
| fi | |
| done | |
| sudo "$HOME/.cargo/bin/bcvk" ephemeral ssh "$VM_NAME" -- bootc status \ | |
| && echo " info: bootc status ok" || echo " info: bootc status failed (not gating)" | |
| exit 0 | |
| - name: Upload boot artifacts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: aarch64-boot-artifacts-${{ github.run_id }} | |
| path: ${{ steps.boot.outputs.artifacts }} | |
| retention-days: 14 | |
| if-no-files-found: ignore |