wrolpi/pi-gen/build.sh
Roland e01795ad7e
Debian 13 (trixie) rebase: docker, debian-live-config, and pi-gen builds.
All three build targets move from Debian 12/bookworm to Debian 13/trixie
for fresh installs; existing Debian 12 systems remain supported in place.

Docker: api/help on python:3.13-trixie, controller on 3.13-slim-trixie,
app on node:20-trixie (matches trixie's nodejs 20), samba/zim on
debian:13.  Full backend suite passes on Python 3.13 in the api container.

debian-live-config: --distribution trixie; postgres teardown for the
SQLite cutover (pg_dropcluster hook block removed, wrolpi-bootstrap.sh no
longer creates a cluster or calls the deleted initialize_api_db.sh — the
API creates wrolpi.db itself on first start).  README documents the
SQLite layout and drops Etcher recommendations.

pi-gen: upstream has no trixie branch — the arm64 branch now defaults to
RELEASE=trixie; KEYBOARD_KEYMAP=en_US is rejected by trixie's
console-setup (ckbcomp validates), use 'us'; stop stamping a Postgres
.pgpass into /etc/skel.

Package lists (both targets): postgresql-15/postgresql-doc/libpq-dev
deleted; libtiff5-dev renamed to libtiff-dev; software-properties-common
and cpufrequtils removed from trixie (Controller CPU throttle falls back
to sysfs, dead cpufreq-set sudoers rule removed); locales and gnupg added
(no longer arrive transitively under --apt-recommends false); sqlite3 CLI
added for reset_tags.sh.

help.sh: replace the Postgres service/port/socket/cluster/psql checks
(all false FAILEDs after the SQLite cutover) with SQLite checks:
wrolpi.db exists, PRAGMA quick_check, file_group table + row count, run
as the wrolpi user so root never creates the -shm sidecar.

Both build scripts take a WROLPI_BUILD_DIR override so development builds
can run beside the release pipeline's build trees; the sudo re-exec
passes it through explicitly (a plain sudo would drop it).

test_common.py: the truncate tests hard-coded sys.getsizeof results from
the 3.11 object layout (str/list shrank in 3.12); assert the contract
(fits budget, prefix, monotonic in budget) so they pass on 3.11 and 3.13.

Verified: trixie ISO (3.7G) and pi-gen desktop image (4.3G) both built
from scratch and passed their sanity checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:43:08 -06:00

167 lines
6.7 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# https://github.com/RPI-Distro/pi-gen
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Overridable so a development build can run beside the release pipeline's
# build tree without colliding with it.
BUILD_DIR="${WROLPI_BUILD_DIR:-/var/tmp/wrolpi-build-pi-gen}"
Help() {
echo "Build WROLPi Raspberry Pi OS image."
echo
echo "Syntax: build.sh [-h] [-b BRANCH]"
echo "options:"
echo "h Print this help."
echo "b Build from this git BRANCH (default: 'release')."
echo
echo "Set WROLPI_BUILD_DIR to build somewhere other than the default"
echo "(${BUILD_DIR}), e.g. beside the release pipeline's tree."
echo
}
BRANCH="release"
while getopts ":hb:" option; do
case $option in
h) Help; exit ;;
b) BRANCH="${OPTARG}" ;;
*) echo "Error: Invalid option"; exit 1 ;;
esac
done
# Get version from the target git branch (not local checkout) so output filename matches actual content.
VERSION=$(curl -sL "https://raw.githubusercontent.com/lrnselfreliance/wrolpi/${BRANCH}/wrolpi/version.txt")
if [ -z "${VERSION}" ]; then
echo "ERROR: Could not fetch version from branch '${BRANCH}'"
exit 1
fi
echo "Building WROLPi version: ${VERSION} from branch: ${BRANCH}"
# Re-execute this script if it wasn't called with sudo. Pass BUILD_DIR
# through explicitly — a plain `sudo "$0"` drops WROLPI_BUILD_DIR from the
# environment and the build would silently land on the default path.
if [ $EUID != 0 ]; then
sudo WROLPI_BUILD_DIR="${BUILD_DIR}" "$0" "$@"
exit $?
fi
CHROOTFS="${BUILD_DIR}/work/WROLPi/stage0/rootfs"
cleanup() {
[ -f /var/tmp/arm64.zip ] && rm /var/tmp/arm64.zip
# Only unmount if it is actually a mountpoint
for mp in \
"${CHROOTFS}/dev/pts" \
"${CHROOTFS}/sys" \
"${CHROOTFS}/proc" \
"${CHROOTFS}/run" \
"${CHROOTFS}/tmp" \
"${CHROOTFS}/dev"; do
if mountpoint -q "${mp}" 2>/dev/null; then
umount "${mp}" 2>/dev/null || umount -l "${mp}" 2>/dev/null
fi
done
# Remove immutable attribute from protected files before cleanup
chattr -i "${BUILD_DIR}"/work/*/stage*/rootfs/opt/wrolpi-blobs/* 2>/dev/null || :
# Final safety net – remove the whole build tree
rm -rf "${BUILD_DIR}"
}
trap cleanup EXIT
set -e
set -x
# Cache directory for downloaded blobs (persists across builds).
BLOB_CACHE="${SCRIPT_DIR}/stage2/04-wrolpi/files"
mkdir -p "${BLOB_CACHE}"
# Download map fonts (skip if cached).
if [ ! -f "${BLOB_CACHE}/map-fonts.tar.gz" ]; then
echo "Downloading map fonts..."
mkdir -p /tmp/map-fonts-dl
curl -fsSL https://github.com/protomaps/basemaps-assets/archive/refs/heads/main.tar.gz \
| tar -xz --strip-components=1 -C /tmp/map-fonts-dl basemaps-assets-main/fonts
tar -czf "${BLOB_CACHE}/map-fonts.tar.gz" -C /tmp/map-fonts-dl fonts
rm -rf /tmp/map-fonts-dl
fi
[[ -f "${BLOB_CACHE}/map-fonts.tar.gz" && -s "${BLOB_CACHE}/map-fonts.tar.gz" ]] || \
(echo "ERROR: Failed to download map fonts!" && exit 1)
# Download map overview (skip if cached).
if [ ! -f "${BLOB_CACHE}/map-overview.pmtiles" ]; then
echo "Downloading map overview..."
curl -fsSL https://wrolpi.nyc3.cdn.digitaloceanspaces.com/maps/map-overview.pmtiles \
-o "${BLOB_CACHE}/map-overview.pmtiles"
fi
[[ -f "${BLOB_CACHE}/map-overview.pmtiles" && -s "${BLOB_CACHE}/map-overview.pmtiles" ]] || \
(echo "ERROR: Failed to download map overview!" && exit 1)
# Clear leftovers from a previous build before unpacking. The EXIT trap
# normally cleans up, but a SIGTERM/kill (e.g. `systemctl stop`) skips it and
# leaves BUILD_DIR behind -- the `mv` below would then nest the new tree inside
# the stale one and break the build. Strip the immutable flag off blob files
# first (the chroot sets chattr +i), or rm -rf cannot remove them.
chattr -i "${BUILD_DIR}"/work/*/stage*/rootfs/opt/wrolpi-blobs/* 2>/dev/null || :
rm -rf "${BUILD_DIR}" /var/tmp/pi-gen-arm64 /var/tmp/arm64.zip
# Get the latest pi-gen code. The `arm64` branch builds trixie (Debian 13).
wget https://github.com/RPi-Distro/pi-gen/archive/refs/heads/arm64.zip -O /var/tmp/arm64.zip
unzip /var/tmp/arm64.zip -d /var/tmp
mv /var/tmp/pi-gen-arm64 "${BUILD_DIR}"
# Copy the configuration and build files into the pi-gen directory.
cp "${SCRIPT_DIR}/config.txt" "${BUILD_DIR}/config.txt"
echo "export WROLPI_BRANCH=${BRANCH}" >> "${BUILD_DIR}/config.txt"
rsync -a "${SCRIPT_DIR}"/stage2/* "${BUILD_DIR}/stage2/"
# We only need to build the Lite and Desktop images.
rm -f "${BUILD_DIR}"/stage*/EXPORT*
#echo 'IMG_SUFFIX="-lite"' > "${BUILD_DIR}/stage2/EXPORT_IMAGE"
echo 'IMG_SUFFIX="-desktop"' > "${BUILD_DIR}/stage5/EXPORT_IMAGE"
# Build the images.
(cd "${BUILD_DIR}" && time nice -n 18 "${BUILD_DIR}"/build.sh -c "${BUILD_DIR}"/config.txt | \
tee "${SCRIPT_DIR}"/build.log)
grep "00-run-chroot.sh completed" "${SCRIPT_DIR}/build.log" >/dev/null 2>&1 || (echo "script 0 failed!" && exit 1)
grep "03-run-chroot.sh completed" "${SCRIPT_DIR}/build.log" >/dev/null 2>&1 || (echo "script 3 failed!" && exit 1)
grep "03-run-chroot.sh completed" "${SCRIPT_DIR}/build.log" >/dev/null 2>&1 || (echo "script 3 failed!" && exit 1)
grep "04-run-chroot.sh completed" "${SCRIPT_DIR}/build.log" >/dev/null 2>&1 || (echo "script 4 failed!" && exit 1)
# Move the built images out of the build directory.
#mv "${BUILD_DIR}"/deploy/*lite*xz "${SCRIPT_DIR}"/WROLPi-v"${VERSION}"-aarch64-lite.img.xz
mv "${BUILD_DIR}"/deploy/*desktop*xz "${SCRIPT_DIR}"/WROLPi-v"${VERSION}"-aarch64-desktop.img.xz
chmod 644 "${SCRIPT_DIR}"/*xz
chown -R 1000:1000 "${SCRIPT_DIR}"
set +x
# Sanity-check the resulting image before declaring success, so a truncated or
# corrupt build never ships.
IMG="${SCRIPT_DIR}/WROLPi-v${VERSION}-aarch64-desktop.img.xz"
MIN_IMG_BYTES=$((1024 * 1024 * 1024)) # 1 GiB floor; compressed is ~5 GiB.
MIN_UNCOMPRESSED_BYTES=$((2 * 1024 * 1024 * 1024)) # 2 GiB floor; real is ~8 GiB.
IMG_BYTES=$(stat -c%s "${IMG}")
if [ "${IMG_BYTES}" -lt "${MIN_IMG_BYTES}" ]; then
echo "ERROR: image is only ${IMG_BYTES} bytes (< ${MIN_IMG_BYTES}); build likely truncated." >&2
exit 1
fi
# Full decompression test: catches any corruption in the .xz stream.
if ! xz -t "${IMG}"; then
echo "ERROR: ${IMG} failed xz integrity check." >&2
exit 1
fi
# Confirm the uncompressed image is a sane size (field 5 of the robot totals).
UNCOMPRESSED_BYTES=$(xz --robot -l "${IMG}" 2>/dev/null | awk -F'\t' '/^totals/{print $5}')
if [ -n "${UNCOMPRESSED_BYTES}" ] && [ "${UNCOMPRESSED_BYTES}" -lt "${MIN_UNCOMPRESSED_BYTES}" ]; then
echo "ERROR: uncompressed image is only ${UNCOMPRESSED_BYTES} bytes (< ${MIN_UNCOMPRESSED_BYTES})." >&2
exit 1
fi
echo "Image sanity checks passed ($(numfmt --to=iec "${IMG_BYTES}" 2>/dev/null || echo "${IMG_BYTES} bytes") compressed, xz OK)."
echo "Build completed successfully"