Commit graph

24 commits

Author SHA1 Message Date
Mitchell R
3575f1169b
feat(os-ota): A/B image repartition + bigger Blacksmith binary runners
Phase 2a of OS OTA: post-process pi-gen output into a RAUC-compatible
A/B layout. New deploy/rauc/repartition-image.sh:

- Decompresses the stock pi-gen 2-partition image
- Extracts bootfs (vfat) + rootfs (ext4) blobs
- Compacts rootfs with resize2fs -M and grows back with 25% headroom
- Patches /etc/fstab inside rootfs to use LABEL=BF_BOOT_A /
  LABEL=BF_ROOT_A / LABEL=BF_DATA (slot-agnostic; RAUC re-labels per
  slot on install)
- Stamps /etc/betterframe/{os-version,os-compatibility} for the kiosk's
  os_update.rs to read at runtime
- Builds two bootfs copies, each with cmdline.txt root= rewritten to
  the matching ROOT slot
- Lays out 6 GPT partitions: BF_BOOTSEL (autoboot.txt with tryboot
  pointing at boot_partition=2 / [tryboot] boot_partition=3), BF_BOOT_A,
  BF_BOOT_B, BF_ROOT_A (populated), BF_ROOT_B (empty, RAUC fills on
  first install), BF_DATA
- Recompresses with xz -T0

build-bundle.sh now takes the already-extracted slot images so the
.raucb bundle re-uses the exact same blobs that ship inside the A/B
initial-flash image — no duplication, no drift.

CI wires the repartition step between pi-gen output and the GitHub
Release upload. Ships the A/B image (not the stock pi-gen one).

Also: bump Blacksmith binary builders from 2/4 vCPU to 8 vCPU each.
Image job stays on GitHub's ubuntu-24.04-arm (Blacksmith arm kernel
6.5 doesn't ship binfmt_misc as a loadable module, which pi-gen-action's
defensive modprobe step still requires).

What's still pending:
  - In-image RAUC install (rauc package + drop system.conf + CA cert
    at /etc/rauc/keyring.pem). Without this, the image boots A/B-laid-
    out but rauc install commands have no daemon to talk to.
  - Admin UI for OS releases + rollouts (task #4).
2026-05-21 10:57:00 +02:00
Mitchell R
084c119c44
feat(os-ota): build + sign + auto-import .raucb bundles in CI
Phase 1 of the OS OTA pipeline. Three pieces:

scripts/gen-rauc-signing-keys.sh — one-shot helper that issues an
Ed25519 X.509 CA + signing cert pair. Operator runs locally, commits
the CA cert (for embedding in kiosk image at /etc/rauc/keyring.pem),
stores the signing pair as GitHub Actions secrets
(BF_RAUC_SIGNING_CERT + BF_RAUC_SIGNING_KEY), keeps the CA private
key offline. RAUC verifies bundles against the keyring in the image.

deploy/rauc/build-bundle.sh — takes the pi-gen .img.xz, parses its
partition table with sfdisk, dd-extracts bootfs (vfat) + rootfs
(ext4) into a staging dir, renders manifest.raucm.in with version
+ git sha, runs `rauc bundle --cert= --key=` to produce a signed
.raucb. Verifies the bundle round-trips with `rauc info`.

build.yml gains two gated steps:
  - "Build RAUC bundle": runs only when both signing secrets are set,
    uploads .raucb as a release asset alongside the .img.xz.
  - "Auto-import OS bundle into BF server": POSTs the GH release asset
    URL to ${BF_AUTOIMPORT_URL}/api/admin/os/import so the server
    pulls + stores the bundle. Mirrors the kiosk-binary auto-import
    flow that already worked.

Compatibility string is `betterframe-rpi5-aarch64` (matches the value
already declared in deploy/rauc/system.conf). Channel passed through
from inputs (dev for master pushes, stable/beta for tags).

What's NOT in this commit:
  - Pi image A/B partition layout (custom genimage / pi-gen patch)
  - rauc package install + keyring drop in pi-gen stage
  - Kiosk-side os_update.rs Rust consumer that polls /api/kiosk/os/check
  - Admin UI for releases + rollouts

A bundle built today reaches /api/admin/os/import on the server but
isn't installable yet — kiosks have no consumer and no A/B layout.
That's the next 3 phases. Bundle production needs to be solid first
so the kiosk side can be tested against real artifacts.
2026-05-21 10:44:24 +02:00
Mitchell R
3d5e27bdfb
fix(release): surface build versions 2026-05-21 08:51:41 +02:00
Mitchell R
6995990aca
fix(ci): avoid auto-import argv limit 2026-05-20 05:21:16 +02:00
Mitchell R
08568e52fa
feat(kiosk): harden field image defaults 2026-05-20 05:18:18 +02:00
Mitchell R
87cde93316
feat(ota): add RAUC OS update foundation 2026-05-20 05:15:29 +02:00
Mitchell R
dae5d0ce88
feat(managed-config): server-side scaffold for Pi-image device config
Kiosks running our pre-built image (managed_image=true at pairing) can
have their hostname, timezone, network (DHCP/static + VLAN), and Wi-Fi
configured from the admin UI. Pull-model: server stores desired-state
JSON, kiosk heartbeat returns pending_config when version exceeds
applied_version, kiosk echoes applied_version back. Wi-Fi PSK encrypted
with the cluster key so ciphertext at rest is shipped to the kiosk
without per-kiosk re-encryption.

Server side only — kiosk Rust applier (betterframe-apply-config helper
+ rollback timer) and pair-initiate marker file are next.

ci(pi-gen): use action's image-path output for asset upload

pi-gen writes the .img.xz into pi-gen-action's own working dir, not our
repo deploy/. Glob never matched. Use steps.pigen.outputs.image-path
directly — no glob needed.
2026-05-20 03:18:11 +02:00
Mitchell R
04d40adb93
ci(pi-gen): stage files into chroot via host-side 00-run.sh
Pi-gen doesn't auto-copy a sub-stage's files/ dir into the chroot. The
chroot script's install commands were reaching for /tmp/bf-files/... which
never existed. Add a host-side 00-run.sh that bulk-copies files/* into
ROOTFS_DIR/tmp/bf-files, then rename the chroot script to 01-run-chroot.sh
so it sorts AFTER the host copy ('-' < '.' bites you otherwise).
2026-05-20 02:31:35 +02:00
Mitchell R
771b94d387
ci(pi-gen): swap to GitHub ubuntu-24.04-arm runner + pi-gen-action v1.11.0
Blacksmith arm64 runner kernel 6.5 ships binfmt_misc built-in (not modular),
so pi-gen-action@v1's modprobe step errors. GitHub's ubuntu-24.04-arm has
modular binfmt_misc and is the configuration #179 confirms works.
2026-05-20 01:59:40 +02:00
Mitchell R
e19dedfe17
ci(pi-gen): native arm64 runner skips QEMU entirely
usimd/pi-gen-action#179: Trixie + QEMU breaks on x86 runners (arch-test
"arm64: not supported"). Native arm64 runner means no qemu, no binfmt
registration dance — pi-gen runs the chroot directly. Faster too.
2026-05-20 01:42:30 +02:00
Mitchell R
5e1f8f80bc
ci(pi-gen): apt qemu-user-static instead of tonistiigi (chroot needs static binary)
tonistiigi/binfmt registers /usr/bin/qemu-aarch64 (dynamic). Even with F-flag
preload, qemu still dlopen's its libs at exec time — fails inside pi-gen's
chroot. Debian's qemu-user-static ships /usr/bin/qemu-aarch64-static and
post-install sets F flag automatically. Pi-gen's dependencies_check needs
the static path.
2026-05-20 01:32:05 +02:00
Mitchell R
39fac39bf2
ci(pi-gen): verbose-output: true to surface pi-gen stdout/stderr
Pi-gen container exits in 0.288s after image build with no logs printed.
Default action input verbose-output=false suppresses pi-gen output;
flipping to true should show what build.sh trips on inside the container.
2026-05-20 01:13:19 +02:00
Mitchell R
d608609ec4
ci(pi-gen): drop extra-host-dependencies (caused sudo apt exit 100)
Log diagnosis on run 26130391965:
  ##[error]The process '/usr/bin/sudo' failed with exit code 100

Failure was inside the action's 'Installing build dependencies on host'
step. extra-host-dependencies: qemu-user-static binfmt-support broke
apt — possibly conflicting locks or the action's input handling.

tonistiigi/binfmt --install arm64 already registered qemu-aarch64 with
'flags: POCF' (F = kernel-resident static binary). That's enough; no
need for the inside-container qemu packages.
2026-05-20 01:05:49 +02:00
Mitchell R
d342a3779f
ci(pi-gen): bring back extra-host-dependencies + binfmt diagnostic 2026-05-20 00:58:02 +02:00
Mitchell R
73a01a3d50
ci(pi-gen): revert to minimal known-valid inputs (drop unverified action keys) 2026-05-20 00:51:37 +02:00
Mitchell R
97c3e78141
ci(pi-gen): swap verbose-output → enable-debug + export-last-stage-only 2026-05-20 00:45:25 +02:00
Mitchell R
c60f2a8b9e
ci(pi-gen): extra-host-dependencies installs qemu inside pi-gen container
Host-side tonistiigi/binfmt registration doesn't propagate into the
pi-gen-action's nested Docker container's view of /proc/sys/fs/binfmt_misc.
usimd/pi-gen-action's extra-host-dependencies input runs apt-get inside
the pi-gen container before pi-gen launches — install qemu-user-static
+ binfmt-support there so the chroot's arm64 binaries can execute.
2026-05-20 00:39:51 +02:00
Mitchell R
69e4bcb14a
ci(pi-gen): tonistiigi/binfmt --install arm64 (F flag, kernel-resident QEMU)
apt's qemu-user-static + update-binfmts produces a registration that
pi-gen's nested Docker container still couldn't see. Switch to the
canonical tonistiigi/binfmt approach: privileged container that
installs QEMU statically with the F (fix-binary) flag, so the kernel
opens the qemu-aarch64-static binary at registration time and uses it
for all subsequent arm64 execs — independent of which container the
exec happens in.

Plus diagnostic: ls /proc/sys/fs/binfmt_misc + cat qemu-aarch64
detail, so next run's log surfaces whether registration actually
landed.
2026-05-20 00:31:42 +02:00
Mitchell R
ab955e12da
ci(pi-gen): install qemu-user-static via apt instead of setup-qemu-action
docker/setup-qemu-action registers binfmt via a privileged side container;
pi-gen-action's own nested Docker container doesn't inherit the
registration. Result: arm64 ELFs in the pi-gen chroot still fail to
exec, exit 1 before any stage runs.

apt-installed qemu-user-static + binfmt-support writes persistent
binfmt_misc entries to the kernel that propagate to every container
share. Pair with update-binfmts --enable qemu-aarch64 and a sanity
ls -la /proc/sys/fs/binfmt_misc/qemu-aarch64.
2026-05-20 00:23:16 +02:00
Mitchell R
3746f685be
ci: bump action versions to latest + add QEMU arm64 binfmt for pi-gen
Real cause of last pi-gen failure was surfaced by verbose-output:
  WARNING: Only a native build environment is supported.
  arm64: not supported on this machine/kernel

ubuntu-latest is x86_64; pi-gen builds an arm64 image and chroots into
it during stages, requiring binfmt_misc handlers for arm64. Add
docker/setup-qemu-action before the pi-gen step.

While here, audit + bump every action version (pinned to current
majors):
  actions/checkout            v4 → v6
  actions/upload-artifact     v4 → v7
  actions/download-artifact   v4 → v8
  softprops/action-gh-release v2 → v3
  docker/setup-qemu-action    @v4 (new)
  usimd/pi-gen-action         @v1 (already current major)
  dtolnay/rust-toolchain      @stable (rolling channel — keep)
2026-05-20 00:11:45 +02:00
Mitchell R
0f664fe1c1
ci(pi-gen): verbose pi-gen output + IMG_SUFFIX in EXPORT_IMAGE for diagnostics 2026-05-19 23:57:26 +02:00
Mitchell R
b7ec18e52e
ci(pi-gen): trixie everywhere + missing prerun.sh + EXPORT_IMAGE marker
Reverts misdiagnosis. pi-gen defaults to trixie since the Debian 13
release, which has gtk4 4.14 + libwebkitgtk-6.0 stock — no backports
needed. Build container, kiosk gtk feature gate, and pi-gen target all
realigned to trixie.

Actual reason last image run failed: our custom stage was missing the
mandatory prerun.sh (pi-gen calls it to seed ROOTFS_DIR from the
previous stage) and the EXPORT_IMAGE marker file (signals 'bake an
image at the end of this stage'). Both added.

Asset upload now globs deploy/*.img.xz so any extra exports stage2
produces ship alongside our customised one.
2026-05-19 05:19:32 +02:00
Mitchell R
3f20d03520
ci: block-style with: in build.yml checkout steps (flow-style + ${{ }} parser conflict) 2026-05-19 05:04:20 +02:00
Mitchell R
8f457c5ca9
ci: single reusable build.yml + release.yml orchestrator (auto-tag on master)
Replaces release-kiosk.yml + release-image.yml with two coupled workflows:

  release.yml — entrypoint. Computes version/channel/tag:
    - master push → semver patch bump from latest stable tag, append
      -dev.<shortsha>, create lightweight tag + prerelease record
    - v* tag push → use tag verbatim, channel from suffix (-beta./-dev. or
      stable), create release if missing
    Then invokes build.yml via uses: ./.github/workflows/build.yml.

  build.yml — reusable (workflow_call). Single source of truth for asset
    production:
    - kiosk binary matrix (aarch64, x86_64) in debian:trixie-slim
    - flashable .img.xz via pi-gen using the aarch64 artifact (gated by
      build-image input; master pushes default false to keep dev cycles
      fast, tag pushes default true for a full release)
    Both jobs attach to the release at tag_name=${{ inputs.tag }}.

Concurrency: master-branch runs cancel superseded peers; tag runs never
cancel. CI auto-import to a running BF server (BF_AUTOIMPORT_URL +
BF_AUTOIMPORT_API_KEY repo secrets) still wired.
2026-05-19 04:58:23 +02:00