Skip to content

Build and Publish Dakota Live ISO #12

Build and Publish Dakota Live ISO

Build and Publish Dakota Live ISO #12

Workflow file for this run

name: Build and Publish Dakota Live ISO
on:
push:
branches: [main]
paths:
- 'dakota/**'
- 'justfile'
- '.github/workflows/build-iso.yml'
schedule:
# Rebuild daily at 03:00 UTC to pick up Dakota image updates
- cron: '0 3 * * *'
workflow_dispatch:
inputs:
skip_upload:
description: 'Build ISO but skip uploading to R2 (dry run)'
required: false
default: false
type: boolean
jobs:
build-and-publish:
name: Build Dakota Live ISO
runs-on: ubuntu-24.04
permissions:
contents: read
packages: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
tool-cache: true
- name: Mount BTRFS for podman storage
# Use 95% of /mnt for the loopback to maximise space for flatpak runtime
# downloads, container image layers, OCI export, and squashfs assembly.
# The podman build --mount=type=cache uses this filesystem, so any
# GH Actions cache pointed at /var/lib/containers would just waste space.
env:
BTRFS_LOOPBACK_FREE: "0.95"
run: bash .github/scripts/mount_btrfs.sh
- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y just podman rclone mtools xorriso
- name: Log in to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
podman login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build Live ISO
run: |
sudo just installer_channel=dev output_dir=output iso-sd-boot dakota
- name: Fix output directory ownership
# sudo just creates output/ as root; subsequent steps run as the runner
# user and cannot write into it without this ownership transfer.
run: sudo chown -R "$USER:$(id -gn)" output/
- name: Compute ISO name
id: iso
run: |
DATE=$(date -u +%Y%m%d)
SHA=$(echo "${{ github.sha }}" | cut -c1-7)
echo "dated=dakota-live-${DATE}-${SHA}.iso" >> "$GITHUB_OUTPUT"
echo "path=output/dakota-live.iso" >> "$GITHUB_OUTPUT"
- name: Generate SHA256 checksum
id: checksum
run: |
ISO="${{ steps.iso.outputs.path }}"
DATED="${{ steps.iso.outputs.dated }}"
CHECKSUM_PATH="output/${DATED}-CHECKSUM"
LATEST_CHECKSUM_PATH="output/dakota-live-latest.iso-CHECKSUM"
# Full sha256sum format (hash + filename) so users can run: sha256sum -c
# Two manifests: one with the dated filename, one with the "latest" alias.
# They share the same hash but different recorded filenames so both work.
sha256sum "$ISO" | sed "s|output/dakota-live.iso|${DATED}|" | tee "$CHECKSUM_PATH"
sha256sum "$ISO" | sed "s|output/dakota-live.iso|dakota-live-latest.iso|" > "$LATEST_CHECKSUM_PATH"
echo "path=${CHECKSUM_PATH}" >> "$GITHUB_OUTPUT"
echo "name=${DATED}-CHECKSUM" >> "$GITHUB_OUTPUT"
echo "latest_path=${LATEST_CHECKSUM_PATH}" >> "$GITHUB_OUTPUT"
- name: Upload ISO to Cloudflare R2
if: ${{ inputs.skip_upload != true && github.event_name != 'pull_request' }}
env:
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_REGION: auto
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.RCLONE_CONFIG_R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.RCLONE_CONFIG_R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.RCLONE_CONFIG_R2_ENDPOINT }}
run: |
ISO="${{ steps.iso.outputs.path }}"
DATED="${{ steps.iso.outputs.dated }}"
BUCKET="${{ secrets.R2_BUCKET }}"
echo "==> Uploading as $DATED ..."
rclone copyto --log-level INFO --checksum --s3-no-check-bucket \
"$ISO" "R2:testing/${DATED}"
echo "==> Updating dakota-live-latest.iso ..."
rclone copyto --log-level INFO --s3-no-check-bucket \
"$ISO" "R2:testing/dakota-live-latest.iso"
echo "==> Uploading checksum as ${{ steps.checksum.outputs.name }} ..."
rclone copyto --log-level INFO --s3-no-check-bucket \
"${{ steps.checksum.outputs.path }}" \
"R2:testing/${{ steps.checksum.outputs.name }}"
echo "==> Updating dakota-live-latest.iso-CHECKSUM ..."
rclone copyto --log-level INFO --s3-no-check-bucket \
"${{ steps.checksum.outputs.latest_path }}" \
"R2:testing/dakota-live-latest.iso-CHECKSUM"
echo "==> Done. Public URLs:"
echo " https://projectbluefin.dev/dakota-live-latest.iso"
echo " https://projectbluefin.dev/dakota-live-latest.iso-CHECKSUM"
- name: Boot verification (UEFI + serial)
timeout-minutes: 10
run: |
sudo apt-get install -y qemu-system-x86 ovmf socat imagemagick -qq
ISO="${{ steps.iso.outputs.path }}"
echo "==> Booting $ISO ..."
OVMF_CODE=""
for f in /usr/share/OVMF/OVMF_CODE_4M.fd \
/usr/share/OVMF/OVMF_CODE.fd \
/usr/share/edk2/ovmf/OVMF_CODE.fd; do
[ -f "$f" ] && OVMF_CODE="$f" && break
done
OVMF_VARS=""
for f in /usr/share/OVMF/OVMF_VARS_4M.fd \
/usr/share/OVMF/OVMF_VARS.fd \
/usr/share/edk2/ovmf/OVMF_VARS.fd; do
if [ -f "$f" ]; then
cp "$f" /tmp/OVMF_VARS.fd
OVMF_VARS="/tmp/OVMF_VARS.fd"
break
fi
done
PFLASH=()
if [ -n "$OVMF_CODE" ] && [ -n "$OVMF_VARS" ]; then
echo "==> UEFI firmware: $OVMF_CODE"
PFLASH+=(-drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE")
PFLASH+=(-drive "if=pflash,format=raw,file=$OVMF_VARS")
fi
sudo qemu-system-x86_64 \
-machine type=q35,accel=kvm \
-cpu host -m 4G -smp 2 \
-cdrom "$ISO" -boot d \
"${PFLASH[@]}" \
-monitor unix:/tmp/qemu-monitor.sock,server,nowait \
-serial file:/tmp/serial.log \
-display none \
-daemonize
# Wait for monitor socket
for i in $(seq 1 30); do
[ -S /tmp/qemu-monitor.sock ] && break
sleep 2
done
echo "==> Waiting up to 5 minutes for live environment to be ready..."
for i in $(seq 1 60); do
if sudo grep -q "DAKOTA_LIVE_READY" /tmp/serial.log 2>/dev/null; then
echo "==> Live environment ready after $((i * 5))s"
break
fi
[ "$i" -eq 60 ] && echo "WARNING: DAKOTA_LIVE_READY not seen in serial log after 5m" || true
sleep 5
done
# Capture screenshot
echo "screendump /tmp/screenshot.ppm" | \
sudo socat - UNIX-CONNECT:/tmp/qemu-monitor.sock || true
sleep 2
if sudo test -f /tmp/screenshot.ppm; then
sudo convert /tmp/screenshot.ppm screenshot.png 2>/dev/null || \
sudo cp /tmp/screenshot.ppm screenshot.png
echo "==> Screenshot saved"
fi
echo "quit" | sudo socat - UNIX-CONNECT:/tmp/qemu-monitor.sock || true
# Fail the job if the live environment never reached the ready state
sudo grep -q "DAKOTA_LIVE_READY" /tmp/serial.log || \
{ echo "ERROR: Live environment did not reach ready state"; sudo tail -50 /tmp/serial.log; exit 1; }
- name: Upload ISO as artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.iso.outputs.dated }}
path: output/dakota-live.iso
if-no-files-found: error
retention-days: 7
- name: Upload SHA256 checksum as artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: checksum-${{ steps.iso.outputs.dated }}
path: ${{ steps.checksum.outputs.path }}
if-no-files-found: warn
retention-days: 7
- name: Upload boot screenshot as artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: boot-screenshot
path: screenshot.png
if-no-files-found: warn