mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 01:01:12 -07:00
Build and release engineering: teardown order, slot safety, release gates
- Controllers stop at K80, before forgectrl at K90: runlevel 0/6 no longer tears down the cooling engine, fire gates, and broker while a controller may still be executing a job. - The grblhal/gfcloud init scripts are real emergency levers: stop routes through the supervisor (POST /controller/stop - a bare pkill was safed and respawned seconds later), start resumes supervision, status exists, and the pkill fallback matches full executable paths instead of truncated names or bare substrings. - slotmigrate: the partition grow gets the same 2048-sector tolerance as the filesystem branch (an exact compare rewrote the MBR at S02 on every boot on disks where the grow cannot land on the last sector), verifies it made progress, and the resize2fs retry is bounded at three attempts with the counter kept on p3 itself. - Installer: archive product/platform are verified after the signature, and a validly signed OLDER release now requires an explicit yes instead of installing as a silent downgrade. All predictable /tmp paths in the installer and ffboot are mktemp now. - release.sh rejects multiple positional versions (the last one used to win silently) and a release without factory-era verification dies unless explicitly bypassed; mkfw.sh refuses to pack when the public key for the post-sign self-check is missing. - forgefirm-logrotate: size-capped rotation (boot + hourly) for the /data logs - a full /data breaks settings, update staging, and the controllers own writes. - Bench build scripts derive every path from their own location or FF_SRC_TOP/FF_BUILD_TOP and log to mktemp files.
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Cross-compiles the bench feeder for the factory board, borrowing the
|
||||
# Yocto cross toolchain from the ulfius recipe work directory.
|
||||
#
|
||||
# Environment (defaults derive from this script's location, assuming
|
||||
# the standard multi-repo checkout layout):
|
||||
# FF_SRC_TOP checkout holding the sibling repos
|
||||
# FF_BUILD_TOP Yocto build tree (default: $FF_SRC_TOP/forgefirm/build)
|
||||
set -e
|
||||
TC=/home/builder/dev/openglow-forgefirm/forgefirm/build/tmp/work/cortexa9t2hf-neon-fslc-linux-gnueabi/ulfius/2.7.15
|
||||
SP="$(cd "$(dirname "$0")" && pwd)"
|
||||
FF_SRC_TOP="${FF_SRC_TOP:-$(cd "$SP/../../.." && pwd)}"
|
||||
FF_BUILD_TOP="${FF_BUILD_TOP:-$FF_SRC_TOP/forgefirm/build}"
|
||||
TC="$FF_BUILD_TOP/tmp/work/cortexa9t2hf-neon-fslc-linux-gnueabi/ulfius/2.7.15"
|
||||
[ -d "$TC/recipe-sysroot" ] || { echo "toolchain not staged at $TC (run: bitbake ulfius)"; exit 1; }
|
||||
"$TC/recipe-sysroot-native/usr/bin/arm-fslc-linux-gnueabi/arm-fslc-linux-gnueabi-gcc" \
|
||||
--sysroot="$TC/recipe-sysroot" \
|
||||
-mthumb -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a9 \
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
#!/bin/bash
|
||||
# Cross-compiles forgectrl (the canonical repo, ../../../forgectrl -> synced
|
||||
# to ~/dev/openglow-forgefirm/forgectrl) for the factory board, borrowing the
|
||||
# Yocto cross toolchain + sysroot from the forgectrl recipe work directory
|
||||
# (which carries ulfius and libjpeg). If that path ages out after a clean,
|
||||
# regenerate it with: bitbake forgectrl. Run from PowerShell:
|
||||
# wsl -d forge-yocto -- bash <path>/build-forgectrl.sh
|
||||
# Cross-compiles forgectrl (the sibling repo) for the factory board,
|
||||
# borrowing the Yocto cross toolchain + sysroot from the forgectrl
|
||||
# recipe work directory (which carries ulfius and libjpeg). If that
|
||||
# path ages out after a clean, regenerate it with: bitbake forgectrl.
|
||||
#
|
||||
# Environment (defaults derive from this script's location, assuming
|
||||
# the standard multi-repo checkout layout):
|
||||
# FF_SRC_TOP checkout holding the sibling repos
|
||||
# FF_BUILD_TOP Yocto build tree (default: $FF_SRC_TOP/forgefirm/build)
|
||||
set -e
|
||||
TC=/home/builder/dev/openglow-forgefirm/forgefirm/build/tmp/work/cortexa9t2hf-neon-fslc-linux-gnueabi/forgectrl/0.1.0
|
||||
SP="$(cd "$(dirname "$0")" && pwd)"
|
||||
FF_SRC_TOP="${FF_SRC_TOP:-$(cd "$SP/../../.." && pwd)}"
|
||||
FF_BUILD_TOP="${FF_BUILD_TOP:-$FF_SRC_TOP/forgefirm/build}"
|
||||
TC="$FF_BUILD_TOP/tmp/work/cortexa9t2hf-neon-fslc-linux-gnueabi/forgectrl/0.1.0"
|
||||
[ -d "$TC/recipe-sysroot" ] || { echo "toolchain not staged at $TC (run: bitbake forgectrl)"; exit 1; }
|
||||
export PATH="$TC/recipe-sysroot-native/usr/bin:$TC/recipe-sysroot-native/usr/bin/arm-fslc-linux-gnueabi:$PATH"
|
||||
cd /home/builder/dev/openglow-forgefirm/forgectrl
|
||||
LOG=$(mktemp -t fcbuild.XXXXXX)
|
||||
cd "$FF_SRC_TOP/forgectrl"
|
||||
rm -rf build-arm
|
||||
cmake -B build-arm \
|
||||
-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=arm \
|
||||
@@ -16,7 +24,8 @@ cmake -B build-arm \
|
||||
-DCMAKE_BUILD_TYPE=None \
|
||||
"-DCMAKE_C_FLAGS=--sysroot=$TC/recipe-sysroot -mthumb -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a9 -O2 -g" \
|
||||
"-DCMAKE_EXE_LINKER_FLAGS=--sysroot=$TC/recipe-sysroot" \
|
||||
> /tmp/cmake-fc.log 2>&1
|
||||
cmake --build build-arm -j8 > /tmp/fcbuild.log 2>&1 || { tail -30 /tmp/fcbuild.log; exit 1; }
|
||||
> "$LOG" 2>&1
|
||||
cmake --build build-arm -j8 >> "$LOG" 2>&1 || { tail -30 "$LOG"; exit 1; }
|
||||
rm -f "$LOG"
|
||||
echo BUILD-OK
|
||||
file build-arm/forgectrl 2>/dev/null || ls build-arm/
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
#!/bin/bash
|
||||
# Cross-compiles grblHAL-glowforge (the canonical driver repo) for the
|
||||
# factory board, borrowing the Yocto cross toolchain from the ulfius work
|
||||
# directory. Run from PowerShell: wsl -d forge-yocto -- bash <path>.
|
||||
# Cross-compiles grblHAL-glowforge (the sibling driver repo) for the
|
||||
# factory board, borrowing the Yocto cross toolchain from the ulfius
|
||||
# recipe work directory. Regenerate a cleaned toolchain path with:
|
||||
# bitbake ulfius.
|
||||
#
|
||||
# Environment (defaults derive from this script's location, assuming
|
||||
# the standard multi-repo checkout layout):
|
||||
# FF_SRC_TOP checkout holding the sibling repos
|
||||
# FF_BUILD_TOP Yocto build tree (default: $FF_SRC_TOP/forgefirm/build)
|
||||
set -e
|
||||
TC=/home/builder/dev/openglow-forgefirm/forgefirm/build/tmp/work/cortexa9t2hf-neon-fslc-linux-gnueabi/ulfius/2.7.15
|
||||
SP="$(cd "$(dirname "$0")" && pwd)"
|
||||
FF_SRC_TOP="${FF_SRC_TOP:-$(cd "$SP/../../.." && pwd)}"
|
||||
FF_BUILD_TOP="${FF_BUILD_TOP:-$FF_SRC_TOP/forgefirm/build}"
|
||||
TC="$FF_BUILD_TOP/tmp/work/cortexa9t2hf-neon-fslc-linux-gnueabi/ulfius/2.7.15"
|
||||
[ -d "$TC/recipe-sysroot" ] || { echo "toolchain not staged at $TC (run: bitbake ulfius)"; exit 1; }
|
||||
export PATH="$TC/recipe-sysroot-native/usr/bin:$TC/recipe-sysroot-native/usr/bin/arm-fslc-linux-gnueabi:$PATH"
|
||||
cd /home/builder/dev/openglow-forgefirm/grblHAL-glowforge
|
||||
LOG=$(mktemp -t gfbuild.XXXXXX)
|
||||
cd "$FF_SRC_TOP/grblHAL-glowforge"
|
||||
rm -rf build-arm
|
||||
cmake -B build-arm \
|
||||
-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=arm \
|
||||
@@ -13,7 +24,8 @@ cmake -B build-arm \
|
||||
-DCMAKE_BUILD_TYPE=None \
|
||||
"-DCMAKE_C_FLAGS=--sysroot=$TC/recipe-sysroot -mthumb -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a9 -O1 -g" \
|
||||
"-DCMAKE_EXE_LINKER_FLAGS=--sysroot=$TC/recipe-sysroot" \
|
||||
> /tmp/cmake-gf.log 2>&1
|
||||
cmake --build build-arm -j8 > /tmp/gfbuild.log 2>&1 || { tail -30 /tmp/gfbuild.log; exit 1; }
|
||||
> "$LOG" 2>&1
|
||||
cmake --build build-arm -j8 >> "$LOG" 2>&1 || { tail -30 "$LOG"; exit 1; }
|
||||
rm -f "$LOG"
|
||||
echo BUILD-OK
|
||||
file build-arm/grblHAL_glowforge 2>/dev/null || ls build-arm/
|
||||
|
||||
+2
-3
@@ -61,9 +61,8 @@ probe_part () {
|
||||
# each foreign-type claim against an already-mounted device.
|
||||
ROOT_DIR=$(sed -n "s|^$1 \([^ ]*\).*|\1|p" /proc/mounts | head -n 1)
|
||||
if [ -z "$ROOT_DIR" ]; then
|
||||
ROOT_DIR="/tmp/ffboot.probe.$$"
|
||||
P_MOUNTED=yes
|
||||
mkdir -p "$ROOT_DIR"
|
||||
ROOT_DIR=$(mktemp -d /tmp/ffboot.probe.XXXXXX) || return 1
|
||||
if ! mount -o ro -t ext4 "$1" "$ROOT_DIR" 2>/dev/null; then
|
||||
rmdir "$ROOT_DIR" 2>/dev/null
|
||||
return 1
|
||||
@@ -132,7 +131,7 @@ env_verify () {
|
||||
}
|
||||
|
||||
set_env () {
|
||||
SCRIPT="/tmp/ffboot.env.$$"
|
||||
SCRIPT=$(mktemp /tmp/ffboot.env.XXXXXX) || return 1
|
||||
# libubootenv format
|
||||
printf 'mmcdev=%s\nmmchwpart=%s\nmmcpart=%s\nmmcroot=%s\n' "$1" "$2" "$3" "$4" > "$SCRIPT"
|
||||
fw_setenv -c "$FWCONFIG" -s "$SCRIPT" 2>/dev/null
|
||||
|
||||
@@ -52,7 +52,7 @@ stop_gf_services () {
|
||||
# dd's exit status is captured via a file so a read failure is not
|
||||
# masked by gzip succeeding on truncated input.
|
||||
archive_dev () {
|
||||
RC_FILE="/tmp/ffinstall.rc.$$"
|
||||
RC_FILE=$(mktemp /tmp/ffinstall.rc.XXXXXX) || return 1
|
||||
rm -f "$RC_FILE"
|
||||
( dd if="$1" bs=1M 2>/dev/null; echo $? > "$RC_FILE" ) | gzip -1 > "$2" &
|
||||
GZPID=$!
|
||||
@@ -83,9 +83,8 @@ slot_probe () {
|
||||
else
|
||||
RD=$(sed -n "s|^/dev/mmcblk2p$1 \([^ ]*\).*|\1|p" /proc/mounts | head -n 1)
|
||||
if [ -z "$RD" ]; then
|
||||
RD="/tmp/ffinstall.probe.$$"
|
||||
S_MOUNTED=yes
|
||||
mkdir -p "$RD" || return 1
|
||||
RD=$(mktemp -d /tmp/ffinstall.probe.XXXXXX) || return 1
|
||||
mount -o ro -t ext4 "/dev/mmcblk2p$1" "$RD" 2>/dev/null \
|
||||
|| { rmdir "$RD" 2>/dev/null; S_TYPE=unknown; return 0; }
|
||||
fi
|
||||
@@ -116,6 +115,25 @@ slot_desc () {
|
||||
esac
|
||||
}
|
||||
|
||||
# ver_lt A B: true when semantic version A < B (leading v ignored).
|
||||
# Returns false on any non-numeric component (e.g. a dev datetime
|
||||
# stamp) - no verdict means no downgrade prompt, never a refusal.
|
||||
ver_lt () {
|
||||
VA=${1#v}; VB=${2#v}
|
||||
[ "$VA" = "$VB" ] && return 1
|
||||
VI=1
|
||||
while [ "$VI" -le 3 ]; do
|
||||
A=$(echo "$VA" | cut -d. -f$VI)
|
||||
B=$(echo "$VB" | cut -d. -f$VI)
|
||||
A=${A:-0}; B=${B:-0}
|
||||
case "$A$B" in *[!0-9]*) return 1 ;; esac
|
||||
[ "$A" -lt "$B" ] && return 0
|
||||
[ "$A" -gt "$B" ] && return 1
|
||||
VI=$((VI + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Verified atomic env flip (all four variables, classic u-boot-tools script
|
||||
# format first - that is what factory firmware ships - then libubootenv
|
||||
# format, then per-variable writes; read-back verified in every case).
|
||||
@@ -136,7 +154,7 @@ env_verify () {
|
||||
}
|
||||
|
||||
set_env () {
|
||||
SCRIPT="/tmp/ffinstall.env.$$"
|
||||
SCRIPT=$(mktemp /tmp/ffinstall.env.XXXXXX) || return 1
|
||||
printf 'mmcdev %s\nmmchwpart %s\nmmcpart %s\nmmcroot %s\n' "$1" "$2" "$3" "$4" > "$SCRIPT"
|
||||
fw_setenv -c "$FWCONFIG" -s "$SCRIPT" 2>/dev/null
|
||||
if env_verify "$1" "$2" "$3" "$4"; then rm -f "$SCRIPT"; return 0; fi
|
||||
@@ -257,12 +275,43 @@ else
|
||||
fi
|
||||
|
||||
# --- verify signature ---------------------------------------------------------
|
||||
KEYFILE="/tmp/forgefirm.pub.$$"
|
||||
KEYFILE=$(mktemp /tmp/forgefirm.pub.XXXXXX) || die "cannot create temp file"
|
||||
printf "$PUBKEY" > "$KEYFILE"
|
||||
[ "$(wc -c < "$KEYFILE")" = "32" ] || die "embedded public key corrupt"
|
||||
echo -e "${ASTERISK}Verifying firmware signature:"
|
||||
fwup -V -i "$FW_FILE" -p "$KEYFILE" || { rm -f "$KEYFILE"; die "signature verification FAILED - refusing to install"; }
|
||||
fwup -m -i "$FW_FILE" | grep meta-version
|
||||
|
||||
# --- archive identity + downgrade gate ----------------------------------------
|
||||
META=$(fwup -m -i "$FW_FILE")
|
||||
M_PRODUCT=$(echo "$META" | sed -n 's/^meta-product="\(.*\)"$/\1/p')
|
||||
M_PLATFORM=$(echo "$META" | sed -n 's/^meta-platform="\(.*\)"$/\1/p')
|
||||
M_VERSION=$(echo "$META" | sed -n 's/^meta-version="\(.*\)"$/\1/p')
|
||||
[ "$M_PRODUCT" = "ForgeFIRM firmware" ] \
|
||||
|| { rm -f "$KEYFILE"; die "archive product is '$M_PRODUCT', not ForgeFIRM firmware - wrong archive"; }
|
||||
[ "$M_PLATFORM" = "glowforge" ] \
|
||||
|| { rm -f "$KEYFILE"; die "archive platform is '$M_PLATFORM', not glowforge - wrong archive"; }
|
||||
echo -e "${ASTERISK}Archive: $M_PRODUCT $M_VERSION ($M_PLATFORM)"
|
||||
|
||||
# A validly signed OLDER release must never install silently; downgrades
|
||||
# need an explicit yes (rollback stays possible, just deliberate).
|
||||
INSTALLED=""
|
||||
for S in 1 2; do
|
||||
slot_probe "$S"
|
||||
if [ "$S_TYPE" = "forgefirm" ] && [ -n "$S_VER" ]; then
|
||||
if [ -z "$INSTALLED" ] || ver_lt "$INSTALLED" "$S_VER"; then
|
||||
INSTALLED="$S_VER"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -n "$INSTALLED" ] && ver_lt "$M_VERSION" "$INSTALLED"; then
|
||||
echo -e "${ASTERISK}This archive ($M_VERSION) is OLDER than the installed ForgeFIRM ($INSTALLED)."
|
||||
read -n1 -p "Install the downgrade anyway? [y/N] " YN
|
||||
echo
|
||||
case "$YN" in
|
||||
y|Y) ;;
|
||||
*) rm -f "$KEYFILE"; die "downgrade declined" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# --- apply to the inactive slot -----------------------------------------------
|
||||
echo -e "${ASTERISK}Writing ForgeFIRM to slot $TARGET (/dev/mmcblk2p$TARGET):"
|
||||
@@ -274,8 +323,7 @@ fwup -a -d "/dev/mmcblk2p$TARGET" -i "$FW_FILE" -t "$TASK" -p "$KEYFILE" \
|
||||
rm -f "$KEYFILE"
|
||||
|
||||
# --- post-write verify --------------------------------------------------------
|
||||
MP="/tmp/ffinstall.verify.$$"
|
||||
mkdir -p "$MP"
|
||||
MP=$(mktemp -d /tmp/ffinstall.verify.XXXXXX) || die "cannot create temp dir"
|
||||
mount -o ro -t ext4 "/dev/mmcblk2p$TARGET" "$MP" || die "new rootfs does not mount"
|
||||
NEWVER=$(cat "$MP/etc/forgefirm-version" 2>/dev/null)
|
||||
[ -n "$NEWVER" ] || { umount "$MP"; die "new rootfs has no ForgeFIRM version stamp"; }
|
||||
|
||||
+4
-3
@@ -72,10 +72,11 @@ EOF
|
||||
if [ -n "$KEY" ]; then
|
||||
[ -f "$KEY" ] || { echo "ERROR: key '$KEY' not found" >&2; exit 1; }
|
||||
"$FWUP" -S -s "$KEY" -i "$WORK/unsigned.fw" -o "$OUT"
|
||||
# The post-sign self-check is mandatory: packing without it means a
|
||||
# release could ship with a signature nothing ever verified.
|
||||
PUB="${KEY%.priv}.pub"
|
||||
if [ -f "$PUB" ]; then
|
||||
"$FWUP" -V -i "$OUT" -p "$PUB" || { echo "ERROR: signature self-check failed" >&2; exit 1; }
|
||||
fi
|
||||
[ -f "$PUB" ] || { echo "ERROR: public key '$PUB' not found - cannot self-check the signature; refusing to pack unverified" >&2; exit 1; }
|
||||
"$FWUP" -V -i "$OUT" -p "$PUB" || { echo "ERROR: signature self-check failed" >&2; exit 1; }
|
||||
echo "signed: $OUT"
|
||||
else
|
||||
cp "$WORK/unsigned.fw" "$OUT"
|
||||
|
||||
+9
-1
@@ -46,7 +46,11 @@ for ARG in "$@"; do
|
||||
--dev) MODE=dev ;;
|
||||
--publish) PUBLISH=1 ;;
|
||||
-*) die "unknown option $ARG" ;;
|
||||
*) VERSION="$ARG" ;;
|
||||
*)
|
||||
[ -z "$VERSION" ] \
|
||||
|| die "multiple versions given ('$VERSION' and '$ARG')"
|
||||
VERSION="$ARG"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -163,6 +167,10 @@ if [ -n "${FWUP_COMPAT:-}" ]; then
|
||||
|| { rm -f "$RAW"; die "factory-era fwup rejects the archive"; }
|
||||
rm -f "$RAW"
|
||||
echo "factory-era fwup verification OK"
|
||||
elif [ "$MODE" = release ] && [ -z "${FWUP_COMPAT_SKIP:-}" ]; then
|
||||
# The factory-compat guarantee is a release property: a public release
|
||||
# must not skip it silently. FWUP_COMPAT_SKIP=1 bypasses deliberately.
|
||||
die "FWUP_COMPAT not set - factory-era verification is required for a release (set FWUP_COMPAT_SKIP=1 to bypass deliberately)"
|
||||
else
|
||||
warn "FWUP_COMPAT not set - factory-era verification skipped"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user