mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
installer v2: single-stage slot install; slotmigrate legacy reclaim
install-forgefirm.sh is now single-stage and never repartitions: run from factory firmware, it archives every factory slot version plus the recovery boot partitions to /data/forgefirm/archive (manifest with md5s), verifies the signed forgefirm.fw against the embedded ForgeFIRM pubkey (raw 32-byte form for the factory's fwup 0.14.2; dev key until the production key ceremony), applies it to the INACTIVE slot with the factory's own fwup, post-verifies the written rootfs, installs /data/ffboot, and flips the saved env with read-back verification. The booted factory slot stays installed and bootable; /data is untouched beyond the archive. Fixed release asset name forgefirm.fw (version in the fwup metadata and release tag). slotmigrate (new recipe, rcS before mountall) reclaims the legacy layout on eMMC-slot boots: deletes p4, grows p3 to the end of the disk (sfdisk + partx BLKPG - works with a sibling partition as root), then e2fsck+resize2fs. Every step is keyed off the actual disk state, so interrupted runs resume and factory-layout disks are a no-op; SD boots never touch the eMMC.
This commit is contained in:
+183
-138
@@ -4,36 +4,92 @@
|
||||
# https://community.openglow.org
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# This program makes a partition on the built-in eMMC flash storage on
|
||||
# the factory Glowforge, and installs the latest copy of OpenGlow/ForgeFIRM.
|
||||
# Single-stage OpenGlow/ForgeFIRM installer. Runs on FACTORY firmware:
|
||||
# 1. archives the factory rootfs slots and the recovery boot partitions
|
||||
# to /data/forgefirm/archive (offline factory restore, forever),
|
||||
# 2. applies the signed ForgeFIRM .fw to the INACTIVE rootfs slot using
|
||||
# the factory's own fwup (signature-verified),
|
||||
# 3. flips the saved U-Boot environment to the new slot and reboots.
|
||||
# The factory /data partition is never repartitioned or modified beyond
|
||||
# the archive directory; the active factory slot stays bootable.
|
||||
#
|
||||
# Usage: install-forgefirm.sh [local-forgefirm.fw]
|
||||
# With no argument the latest release .fw is downloaded from GitHub.
|
||||
|
||||
RELEASE_FW_URL="https://github.com/ScottW514/forgefirm/releases/latest/download/forgefirm.fw"
|
||||
FFBOOT_URL="https://raw.githubusercontent.com/ScottW514/forgefirm/master/scripts/ffboot"
|
||||
ARCHIVE_DIR="/data/forgefirm/archive"
|
||||
FW_FILE="/data/forgefirm/forgefirm.fw"
|
||||
MIN_DATA_FREE_KB=300000
|
||||
|
||||
# ForgeFIRM release-signing public key (raw 32-byte Ed25519, the format the
|
||||
# factory's fwup 0.14.2 expects).
|
||||
# !! DEV KEY - must be replaced at the production key ceremony !!
|
||||
PUBKEY='\x79\xf5\xc2\x53\x45\x13\x49\x51\xd4\x63\x17\x9d\x60\xd7\x7a\x97\xa7\xd6\xd6\xf4\xd8\x9f\x9d\xbd\x8f\xcc\x28\xfc\xba\xa0\x5d\x11'
|
||||
|
||||
LIGHTRED="\033[1;31m"
|
||||
RED="\033[31m"
|
||||
GREEN="\033[32m"
|
||||
YELLOW="\033[1;33m"
|
||||
BRIGHT="\033[1;39m"
|
||||
RESET="\033[0m"
|
||||
ASTERISK="${LIGHTRED}✺${RESET}"
|
||||
|
||||
# Abort loudly instead of completing silently broken: a failed step (image
|
||||
# download, flash write, uEnv rewrite) must stop the install.
|
||||
die () {
|
||||
echo
|
||||
echo -e "${LIGHTRED}!! INSTALL FAILED:${RESET} $1"
|
||||
echo -e "${LIGHTRED}!! The device was NOT fully installed. Fix the problem and re-run.${RESET}"
|
||||
echo -e "${LIGHTRED}!! No boot change was made unless stated otherwise. Fix and re-run.${RESET}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
stop_gf_services () {
|
||||
echo -n -e "${ASTERISK}Stopping Glowforge services"
|
||||
sv stop /sv/glowforge 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/glowforge/log 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/glowforge-datalogger 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/glowforge-datalogger/log 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/glowforge-updater 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/glowforge-updater/log 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/bugeggs 2>&1 >/dev/null; echo -n "."
|
||||
sv stop /sv/bugeggs/log 2>&1 >/dev/null; echo "."
|
||||
for SVC in glowforge glowforge-datalogger glowforge-updater bugeggs; do
|
||||
sv stop /sv/$SVC 2>/dev/null >/dev/null; echo -n "."
|
||||
sv stop /sv/$SVC/log 2>/dev/null >/dev/null; echo -n "."
|
||||
done
|
||||
echo "."
|
||||
}
|
||||
|
||||
# slot_probe <1|2>: sets S_TYPE (factory|forgefirm|unknown) and S_VER
|
||||
slot_probe () {
|
||||
S_TYPE=unknown; S_VER=""
|
||||
MP="/factory/img$1"
|
||||
if [ ! -f "$MP/etc/version" ] && [ ! -f "$MP/etc/forgefirm-version" ]; then
|
||||
mkdir -p "$MP"
|
||||
mount -o ro "/dev/mmcblk2p$1" "$MP" 2>/dev/null
|
||||
fi
|
||||
if [ -f "$MP/etc/forgefirm-version" ]; then
|
||||
S_TYPE=forgefirm; S_VER=$(cat "$MP/etc/forgefirm-version")
|
||||
elif [ -f "$MP/etc/version" ]; then
|
||||
S_TYPE=factory; S_VER=$(cat "$MP/etc/version")
|
||||
fi
|
||||
}
|
||||
|
||||
# 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).
|
||||
FWCONFIG="/etc/fw_env.config"
|
||||
|
||||
env_get () { fw_printenv -c "$FWCONFIG" -n "$1" 2>/dev/null; }
|
||||
|
||||
env_verify () {
|
||||
[ "$(env_get mmcdev)" = "$1" ] && [ "$(env_get mmchwpart)" = "$2" ] && \
|
||||
[ "$(env_get mmcpart)" = "$3" ] && [ "$(env_get mmcroot)" = "$4" ]
|
||||
}
|
||||
|
||||
set_env () {
|
||||
SCRIPT="/tmp/ffinstall.env.$$"
|
||||
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
|
||||
printf 'mmcdev=%s\nmmchwpart=%s\nmmcpart=%s\nmmcroot=%s\n' "$1" "$2" "$3" "$4" > "$SCRIPT"
|
||||
fw_setenv -c "$FWCONFIG" -s "$SCRIPT" 2>/dev/null
|
||||
rm -f "$SCRIPT"
|
||||
if env_verify "$1" "$2" "$3" "$4"; then return 0; fi
|
||||
fw_setenv -c "$FWCONFIG" mmcdev "$1" && \
|
||||
fw_setenv -c "$FWCONFIG" mmchwpart "$2" && \
|
||||
fw_setenv -c "$FWCONFIG" mmcpart "$3" && \
|
||||
fw_setenv -c "$FWCONFIG" mmcroot "$4"
|
||||
env_verify "$1" "$2" "$3" "$4"
|
||||
}
|
||||
|
||||
echo
|
||||
@@ -41,135 +97,124 @@ echo -e "${LIGHTRED} ✺┈┈┈┈┈┈${RESET}"
|
||||
echo -e "${BRIGHT}Open${RESET}Glow ForgeFIRM Installation Tool"
|
||||
echo
|
||||
|
||||
if [ -d "/factory" ]; then
|
||||
echo -e "${YELLOW}!! This script must be ran from the factory firmware !!${RESET}"
|
||||
exit 2
|
||||
# --- pre-flight ---------------------------------------------------------------
|
||||
[ -f /etc/version ] && [ -d /glowforge ] \
|
||||
|| die "this script must be run from the FACTORY firmware"
|
||||
command -v fwup >/dev/null || die "fwup not found on this system"
|
||||
command -v fw_setenv >/dev/null || die "fw_setenv not found on this system"
|
||||
[ -f "$FWCONFIG" ] || die "$FWCONFIG not found"
|
||||
|
||||
BOOTED=$(sed -n 's/.*root=\([^ ]*\).*/\1/p' /proc/cmdline)
|
||||
case "$BOOTED" in
|
||||
/dev/mmcblk2p1) ACTIVE=1; TARGET=2; TASK=upgrade.b ;;
|
||||
/dev/mmcblk2p2) ACTIVE=2; TARGET=1; TASK=upgrade.a ;;
|
||||
*) die "booted from $BOOTED - expected factory eMMC slot 1 or 2" ;;
|
||||
esac
|
||||
|
||||
for N in 1 2; do
|
||||
SZ=$(cat /sys/class/block/mmcblk2p$N/size 2>/dev/null)
|
||||
[ "$SZ" = "409600" ] || die "slot $N is not the 200 MiB factory layout (size=$SZ)"
|
||||
done
|
||||
|
||||
FREE_KB=$(df -k /data | tail -1 | awk '{print $4}')
|
||||
[ "$FREE_KB" -ge "$MIN_DATA_FREE_KB" ] 2>/dev/null \
|
||||
|| die "need ${MIN_DATA_FREE_KB} KB free on /data, have ${FREE_KB:-unknown}"
|
||||
|
||||
echo -e "${LIGHTRED}!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!${RESET}"
|
||||
echo -e "${YELLOW} THIS IS EXPERIMENTAL SOFTWARE!${RESET}"
|
||||
echo -e "The installation and/or use of this software may"
|
||||
echo -e "result in damage to your device and/or property,"
|
||||
echo -e "loss of warranty, and severe bodily injury and/or"
|
||||
echo -e "death. This software is not affiliated with or"
|
||||
echo -e "endorsed by Glowforge. ${BRIGHT}USE IT AT YOUR OWN RISK!${RESET}"
|
||||
echo -e "${LIGHTRED}!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!${RESET}"
|
||||
echo
|
||||
echo -e "Booted slot: $ACTIVE - ForgeFIRM will be installed to slot $TARGET."
|
||||
echo -e "The factory firmware in slot $ACTIVE stays installed and bootable."
|
||||
echo
|
||||
read -p "Are you sure you want to continue [N/y]? " continue
|
||||
echo
|
||||
if [ "$continue" != "y" ]; then
|
||||
echo "Wise choice. Exiting without changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -d "/ogtmp" ]; then
|
||||
# We're resuming after reboot
|
||||
echo -e "${BRIGHT}Starting Stage 2:${RESET}"
|
||||
stop_gf_services
|
||||
|
||||
echo 0 > /proc/sys/kernel/printk
|
||||
stop_gf_services
|
||||
|
||||
echo -n -e "${ASTERISK}Restoring /data files"
|
||||
mount -o remount,rw /
|
||||
rm -rf /data/etc; echo -n "."
|
||||
mv /ogtmp/etc /data/; echo -n "."
|
||||
mv /ogtmp/data_pipe.bin /data/; echo -n "."
|
||||
mv /ogtmp/dropbear_rsa_host_key /data/; echo -n "."
|
||||
rm -rf /ogtmp; echo "."
|
||||
|
||||
echo -n -e "${ASTERISK}Restarting network services."
|
||||
/etc/init.d/networking restart 2>&1 >/dev/null
|
||||
for VAR in 1 2 3 4 5; do
|
||||
sleep 2
|
||||
echo -n "."
|
||||
done
|
||||
echo
|
||||
|
||||
echo -e "${ASTERISK}Downloading latest OpenGlow/ForgeFIRM image:"
|
||||
# Asset name matches the Scarthgap deploy artifact verbatim (rootfs.wic.gz),
|
||||
# so releases are uploaded without renaming (see kas/README.md release order).
|
||||
curl -fL https://github.com/ScottW514/forgefirm/releases/latest/download/forgefirm-image-glowforge.rootfs.wic.gz --output /data/forgefirm-image-glowforge.rootfs.wic.gz \
|
||||
|| die "image download failed"
|
||||
curl -fL https://raw.githubusercontent.com/ScottW514/forgefirm/master/scripts/ffboot --output /data/ffboot \
|
||||
|| die "ffboot download failed"
|
||||
chmod +x /data/ffboot
|
||||
echo
|
||||
|
||||
echo -e "${ASTERISK}Writing OpenGlow/ForegFIRM image to flash:"
|
||||
zcat /data/forgefirm-image-glowforge.rootfs.wic.gz | dd of=/dev/mmcblk2p4 skip=2 \
|
||||
|| die "flash write to /dev/mmcblk2p4 failed"
|
||||
mkdir -p /data/mnt
|
||||
mount /dev/mmcblk2p4 /data/mnt || die "mounting the new image failed (bad flash write?)"
|
||||
[ -f /data/mnt/boot/uEnv.txt ] \
|
||||
|| { umount /data/mnt; die "/boot/uEnv.txt missing from the new image (bad or outdated image)"; }
|
||||
sed -i 's/mmcblk1p1/mmcblk2p4/g' /data/mnt/boot/uEnv.txt \
|
||||
|| { umount /data/mnt; die "rewriting uEnv.txt for eMMC boot failed"; }
|
||||
umount /data/mnt || die "unmounting the new image failed"
|
||||
rm -rf /data/mnt
|
||||
rm -f /data/forgefirm-image-glowforge.rootfs.wic.gz
|
||||
|
||||
echo 5 > /proc/sys/kernel/printk
|
||||
|
||||
echo -e "${BRIGHT}Stage 2 Complete!${RESET}"
|
||||
echo
|
||||
read -n1 -p "Press any key to reboot into OpenGlow/ForgeFIRM..." continue
|
||||
/data/ffboot -e4
|
||||
else
|
||||
# We're starting fresh
|
||||
echo -e "${LIGHTRED}!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!${RESET}"
|
||||
echo -e "${YELLOW} THIS IS EXPERIMENTAL SOFTWARE!${RESET}"
|
||||
echo -e "The installation and/or use of this software may"
|
||||
echo -e "result in damage to your device and/or property,"
|
||||
echo -e "loss of warranty, and severe bodily injury and/or"
|
||||
echo -e "death. This software is not affiliated with or"
|
||||
echo -e "endorsed by Glowforge. ${BRIGHT}USE IT AT YOUR OWN RISK!${RESET}"
|
||||
echo -e "${LIGHTRED}!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!${RESET}"
|
||||
echo
|
||||
read -p "Are you sure you want to continue [N/y]? " continue
|
||||
echo
|
||||
if [ "$continue" != "y" ]; then
|
||||
echo "Wise choice. Exiting without changes."
|
||||
exit 0
|
||||
# --- archive factory content --------------------------------------------------
|
||||
mkdir -p "$ARCHIVE_DIR"
|
||||
for N in 1 2; do
|
||||
slot_probe $N
|
||||
[ "$S_TYPE" = "factory" ] || continue
|
||||
ARC="$ARCHIVE_DIR/factory-rootfs-$S_VER.img.gz"
|
||||
if [ -s "$ARC" ]; then
|
||||
echo -e "${ASTERISK}Slot $N (factory $S_VER) already archived."
|
||||
continue
|
||||
fi
|
||||
echo -e "${BRIGHT}Starting Stage 1:${RESET}"
|
||||
echo -e "${ASTERISK}Archiving slot $N (factory $S_VER) - takes a few minutes:"
|
||||
dd if=/dev/mmcblk2p$N bs=1M 2>/dev/null | gzip -1 > "$ARC" \
|
||||
|| { rm -f "$ARC"; die "archiving slot $N failed"; }
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') slot$N factory $S_VER $(basename $ARC) md5=$(md5sum "$ARC" | cut -d' ' -f1)" >> "$ARCHIVE_DIR/manifest"
|
||||
done
|
||||
for B in 0 1; do
|
||||
ARC="$ARCHIVE_DIR/recovery-boot$B.img.gz"
|
||||
[ -s "$ARC" ] && continue
|
||||
echo -e "${ASTERISK}Archiving recovery boot$B:"
|
||||
dd if=/dev/mmcblk2boot$B bs=1M 2>/dev/null | gzip -1 > "$ARC" \
|
||||
|| { rm -f "$ARC"; die "archiving boot$B failed"; }
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') boot$B recovery - $(basename $ARC) md5=$(md5sum "$ARC" | cut -d' ' -f1)" >> "$ARCHIVE_DIR/manifest"
|
||||
done
|
||||
|
||||
echo 0 > /proc/sys/kernel/printk
|
||||
stop_gf_services
|
||||
|
||||
echo -n -e "${ASTERISK}Backing up critical files on /data partition"
|
||||
mount -o remount,rw /; echo -n "."
|
||||
mkdir /ogtmp; echo -n "."
|
||||
cp -R /data/etc /ogtmp/; echo -n "."
|
||||
cp /data/data_pipe.bin /ogtmp/; echo -n "."
|
||||
cp /data/dropbear_rsa_host_key /ogtmp/; echo "."
|
||||
|
||||
echo -e "${ASTERISK}Creating partitions (ignore fdisk warning):"
|
||||
umount -l /dev/mmcblk2p3
|
||||
sed -e 's/\s*\([\+0-9a-zA-Z]*\).*/\1/' << EOF | fdisk /dev/mmcblk2 2>&1 >/dev/null
|
||||
o # Removing existing partitions
|
||||
n # New partition
|
||||
p # Primary partition
|
||||
1 # Creating new /data partition
|
||||
129 # Start at the end of partition 2
|
||||
6528 # 2GB /data parttion
|
||||
n # New partition
|
||||
p # Primary partition
|
||||
2 # Creating new /data partition
|
||||
6529 # Start at the end of partition 2
|
||||
12928 # 2GB /data parttion
|
||||
n # New partition
|
||||
p # Primary partition
|
||||
3 # Creating new /data partition
|
||||
12929 # Start at the end of partition 2
|
||||
+2048M # 2GB /data parttion
|
||||
n # New partition for OpenGlow
|
||||
p # Primary partition (Will automatically select 4)
|
||||
75430 # Start at the end of partition 3
|
||||
# default, Use remaining space
|
||||
w # write the partition table
|
||||
EOF
|
||||
|
||||
# We need to clear the original partition of filesystem data
|
||||
# This way the Glowforge will properly recreate the /data partition
|
||||
# on the next reboot.
|
||||
dd if=/dev/zero of=/dev/mmcblk2p3 bs=4096 count=4096 2>&1 >/dev/null
|
||||
|
||||
echo -e "${BRIGHT}Completed Stage 1${RESET}"
|
||||
echo
|
||||
echo -e "The device needs to reboot."
|
||||
echo -e "After the device has rebooted, run this script"
|
||||
echo -e "to continue the installation process."
|
||||
echo
|
||||
echo 5 > /proc/sys/kernel/printk
|
||||
read -n1 -p "Press any key to reboot..." continue
|
||||
echo
|
||||
echo "Rebooting..."
|
||||
reboot
|
||||
# --- acquire the ForgeFIRM .fw ------------------------------------------------
|
||||
mkdir -p /data/forgefirm
|
||||
if [ -n "$1" ]; then
|
||||
[ -s "$1" ] || die "local firmware file '$1' not found"
|
||||
cp "$1" "$FW_FILE"
|
||||
echo -e "${ASTERISK}Using local firmware file: $1"
|
||||
else
|
||||
echo -e "${ASTERISK}Downloading latest OpenGlow/ForgeFIRM release:"
|
||||
curl -fL "$RELEASE_FW_URL" --output "$FW_FILE" || die "firmware download failed"
|
||||
fi
|
||||
|
||||
# --- verify signature ---------------------------------------------------------
|
||||
KEYFILE="/tmp/forgefirm.pub.$$"
|
||||
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
|
||||
|
||||
# --- apply to the inactive slot -----------------------------------------------
|
||||
echo -e "${ASTERISK}Writing ForgeFIRM to slot $TARGET (/dev/mmcblk2p$TARGET):"
|
||||
umount "/factory/img$TARGET" 2>/dev/null
|
||||
fwup -a -d "/dev/mmcblk2p$TARGET" -i "$FW_FILE" -t "$TASK" -p "$KEYFILE" \
|
||||
|| { rm -f "$KEYFILE"; die "fwup apply failed - slot $TARGET is now undefined, factory slot $ACTIVE is untouched"; }
|
||||
rm -f "$KEYFILE"
|
||||
|
||||
# --- post-write verify --------------------------------------------------------
|
||||
MP="/factory/img$TARGET"
|
||||
mkdir -p "$MP"
|
||||
mount -o ro "/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"; }
|
||||
[ -f "$MP/boot/zImage" ] || { umount "$MP"; die "new rootfs has no kernel"; }
|
||||
umount "$MP"
|
||||
echo -e "${ASTERISK}Slot $TARGET now holds ForgeFIRM $NEWVER"
|
||||
|
||||
# --- ffboot for the factory side ----------------------------------------------
|
||||
curl -fL "$FFBOOT_URL" --output /data/ffboot || die "ffboot download failed"
|
||||
chmod +x /data/ffboot
|
||||
|
||||
# --- flip the boot selection --------------------------------------------------
|
||||
echo -e "${ASTERISK}Setting boot to /dev/mmcblk2p$TARGET"
|
||||
set_env 1 0 "$TARGET" "/dev/mmcblk2p$TARGET" \
|
||||
|| die "environment write did not verify - boot selection unchanged; run /data/ffboot -e$TARGET manually"
|
||||
|
||||
echo
|
||||
echo -e "${BRIGHT}Installation complete.${RESET}"
|
||||
echo -e "To return to factory firmware later: ${BRIGHT}/data/ffboot -e${RESET} (from ForgeFIRM: ${BRIGHT}ffboot -e${RESET})"
|
||||
echo
|
||||
read -n1 -p "Press any key to reboot into OpenGlow/ForgeFIRM..." continue
|
||||
echo
|
||||
reboot
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user