Skip to content

Boot Test dakota (aarch64) #55

Boot Test dakota (aarch64)

Boot Test dakota (aarch64) #55

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