mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-25 18:40:06 -04:00
* Bake the app build into images; rebuild on-device only when stale Previously the Raspberry Pi image shipped without app/build/ and compiled the React bundle on first boot via repair.sh -- a multi-minute, memory-hungry step that can OOM a 2GB Pi. The Debian Live ISO already baked the build in, so the two paths had drifted. Add scripts/build_app.sh as the single source of truth: it hashes the build inputs (src/, public/, package.json, package-lock.json, tsconfig.json) and runs `npm run build` (with GENERATE_SOURCEMAP=false) only when build/ is missing or those inputs changed, then writes build/.build-stamp. Wire all three call sites to it: - pi-gen 03-run-chroot.sh now builds after npm install, baking build/ into the Pi image like the Debian hook already does. - debian-live 9999-wrolpi.hook.chroot uses it so the baked build is stamped. - repair.sh uses it, so first-boot/manual-repair/install/upgrade all become no-ops when the shipped build is current. Result: fresh images ship a stamped build/, so a weak Pi never compiles on boot or repair; an on-device rebuild happens only when an upgrade actually changes app/. build_app.sh never runs npm install, so repair.sh keeps its offline guarantee. * build_app.sh: rebuild when built output is incomplete, not just missing Addresses review feedback: a matching stamp only proves the inputs are unchanged, not that the output survived. repair.sh exists to fix corruption, so a build/ with index.html + stamp but a missing JS/CSS asset must still rebuild. Verify every file referenced by build/asset-manifest.json exists (and that the manifest itself parses); rebuild otherwise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.9 KiB
Bash
Executable file
86 lines
3.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Build the React app ONLY when the built output is missing or stale.
|
|
#
|
|
# This is the single source of truth for producing app/build/. It is called
|
|
# from three places that must never drift apart:
|
|
# - repair.sh (on-device: first boot, manual repair, install, upgrade)
|
|
# - pi-gen/stage2/04-wrolpi/03-run-chroot.sh (bakes build/ into the Raspberry Pi image)
|
|
# - debian-live-config/.../9999-wrolpi.hook.chroot (bakes build/ into the Debian Live ISO)
|
|
#
|
|
# Both image builds run this at image-build time on the (powerful) build host, so
|
|
# the shipped image already contains app/build/ with a matching stamp. On the
|
|
# device the same script then finds the stamp current and does nothing -- a weak
|
|
# Pi (even 2GB) never has to run the multi-minute webpack build itself. It only
|
|
# rebuilds when the app source actually changed (e.g. an upgrade pulled new code)
|
|
# or when build/ is missing/corrupt.
|
|
#
|
|
# Offline-safe: this only ever runs `npm run build`, which uses the already
|
|
# installed node_modules. It never runs `npm install`, so repair.sh keeps its
|
|
# "will not use internet" guarantee. Installing node_modules stays the caller's
|
|
# job (the install/upgrade scripts and image hooks do it before calling here).
|
|
set -e
|
|
|
|
APP_DIR="${1:-/opt/wrolpi/app}"
|
|
cd "$APP_DIR"
|
|
|
|
# Hash of every input that determines the built output.
|
|
#
|
|
# KEEP IN SYNC: if a build-affecting input is ever added (craco.config.js, an
|
|
# .env / .env.production, a new config dir, etc.) add it here. A missed input
|
|
# means a stale build ships silently -- the worst failure mode, because it looks
|
|
# like everything worked.
|
|
app_build_stamp() {
|
|
{
|
|
sha256sum package.json package-lock.json tsconfig.json
|
|
find src public -type f -exec sha256sum {} +
|
|
} | sort | sha256sum | cut -d' ' -f1
|
|
}
|
|
|
|
# Verify the built output is actually intact, not just present.
|
|
#
|
|
# A matching stamp only proves the *inputs* are unchanged; it says nothing about
|
|
# whether the *outputs* survived. repair.sh exists precisely to fix a corrupted
|
|
# install, so a build/ that still has index.html + a stamp but is missing a JS/CSS
|
|
# chunk must NOT be treated as current -- otherwise repair would leave the UI
|
|
# broken where the old unconditional build restored it. Check that every asset
|
|
# referenced by build/asset-manifest.json exists on disk. node is guaranteed
|
|
# here: anything that can run `npm run build` can run node.
|
|
build_output_intact() {
|
|
[ -f build/asset-manifest.json ] || return 1
|
|
node -e '
|
|
const fs = require("fs");
|
|
let m;
|
|
try { m = JSON.parse(fs.readFileSync("build/asset-manifest.json", "utf8")); }
|
|
catch { process.exit(1); } // unparseable manifest -> rebuild
|
|
const missing = Object.values(m.files || {})
|
|
.map(p => "build/" + p.replace(/^\//, ""))
|
|
.filter(p => !fs.existsSync(p));
|
|
process.exit(missing.length ? 1 : 0);
|
|
' 2>/dev/null
|
|
}
|
|
|
|
need_build=false
|
|
if [ ! -f build/index.html ]; then
|
|
echo "app build is missing."
|
|
need_build=true
|
|
elif ! build_output_intact; then
|
|
echo "app build is present but incomplete/corrupt (missing referenced assets)."
|
|
need_build=true
|
|
elif [ "$(app_build_stamp)" != "$(cat build/.build-stamp 2>/dev/null)" ]; then
|
|
echo "app source changed since the last build."
|
|
need_build=true
|
|
fi
|
|
|
|
if [ "$need_build" = true ]; then
|
|
echo "Building the React production bundle (this can take several minutes)..."
|
|
# GENERATE_SOURCEMAP=false: source maps are a debugging aid for the deployed
|
|
# bundle that WROLPi never ships or uses; skipping them cuts build time and,
|
|
# importantly on low-RAM devices, peak memory.
|
|
GENERATE_SOURCEMAP=false BROWSERSLIST_IGNORE_OLD_DATA=true npm run build
|
|
# Stamp lives inside build/ so it is naturally absent whenever build/ is, and
|
|
# travels with the baked artifact into the image.
|
|
app_build_stamp > build/.build-stamp
|
|
echo "app build complete; wrote build/.build-stamp."
|
|
else
|
|
echo "app build is current -- skipping (stamp match)."
|
|
fi
|