mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 22:26:33 +00:00
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).
This commit is contained in:
parent
659670b494
commit
3575f1169b
3 changed files with 234 additions and 89 deletions
58
.github/workflows/build.yml
vendored
58
.github/workflows/build.yml
vendored
|
|
@ -54,9 +54,9 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
runs-on: blacksmith-2vcpu-ubuntu-2404-arm
|
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
# Trixie container matches Pi OS Trixie's glibc + apt packages.
|
# Trixie container matches Pi OS Trixie's glibc + apt packages.
|
||||||
container:
|
container:
|
||||||
|
|
@ -218,20 +218,50 @@ jobs:
|
||||||
echo "image-path: ${{ steps.pigen.outputs.image-path }}"
|
echo "image-path: ${{ steps.pigen.outputs.image-path }}"
|
||||||
ls -la "$(dirname '${{ steps.pigen.outputs.image-path }}')" || true
|
ls -la "$(dirname '${{ steps.pigen.outputs.image-path }}')" || true
|
||||||
|
|
||||||
# pi-gen writes the .img.xz under its own checkout (inside pi-gen-action's
|
# ---- A/B repartition + slot extraction --------------------------------
|
||||||
# working dir), not our repo deploy/. The action exposes the exact path
|
# Post-process the stock pi-gen .img.xz into an A/B-ready RAUC image
|
||||||
# via the `image-path` output — use it directly instead of globbing.
|
# AND emit the rootfs.ext4 + bootfs.vfat slot blobs that the .raucb
|
||||||
- name: Upload image to GitHub Release
|
# 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
|
||||||
|
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
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ inputs.tag }}
|
tag_name: ${{ inputs.tag }}
|
||||||
files: ${{ steps.pigen.outputs.image-path }}
|
files: ${{ steps.repartition.outputs.ab-image-path }}
|
||||||
|
|
||||||
# ---- RAUC bundle (OS OTA) --------------------------------------------
|
# ---- RAUC bundle (OS OTA) --------------------------------------------
|
||||||
# Build a signed .raucb bundle from the same partitions baked into the
|
# Build a signed .raucb bundle from the SAME slot images embedded in
|
||||||
# .img.xz. Kiosks fetch this from /api/kiosk/os/check + rauc install it
|
# the A/B initial-flash image. Kiosks fetch this from
|
||||||
# into the inactive A/B slot. Skipped when signing secrets aren't set
|
# /api/kiosk/os/check + rauc install it into the inactive slot.
|
||||||
# (image still ships for manual flashing).
|
# Skipped when signing secrets aren't set.
|
||||||
- name: Build RAUC bundle
|
- name: Build RAUC bundle
|
||||||
id: raucb
|
id: raucb
|
||||||
if: ${{ secrets.BF_RAUC_SIGNING_CERT != '' && secrets.BF_RAUC_SIGNING_KEY != '' }}
|
if: ${{ secrets.BF_RAUC_SIGNING_CERT != '' && secrets.BF_RAUC_SIGNING_KEY != '' }}
|
||||||
|
|
@ -240,8 +270,7 @@ jobs:
|
||||||
BF_RAUC_SIGNING_KEY: ${{ secrets.BF_RAUC_SIGNING_KEY }}
|
BF_RAUC_SIGNING_KEY: ${{ secrets.BF_RAUC_SIGNING_KEY }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
sudo apt-get update
|
sudo apt-get install -y --no-install-recommends rauc openssl
|
||||||
sudo apt-get install -y --no-install-recommends rauc e2fsprogs xz-utils util-linux openssl
|
|
||||||
mkdir -p /tmp/rauc-signing
|
mkdir -p /tmp/rauc-signing
|
||||||
chmod 700 /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_CERT" > /tmp/rauc-signing/cert.pem
|
||||||
|
|
@ -250,7 +279,8 @@ jobs:
|
||||||
chmod +x deploy/rauc/build-bundle.sh
|
chmod +x deploy/rauc/build-bundle.sh
|
||||||
out="${{ github.workspace }}/betterframe-${{ inputs.version }}.raucb"
|
out="${{ github.workspace }}/betterframe-${{ inputs.version }}.raucb"
|
||||||
deploy/rauc/build-bundle.sh \
|
deploy/rauc/build-bundle.sh \
|
||||||
"${{ steps.pigen.outputs.image-path }}" \
|
"${{ steps.repartition.outputs.rootfs-path }}" \
|
||||||
|
"${{ steps.repartition.outputs.bootfs-path }}" \
|
||||||
"$out" \
|
"$out" \
|
||||||
"${{ inputs.version }}" \
|
"${{ inputs.version }}" \
|
||||||
"${{ github.sha }}" \
|
"${{ github.sha }}" \
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,22 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Build a signed RAUC .raucb bundle from a pi-gen-produced .img.xz.
|
# Build a signed RAUC .raucb bundle from pre-extracted slot images.
|
||||||
|
#
|
||||||
|
# The repartition-image.sh script (run earlier in CI) already extracts
|
||||||
|
# rootfs.ext4 + bootfs.vfat from the pi-gen output, so this script just
|
||||||
|
# stages them with a rendered manifest + runs `rauc bundle`.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# build-bundle.sh <input.img.xz> <output.raucb> <version> <git_sha> \
|
# build-bundle.sh <rootfs.ext4> <bootfs.vfat> <out.raucb> \
|
||||||
# <signing_cert.pem> <signing_key.pem>
|
# <version> <git_sha> <signing_cert.pem> <signing_key.pem>
|
||||||
#
|
|
||||||
# Approach: decompress the .img.xz, identify its bootfs (vfat) + rootfs
|
|
||||||
# (ext4) partitions via sfdisk, dd them into bundle-staging/ as
|
|
||||||
# bootfs.vfat + rootfs.ext4, render the manifest template with version
|
|
||||||
# + git sha, then `rauc bundle --cert= --key= staging out.raucb`.
|
|
||||||
#
|
|
||||||
# We use the FAT and ext4 partitions from a stock pi-gen image — i.e. the
|
|
||||||
# bundle content matches what's on a freshly-flashed kiosk. The TARGET
|
|
||||||
# device still needs an A/B partition layout for RAUC to actually install
|
|
||||||
# (separate workstream); a bundle built today is only consumable by
|
|
||||||
# kiosks already running the A/B layout.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
IN_IMG_XZ="${1:?input .img.xz required}"
|
ROOTFS_IN="${1:?rootfs.ext4 path required}"
|
||||||
OUT_RAUCB="${2:?output .raucb path required}"
|
BOOTFS_IN="${2:?bootfs.vfat path required}"
|
||||||
VERSION="${3:?version required}"
|
OUT_RAUCB="${3:?output .raucb path required}"
|
||||||
GIT_SHA="${4:?git sha required}"
|
VERSION="${4:?version required}"
|
||||||
SIGNING_CERT="${5:?signing cert path required}"
|
GIT_SHA="${5:?git sha required}"
|
||||||
SIGNING_KEY="${6:?signing key path required}"
|
SIGNING_CERT="${6:?signing cert path required}"
|
||||||
|
SIGNING_KEY="${7:?signing key path required}"
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
MANIFEST_IN="${SCRIPT_DIR}/manifest.raucm.in"
|
MANIFEST_IN="${SCRIPT_DIR}/manifest.raucm.in"
|
||||||
|
|
@ -30,67 +24,17 @@ MANIFEST_IN="${SCRIPT_DIR}/manifest.raucm.in"
|
||||||
WORK_DIR="$(mktemp -d)"
|
WORK_DIR="$(mktemp -d)"
|
||||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||||
|
|
||||||
echo "==> Decompressing $IN_IMG_XZ"
|
|
||||||
RAW_IMG="${WORK_DIR}/image.img"
|
|
||||||
xz -d -c "$IN_IMG_XZ" > "$RAW_IMG"
|
|
||||||
|
|
||||||
echo "==> Reading partition table"
|
|
||||||
# sfdisk -d emits: <device>: start=N, size=N, type=X, name=...
|
|
||||||
# pi-gen layout: p1 = bootfs (vfat, type=c), p2 = rootfs (ext4, type=83)
|
|
||||||
BOOT_INFO="$(sfdisk -d "$RAW_IMG" | awk '/img1/ || /img.*: start/ {print}')"
|
|
||||||
ROOT_INFO="$(sfdisk -d "$RAW_IMG" | awk '/img2/ || /img.*: start/ {print}')"
|
|
||||||
|
|
||||||
# Robust parse: walk partition lines, identify by type code.
|
|
||||||
parse_part() {
|
|
||||||
local part_idx="$1"
|
|
||||||
sfdisk -d "$RAW_IMG" \
|
|
||||||
| awk -v idx="$part_idx" '
|
|
||||||
/: start=/ {
|
|
||||||
n++;
|
|
||||||
if (n == idx) {
|
|
||||||
for (i = 1; i <= NF; i++) {
|
|
||||||
if ($i ~ /start=/) { gsub(/[^0-9]/, "", $i); start = $i }
|
|
||||||
if ($i ~ /size=/) { gsub(/[^0-9]/, "", $i); size = $i }
|
|
||||||
}
|
|
||||||
print start, size;
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
}
|
|
||||||
|
|
||||||
read BOOT_START BOOT_SIZE < <(parse_part 1)
|
|
||||||
read ROOT_START ROOT_SIZE < <(parse_part 2)
|
|
||||||
if [ -z "${BOOT_START:-}" ] || [ -z "${ROOT_START:-}" ]; then
|
|
||||||
echo "could not parse pi-gen partition table — expected 2 partitions" >&2
|
|
||||||
sfdisk -d "$RAW_IMG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " bootfs: start=$BOOT_START size=$BOOT_SIZE sectors (512B each)"
|
|
||||||
echo " rootfs: start=$ROOT_START size=$ROOT_SIZE sectors (512B each)"
|
|
||||||
|
|
||||||
STAGE="${WORK_DIR}/bundle"
|
STAGE="${WORK_DIR}/bundle"
|
||||||
mkdir -p "$STAGE"
|
mkdir -p "$STAGE"
|
||||||
|
|
||||||
echo "==> Extracting bootfs.vfat"
|
cp "$ROOTFS_IN" "${STAGE}/rootfs.ext4"
|
||||||
dd if="$RAW_IMG" of="${STAGE}/bootfs.vfat" \
|
cp "$BOOTFS_IN" "${STAGE}/bootfs.vfat"
|
||||||
bs=512 skip="$BOOT_START" count="$BOOT_SIZE" status=none
|
|
||||||
|
|
||||||
echo "==> Extracting rootfs.ext4"
|
|
||||||
dd if="$RAW_IMG" of="${STAGE}/rootfs.ext4" \
|
|
||||||
bs=512 skip="$ROOT_START" count="$ROOT_SIZE" status=none
|
|
||||||
|
|
||||||
# Shrink the rootfs to actual used space so bundles don't ship empty bytes.
|
|
||||||
# pi-gen's export-image already does this, but verify file integrity first.
|
|
||||||
echo "==> Checking rootfs.ext4 integrity"
|
|
||||||
e2fsck -fy "${STAGE}/rootfs.ext4" || true # tolerate "clean but old fs version" warnings
|
|
||||||
|
|
||||||
echo "==> Rendering manifest"
|
echo "==> Rendering manifest"
|
||||||
sed -e "s|@VERSION@|${VERSION}|g" \
|
sed -e "s|@VERSION@|${VERSION}|g" \
|
||||||
-e "s|@GIT_SHA@|${GIT_SHA}|g" \
|
-e "s|@GIT_SHA@|${GIT_SHA}|g" \
|
||||||
"$MANIFEST_IN" > "${STAGE}/manifest.raucm"
|
"$MANIFEST_IN" > "${STAGE}/manifest.raucm"
|
||||||
|
|
||||||
echo "==> Bundle staging contents"
|
|
||||||
ls -la "$STAGE"
|
ls -la "$STAGE"
|
||||||
cat "${STAGE}/manifest.raucm"
|
cat "${STAGE}/manifest.raucm"
|
||||||
|
|
||||||
|
|
@ -101,9 +45,8 @@ rauc bundle \
|
||||||
--key="$SIGNING_KEY" \
|
--key="$SIGNING_KEY" \
|
||||||
"$STAGE" "$OUT_RAUCB"
|
"$STAGE" "$OUT_RAUCB"
|
||||||
|
|
||||||
echo "==> Verifying bundle (uses the signing cert as its own trust anchor)"
|
echo "==> Verifying bundle"
|
||||||
rauc info --keyring="$SIGNING_CERT" "$OUT_RAUCB"
|
rauc info --keyring="$SIGNING_CERT" "$OUT_RAUCB"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "==> Bundle written: $OUT_RAUCB"
|
echo "==> Bundle: $(ls -la "$OUT_RAUCB")"
|
||||||
ls -la "$OUT_RAUCB"
|
|
||||||
|
|
|
||||||
172
deploy/rauc/repartition-image.sh
Executable file
172
deploy/rauc/repartition-image.sh
Executable file
|
|
@ -0,0 +1,172 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Convert a stock pi-gen .img.xz (2-partition: boot + root) into a RAUC
|
||||||
|
# A/B image (6 partitions: BF_BOOTSEL + BF_BOOT_A + BF_BOOT_B + BF_ROOT_A
|
||||||
|
# + BF_ROOT_B + BF_DATA). Also emits the raw rootfs.ext4 and bootfs.vfat
|
||||||
|
# slot images that the .raucb bundle builder consumes.
|
||||||
|
#
|
||||||
|
# Why post-process pi-gen instead of patching its export-image stage:
|
||||||
|
# pi-gen's image builder is fitted to stock Pi OS layouts. Bending it
|
||||||
|
# to A/B was fighting the tool every step. Treating its output as a
|
||||||
|
# black box and re-laying out in CI keeps pi-gen vanilla + lets us
|
||||||
|
# iterate the partition logic locally with losetup.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# repartition-image.sh <in.img.xz> <out.img.xz> <rootfs.ext4> <bootfs.vfat>
|
||||||
|
#
|
||||||
|
# Requires root (loop mounts, mkfs).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IN_IMG_XZ="${1:?input .img.xz required}"
|
||||||
|
OUT_IMG_XZ="${2:?output .img.xz required}"
|
||||||
|
ROOTFS_OUT="${3:?rootfs.ext4 output path required}"
|
||||||
|
BOOTFS_OUT="${4:?bootfs.vfat output path required}"
|
||||||
|
|
||||||
|
WORK="$(mktemp -d)"
|
||||||
|
trap 'cleanup' EXIT
|
||||||
|
cleanup() {
|
||||||
|
set +e
|
||||||
|
if [ -n "${OUT_LOOP:-}" ]; then losetup -d "$OUT_LOOP" 2>/dev/null; fi
|
||||||
|
if [ -n "${SRC_LOOP:-}" ]; then losetup -d "$SRC_LOOP" 2>/dev/null; fi
|
||||||
|
for m in "$WORK"/mnt-*; do [ -d "$m" ] && umount "$m" 2>/dev/null; done
|
||||||
|
rm -rf "$WORK"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "==> Decompressing $IN_IMG_XZ"
|
||||||
|
xz -d -c "$IN_IMG_XZ" > "$WORK/in.img"
|
||||||
|
|
||||||
|
echo "==> Reading source partition table"
|
||||||
|
SRC_LOOP="$(losetup -fP --show "$WORK/in.img")"
|
||||||
|
sfdisk -d "$WORK/in.img"
|
||||||
|
|
||||||
|
# Pi-gen layout is always p1=boot vfat, p2=root ext4.
|
||||||
|
SRC_BOOT="${SRC_LOOP}p1"
|
||||||
|
SRC_ROOT="${SRC_LOOP}p2"
|
||||||
|
|
||||||
|
echo "==> Extracting bootfs + rootfs from source image"
|
||||||
|
dd if="$SRC_BOOT" of="$WORK/bootfs.vfat" bs=4M status=progress
|
||||||
|
dd if="$SRC_ROOT" of="$WORK/rootfs.ext4" bs=4M status=progress
|
||||||
|
losetup -d "$SRC_LOOP"; SRC_LOOP=""
|
||||||
|
|
||||||
|
# Shrink rootfs to actual used + small headroom so the bundle and image
|
||||||
|
# don't ship empty bytes. resize2fs -M shrinks to minimum.
|
||||||
|
echo "==> Compacting rootfs.ext4"
|
||||||
|
e2fsck -fy "$WORK/rootfs.ext4" || true
|
||||||
|
resize2fs -M "$WORK/rootfs.ext4"
|
||||||
|
ROOTFS_BYTES_USED="$(stat -c%s "$WORK/rootfs.ext4")"
|
||||||
|
# Grow back with ~25% headroom so first boot has room for apt-update etc.
|
||||||
|
ROOTFS_BYTES_SLOT=$(( ROOTFS_BYTES_USED * 5 / 4 ))
|
||||||
|
# Round up to MiB.
|
||||||
|
ROOTFS_BYTES_SLOT=$(( (ROOTFS_BYTES_SLOT + 1048575) / 1048576 * 1048576 ))
|
||||||
|
truncate -s "$ROOTFS_BYTES_SLOT" "$WORK/rootfs.ext4"
|
||||||
|
resize2fs "$WORK/rootfs.ext4"
|
||||||
|
echo " rootfs slot size: $((ROOTFS_BYTES_SLOT / 1024 / 1024)) MiB"
|
||||||
|
|
||||||
|
# Bootfs we leave as-is (FAT, can't easily shrink, ~256MB).
|
||||||
|
BOOTFS_BYTES_SLOT="$(stat -c%s "$WORK/bootfs.vfat")"
|
||||||
|
echo " bootfs slot size: $((BOOTFS_BYTES_SLOT / 1024 / 1024)) MiB"
|
||||||
|
|
||||||
|
# Patch rootfs fstab + boot cmdline to mount by LABEL (slot-agnostic).
|
||||||
|
# Pi-gen ships PARTUUID-based fstab; with two ROOT slots PARTUUID is
|
||||||
|
# wrong per-slot. LABEL works because RAUC formats each slot with its
|
||||||
|
# correct label after install. For the initial flash we hand-set BF_*
|
||||||
|
# labels below.
|
||||||
|
echo "==> Patching rootfs /etc/fstab to use LABEL=BF_*"
|
||||||
|
mkdir -p "$WORK/mnt-root"
|
||||||
|
mount -o loop "$WORK/rootfs.ext4" "$WORK/mnt-root"
|
||||||
|
cat > "$WORK/mnt-root/etc/fstab" <<'EOF'
|
||||||
|
LABEL=BF_BOOT_A /boot/firmware vfat defaults 0 2
|
||||||
|
LABEL=BF_ROOT_A / ext4 defaults,noatime 0 1
|
||||||
|
LABEL=BF_DATA /var/lib/betterframe ext4 defaults,noatime,nofail 0 2
|
||||||
|
EOF
|
||||||
|
# Stamp the OS version + compatibility for the kiosk's os_update.rs
|
||||||
|
# to read at runtime. CI passes BF_BUILD_VERSION via env.
|
||||||
|
mkdir -p "$WORK/mnt-root/etc/betterframe"
|
||||||
|
printf '%s\n' "${BF_BUILD_VERSION:-0.0.0}" > "$WORK/mnt-root/etc/betterframe/os-version"
|
||||||
|
printf '%s\n' "${BF_RAUC_COMPATIBILITY:-betterframe-rpi5-aarch64}" > "$WORK/mnt-root/etc/betterframe/os-compatibility"
|
||||||
|
umount "$WORK/mnt-root"
|
||||||
|
|
||||||
|
# Two bootfs copies, each rewriting cmdline.txt root=LABEL=BF_ROOT_{A,B}.
|
||||||
|
echo "==> Building BF_BOOT_A bootfs"
|
||||||
|
cp "$WORK/bootfs.vfat" "$WORK/bootfs_A.vfat"
|
||||||
|
mkdir -p "$WORK/mnt-boota"
|
||||||
|
mount -o loop "$WORK/bootfs_A.vfat" "$WORK/mnt-boota"
|
||||||
|
sed -i 's|root=PARTUUID=[^ ]*|root=LABEL=BF_ROOT_A|' "$WORK/mnt-boota/cmdline.txt"
|
||||||
|
umount "$WORK/mnt-boota"
|
||||||
|
|
||||||
|
echo "==> Building BF_BOOT_B bootfs (placeholder, kernel from A)"
|
||||||
|
cp "$WORK/bootfs.vfat" "$WORK/bootfs_B.vfat"
|
||||||
|
mkdir -p "$WORK/mnt-bootb"
|
||||||
|
mount -o loop "$WORK/bootfs_B.vfat" "$WORK/mnt-bootb"
|
||||||
|
sed -i 's|root=PARTUUID=[^ ]*|root=LABEL=BF_ROOT_B|' "$WORK/mnt-bootb/cmdline.txt"
|
||||||
|
umount "$WORK/mnt-bootb"
|
||||||
|
|
||||||
|
# Layout the new combined image. GPT (Pi 5 firmware supports it). All
|
||||||
|
# sizes in MiB to keep sfdisk happy.
|
||||||
|
SELECTOR_MB=8
|
||||||
|
BOOT_MB=$((BOOTFS_BYTES_SLOT / 1024 / 1024))
|
||||||
|
ROOT_MB=$((ROOTFS_BYTES_SLOT / 1024 / 1024))
|
||||||
|
DATA_MB=512 # placeholder; resize2fs at first boot expands to free space
|
||||||
|
TOTAL_MB=$((SELECTOR_MB + BOOT_MB*2 + ROOT_MB*2 + DATA_MB + 32))
|
||||||
|
|
||||||
|
echo "==> Allocating ${TOTAL_MB} MiB output image"
|
||||||
|
truncate -s "${TOTAL_MB}M" "$WORK/out.img"
|
||||||
|
|
||||||
|
echo "==> Writing GPT partition table"
|
||||||
|
sfdisk "$WORK/out.img" <<EOF
|
||||||
|
label: gpt
|
||||||
|
|
||||||
|
start=2048, size=$((SELECTOR_MB * 2048)), type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="BF_BOOTSEL"
|
||||||
|
size=$((BOOT_MB * 2048)), type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="BF_BOOT_A"
|
||||||
|
size=$((BOOT_MB * 2048)), type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name="BF_BOOT_B"
|
||||||
|
size=$((ROOT_MB * 2048)), type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="BF_ROOT_A"
|
||||||
|
size=$((ROOT_MB * 2048)), type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="BF_ROOT_B"
|
||||||
|
type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="BF_DATA"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
OUT_LOOP="$(losetup -fP --show "$WORK/out.img")"
|
||||||
|
|
||||||
|
echo "==> Formatting selector + writing autoboot.txt"
|
||||||
|
mkfs.vfat -n "BF_BOOTSEL" "${OUT_LOOP}p1"
|
||||||
|
mkdir -p "$WORK/mnt-sel"
|
||||||
|
mount "${OUT_LOOP}p1" "$WORK/mnt-sel"
|
||||||
|
cat > "$WORK/mnt-sel/autoboot.txt" <<'EOF'
|
||||||
|
[all]
|
||||||
|
tryboot_a_b=1
|
||||||
|
PARTITION_WALK=1
|
||||||
|
boot_partition=2
|
||||||
|
|
||||||
|
[tryboot]
|
||||||
|
boot_partition=3
|
||||||
|
EOF
|
||||||
|
umount "$WORK/mnt-sel"
|
||||||
|
|
||||||
|
echo "==> Writing BF_BOOT_A + BF_BOOT_B"
|
||||||
|
dd if="$WORK/bootfs_A.vfat" of="${OUT_LOOP}p2" bs=4M conv=fsync
|
||||||
|
dd if="$WORK/bootfs_B.vfat" of="${OUT_LOOP}p3" bs=4M conv=fsync
|
||||||
|
# Force the label on the partition's vfat header.
|
||||||
|
fatlabel "${OUT_LOOP}p2" BF_BOOT_A
|
||||||
|
fatlabel "${OUT_LOOP}p3" BF_BOOT_B
|
||||||
|
|
||||||
|
echo "==> Writing BF_ROOT_A + initializing BF_ROOT_B empty"
|
||||||
|
dd if="$WORK/rootfs.ext4" of="${OUT_LOOP}p4" bs=4M conv=fsync
|
||||||
|
e2label "${OUT_LOOP}p4" BF_ROOT_A
|
||||||
|
mkfs.ext4 -F -L BF_ROOT_B "${OUT_LOOP}p5"
|
||||||
|
|
||||||
|
echo "==> Formatting BF_DATA"
|
||||||
|
mkfs.ext4 -F -L BF_DATA "${OUT_LOOP}p6"
|
||||||
|
|
||||||
|
losetup -d "$OUT_LOOP"; OUT_LOOP=""
|
||||||
|
|
||||||
|
echo "==> Final partition table"
|
||||||
|
sfdisk -d "$WORK/out.img"
|
||||||
|
|
||||||
|
echo "==> Emitting bundle slot images"
|
||||||
|
cp "$WORK/rootfs.ext4" "$ROOTFS_OUT"
|
||||||
|
cp "$WORK/bootfs.vfat" "$BOOTFS_OUT"
|
||||||
|
|
||||||
|
echo "==> Compressing output image (xz -T0)"
|
||||||
|
xz -T0 -9 -c "$WORK/out.img" > "$OUT_IMG_XZ"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Done."
|
||||||
|
ls -la "$OUT_IMG_XZ" "$ROOTFS_OUT" "$BOOTFS_OUT"
|
||||||
Loading…
Reference in a new issue