# Reusable build workflow. Invoked by release.yml on every master push (dev # release) and every v* tag push (stable / beta). Produces: # - betterframe-kiosk-- (Linux ELF, no extension) # - betterframe-client--.img.xz (flashable Pi OS image) # All assets attached to the GitHub Release at ${{ inputs.tag }}. name: build on: workflow_call: inputs: version: description: "Semver without leading v (0.4.2 or 0.4.2-dev.abcdef0)" required: true type: string channel: description: "stable | beta | dev" required: true type: string tag: description: "Git tag (with leading v)" required: true type: string ref: description: "Git ref to check out (commit sha or tag name)" required: true type: string build-image: description: "Also build the flashable .img.xz (slow; skip for fast dev builds)" required: false type: boolean default: true secrets: BF_AUTOIMPORT_URL: required: false BF_AUTOIMPORT_API_KEY: required: false # RAUC bundle signing — generated once via scripts/gen-rauc-signing-keys.sh # and uploaded to the GH repo secrets. Without both, the OS-bundle step # is skipped; .img.xz still ships as a release asset for manual flashing. BF_RAUC_SIGNING_CERT: required: false BF_RAUC_SIGNING_KEY: required: false permissions: contents: write jobs: # ---- Per-arch kiosk binary ------------------------------------------------ binary: strategy: fail-fast: false matrix: include: - target: aarch64-unknown-linux-gnu runs-on: blacksmith-2vcpu-ubuntu-2404-arm - target: x86_64-unknown-linux-gnu runs-on: blacksmith-4vcpu-ubuntu-2404 runs-on: ${{ matrix.runs-on }} # Trixie container matches Pi OS Trixie's glibc + apt packages. container: image: debian:trixie-slim steps: - name: Bootstrap apt + git (container has none preinstalled) run: | apt-get update apt-get install -y --no-install-recommends \ ca-certificates curl git build-essential pkg-config jq sudo git config --global --add safe.directory '*' - uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} - name: Install GTK/GStreamer/WebKit build deps run: | apt-get install -y --no-install-recommends \ libgtk-4-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ libwebkitgtk-6.0-dev libssl-dev - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: cargo build --release working-directory: kiosk env: BF_BUILD_ARCH: ${{ matrix.target }} BF_BUILD_VERSION: ${{ inputs.version }} run: cargo build --release --target ${{ matrix.target }} - name: Strip + rename working-directory: kiosk run: | strip target/${{ matrix.target }}/release/betterframe-kiosk cp target/${{ matrix.target }}/release/betterframe-kiosk \ betterframe-kiosk-${{ inputs.version }}-${{ matrix.target }} - name: Upload to GitHub Release (binary) uses: softprops/action-gh-release@v3 with: tag_name: ${{ inputs.tag }} files: kiosk/betterframe-kiosk-${{ inputs.version }}-${{ matrix.target }} - name: Auto-import into BF server if: env.BF_AUTOIMPORT_URL != '' && env.BF_AUTOIMPORT_API_KEY != '' env: BF_AUTOIMPORT_URL: ${{ secrets.BF_AUTOIMPORT_URL }} BF_AUTOIMPORT_API_KEY: ${{ secrets.BF_AUTOIMPORT_API_KEY }} working-directory: kiosk run: | bin="betterframe-kiosk-${{ inputs.version }}-${{ matrix.target }}" base64 -w 0 "$bin" > "${bin}.b64" jq -nc \ --arg v "${{ inputs.version }}" \ --arg c "${{ inputs.channel }}" \ --arg a "${{ matrix.target }}" \ --arg n "GitHub Actions build of ${{ inputs.tag }} (${{ github.sha }})" \ --rawfile b "${bin}.b64" \ '{version:$v, channel:$c, arch:$a, release_notes:$n, content_b64:$b}' \ > "${bin}.import.json" curl -sSf -X POST \ -H "Authorization: Bearer ${BF_AUTOIMPORT_API_KEY}" \ -H "Content-Type: application/json" \ --data-binary @"${bin}.import.json" \ "${BF_AUTOIMPORT_URL}/api/admin/firmware/import" - name: Upload artifact (always) uses: actions/upload-artifact@v7 with: name: betterframe-kiosk-${{ matrix.target }} path: kiosk/betterframe-kiosk-${{ inputs.version }}-${{ matrix.target }} retention-days: 14 # ---- Flashable Pi OS Trixie image (aarch64 only) ------------------------- # Native arm64 runner avoids QEMU entirely — pi-gen runs faster + sidesteps # the Trixie binfmt regression (usimd/pi-gen-action#179). With native arm64 # the chroot's binaries match the kernel, so arch-test passes immediately. image: if: ${{ inputs.build-image }} needs: binary # GitHub's official native-arm64 runner. Blacksmith's arm64 kernel (6.5) # doesn't ship binfmt_misc as a loadable module so pi-gen-action@v1's # `modprobe binfmt_misc` errors out even though we don't need it on a # native runner. ubuntu-24.04-arm has a kernel where it's modular and # modprobe-able. Issue #179 confirms this is the intended path. runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} - name: Pull kiosk aarch64 binary from this run's artifact uses: actions/download-artifact@v8 with: name: betterframe-kiosk-aarch64-unknown-linux-gnu path: staging/ - name: Render BF logo for plymouth run: | sudo apt-get -y update sudo apt-get -y install --no-install-recommends librsvg2-bin mv staging/betterframe-kiosk-${{ inputs.version }}-aarch64-unknown-linux-gnu \ staging/betterframe-kiosk chmod +x staging/betterframe-kiosk rsvg-convert -w 480 server/src/web-static/betterframe-logo.svg -o staging/logo.png - name: Stage files for pi-gen run: | mkdir -p deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files cp staging/betterframe-kiosk \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp staging/logo.png \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/systemd/betterframe-kiosk.service \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/systemd/betterframe-firmware-rollback.sh \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/systemd/betterframe-rauc-mark-good.service \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/systemd/betterframe-rauc-mark-good.sh \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/tmpfiles/betterframe-kiosk.conf \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/udev/90-betterframe-no-hid.rules \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/pam.d/cage \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/cage.pam cp deploy/plymouth/betterframe/betterframe.plymouth \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ cp deploy/plymouth/betterframe/betterframe.script \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/ chmod +x deploy/pi-gen/stage-betterframe-client/01-install-kiosk/00-run.sh \ deploy/pi-gen/stage-betterframe-client/01-install-kiosk/01-run-chroot.sh \ deploy/pi-gen/stage-betterframe-client/prerun.sh - name: Build Pi image (pi-gen) id: pigen uses: usimd/pi-gen-action@v1.11.0 with: image-name: betterframe-client-${{ inputs.version }} stage-list: stage0 stage1 stage2 ./deploy/pi-gen/stage-betterframe-client # pi-gen default release is trixie (Debian 13). enable-ssh: 0 username: bfadmin password: betterframe locale: en_US.UTF-8 timezone: Etc/UTC hostname: betterframe-kiosk compression: xz # Surface pi-gen's stdout/stderr (default suppressed). verbose-output: true - name: Show pi-gen output path run: | echo "image-path: ${{ steps.pigen.outputs.image-path }}" ls -la "$(dirname '${{ steps.pigen.outputs.image-path }}')" || true # pi-gen writes the .img.xz under its own checkout (inside pi-gen-action's # working dir), not our repo deploy/. The action exposes the exact path # via the `image-path` output — use it directly instead of globbing. - name: Upload image to GitHub Release uses: softprops/action-gh-release@v3 with: tag_name: ${{ inputs.tag }} files: ${{ steps.pigen.outputs.image-path }} # ---- RAUC bundle (OS OTA) -------------------------------------------- # Build a signed .raucb bundle from the same partitions baked into the # .img.xz. Kiosks fetch this from /api/kiosk/os/check + rauc install it # into the inactive A/B slot. Skipped when signing secrets aren't set # (image still ships for manual flashing). - name: Build RAUC bundle id: raucb if: ${{ secrets.BF_RAUC_SIGNING_CERT != '' && secrets.BF_RAUC_SIGNING_KEY != '' }} env: BF_RAUC_SIGNING_CERT: ${{ secrets.BF_RAUC_SIGNING_CERT }} BF_RAUC_SIGNING_KEY: ${{ secrets.BF_RAUC_SIGNING_KEY }} run: | set -e sudo apt-get update sudo apt-get install -y --no-install-recommends rauc e2fsprogs xz-utils util-linux openssl mkdir -p /tmp/rauc-signing chmod 700 /tmp/rauc-signing printf '%s\n' "$BF_RAUC_SIGNING_CERT" > /tmp/rauc-signing/cert.pem printf '%s\n' "$BF_RAUC_SIGNING_KEY" > /tmp/rauc-signing/key.pem chmod 600 /tmp/rauc-signing/key.pem chmod +x deploy/rauc/build-bundle.sh out="${{ github.workspace }}/betterframe-${{ inputs.version }}.raucb" deploy/rauc/build-bundle.sh \ "${{ steps.pigen.outputs.image-path }}" \ "$out" \ "${{ inputs.version }}" \ "${{ github.sha }}" \ /tmp/rauc-signing/cert.pem \ /tmp/rauc-signing/key.pem rm -rf /tmp/rauc-signing echo "bundle-path=$out" >> "$GITHUB_OUTPUT" echo "bundle-sha256=$(sha256sum "$out" | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - name: Upload RAUC bundle to GitHub Release if: ${{ steps.raucb.outputs.bundle-path != '' }} uses: softprops/action-gh-release@v3 with: tag_name: ${{ inputs.tag }} files: ${{ steps.raucb.outputs.bundle-path }} # Auto-import to BF server. Mirrors the kiosk-binary auto-import step. # Skipped if BF_AUTOIMPORT_* secrets are missing OR no bundle was built. - name: Auto-import OS bundle into BF server if: ${{ steps.raucb.outputs.bundle-path != '' && secrets.BF_AUTOIMPORT_URL != '' && secrets.BF_AUTOIMPORT_API_KEY != '' }} env: BF_AUTOIMPORT_URL: ${{ secrets.BF_AUTOIMPORT_URL }} BF_AUTOIMPORT_API_KEY: ${{ secrets.BF_AUTOIMPORT_API_KEY }} run: | set -e tag="${{ inputs.tag }}" repo="${{ github.repository }}" # Direct GH release asset URL — server downloads from here. asset_name="$(basename "${{ steps.raucb.outputs.bundle-path }}")" asset_url="https://github.com/${repo}/releases/download/${tag}/${asset_name}" payload="$(jq -nc \ --arg v "${{ inputs.version }}" \ --arg c "${{ inputs.channel }}" \ --arg compat "betterframe-rpi5-aarch64" \ --arg sha "${{ steps.raucb.outputs.bundle-sha256 }}" \ --arg url "$asset_url" \ --arg n "GitHub Actions build of ${{ inputs.tag }} (${{ github.sha }})" \ '{version:$v, channel:$c, compatibility:$compat, source_url:$url, sha256:$sha, release_notes:$n}')" curl -sSf -X POST \ -H "Authorization: Bearer ${BF_AUTOIMPORT_API_KEY}" \ -H "Content-Type: application/json" \ -d "$payload" \ "${BF_AUTOIMPORT_URL}/api/admin/os/import"