mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 17:56:34 +00:00
- Add betterframe-expand-data systemd service: growpart + resize2fs on BF_DATA (last partition) so it fills the full SD card on first boot. Solves the "No space left on device" issue with OS update downloads. - Change OS update staging dir from /var/tmp/betterframe to /var/lib/betterframe/tmp (on BF_DATA partition, not rootfs). - Wire firmware and OS update progress callbacks into the GTK overlay banner — shows "OS Update v1.2.3: Downloading — 45%" etc. - Add per-partition disk reporting in heartbeat (/, /boot/firmware, /var/lib/betterframe) with total/used/free/percent. - Display partition table on kiosk detail page in admin UI. - PG + SQLite migrations for partitions_json column on kiosks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
375 lines
16 KiB
YAML
375 lines
16 KiB
YAML
# Reusable build workflow. Invoked by release.yml on every master push (dev
|
|
# release) and every v* tag push (stable / beta). Produces:
|
|
# - betterframe-kiosk-<version>-<arch> (Linux ELF, no extension)
|
|
# - betterframe-client-<version>-<arch>.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
|
|
BF_AXIOM_KEY:
|
|
required: false
|
|
BF_AXIOM_DATASET:
|
|
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-8vcpu-ubuntu-2404-arm
|
|
- target: x86_64-unknown-linux-gnu
|
|
runs-on: blacksmith-8vcpu-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 }}
|
|
BF_AXIOM_KEY: ${{ secrets.BF_AXIOM_KEY }}
|
|
BF_AXIOM_DATASET: ${{ secrets.BF_AXIOM_DATASET }}
|
|
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"
|
|
for attempt in 1 2 3; do
|
|
if curl -sSf --retry 2 --retry-delay 5 -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"; then
|
|
break
|
|
fi
|
|
echo "Import attempt $attempt failed, retrying in 10s..."
|
|
sleep 10
|
|
done
|
|
|
|
- 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
|
|
env:
|
|
HAS_RAUC_SECRETS: ${{ secrets.BF_RAUC_SIGNING_CERT != '' && secrets.BF_RAUC_SIGNING_KEY != '' }}
|
|
HAS_AUTOIMPORT: ${{ secrets.BF_AUTOIMPORT_URL != '' && secrets.BF_AUTOIMPORT_API_KEY != '' }}
|
|
# 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/
|
|
# Transparent cursor theme
|
|
cp deploy/cursor-theme/betterframe-empty/cursor.theme \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/cursor.theme
|
|
# RAUC config + custom bootloader backend
|
|
cp deploy/rauc/system.conf \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/rauc-system.conf
|
|
cp deploy/rauc/betterframe-rauc-boot.sh \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/
|
|
# Firewall ruleset
|
|
cp deploy/nftables/nftables.conf \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/nftables.conf
|
|
# First-boot password rotation
|
|
cp deploy/systemd/betterframe-firstboot.service \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/
|
|
cp deploy/systemd/betterframe-firstboot.sh \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/
|
|
cp deploy/systemd/betterframe-expand-data.service \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/
|
|
cp deploy/systemd/betterframe-expand-data.sh \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/
|
|
# CA cert is operator-supplied — generated locally via
|
|
# scripts/gen-rauc-signing-keys.sh and committed at
|
|
# deploy/rauc/ca-cert.pem. Without it the image installs but
|
|
# rauc refuses every bundle (verify-only mode).
|
|
if [ -f deploy/rauc/ca-cert.pem ]; then
|
|
cp deploy/rauc/ca-cert.pem \
|
|
deploy/pi-gen/stage-betterframe-client/01-install-kiosk/files/rauc-keyring.pem
|
|
else
|
|
echo "::warning::deploy/rauc/ca-cert.pem missing — OS OTA disabled in this image"
|
|
fi
|
|
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
|
|
enable-ssh: 0
|
|
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
|
|
|
|
# ---- A/B repartition + slot extraction --------------------------------
|
|
# Post-process the stock pi-gen .img.xz into an A/B-ready RAUC image
|
|
# AND emit the rootfs.ext4 + bootfs.vfat slot blobs that the .raucb
|
|
# bundle re-uses. Keeps pi-gen vanilla; all RAUC awareness lives here.
|
|
- name: Repartition image to A/B layout
|
|
id: repartition
|
|
env:
|
|
BF_BUILD_VERSION: ${{ inputs.version }}
|
|
BF_RAUC_COMPATIBILITY: betterframe-rpi5-aarch64
|
|
run: |
|
|
set -e
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
xz-utils util-linux e2fsprogs dosfstools gdisk jq
|
|
chmod +x deploy/rauc/repartition-image.sh
|
|
ws="${{ github.workspace }}"
|
|
out_img="${ws}/betterframe-client-${{ inputs.version }}.img.xz"
|
|
rootfs="${ws}/rootfs.ext4"
|
|
bootfs="${ws}/bootfs.vfat"
|
|
sudo BF_BUILD_VERSION="$BF_BUILD_VERSION" \
|
|
BF_RAUC_COMPATIBILITY="$BF_RAUC_COMPATIBILITY" \
|
|
deploy/rauc/repartition-image.sh \
|
|
"${{ steps.pigen.outputs.image-path }}" \
|
|
"$out_img" \
|
|
"$rootfs" \
|
|
"$bootfs"
|
|
sudo chown "$USER:" "$out_img" "$rootfs" "$bootfs"
|
|
echo "ab-image-path=$out_img" >> "$GITHUB_OUTPUT"
|
|
echo "rootfs-path=$rootfs" >> "$GITHUB_OUTPUT"
|
|
echo "bootfs-path=$bootfs" >> "$GITHUB_OUTPUT"
|
|
|
|
# Ship the A/B image (not the original stock one). The original is
|
|
# discarded — only useful if you can't run repartition for some reason.
|
|
- name: Upload A/B image to GitHub Release
|
|
uses: softprops/action-gh-release@v3
|
|
with:
|
|
tag_name: ${{ inputs.tag }}
|
|
files: ${{ steps.repartition.outputs.ab-image-path }}
|
|
|
|
# ---- RAUC bundle (OS OTA) --------------------------------------------
|
|
# Build a signed .raucb bundle from the SAME slot images embedded in
|
|
# the A/B initial-flash image. Kiosks fetch this from
|
|
# /api/kiosk/os/check + rauc install it into the inactive slot.
|
|
# Skipped when signing secrets aren't set.
|
|
- name: Build RAUC bundle
|
|
id: raucb
|
|
if: ${{ env.HAS_RAUC_SECRETS == 'true' }}
|
|
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 install -y --no-install-recommends rauc 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.repartition.outputs.rootfs-path }}" \
|
|
"${{ steps.repartition.outputs.bootfs-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 != '' && env.HAS_AUTOIMPORT == 'true' }}
|
|
env:
|
|
BF_AUTOIMPORT_URL: ${{ secrets.BF_AUTOIMPORT_URL }}
|
|
BF_AUTOIMPORT_API_KEY: ${{ secrets.BF_AUTOIMPORT_API_KEY }}
|
|
run: |
|
|
set -e
|
|
# Wait for GitHub CDN to propagate the release asset.
|
|
sleep 15
|
|
tag="${{ inputs.tag }}"
|
|
repo="${{ github.repository }}"
|
|
asset_name="$(basename "${{ steps.raucb.outputs.bundle-path }}")"
|
|
asset_url="https://github.com/${repo}/releases/download/${tag}/${asset_name}"
|
|
# Verify asset is reachable before telling the server to download it.
|
|
for i in 1 2 3; do
|
|
if curl -sSf -o /dev/null -I "$asset_url" 2>/dev/null; then break; fi
|
|
echo "Asset not ready yet, retry $i..."
|
|
sleep 10
|
|
done
|
|
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"
|