mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
release.sh: the release pipeline
Gates (clean tree, version single-source across FORGEFIRM_RELEASE / rootfs stamp / .fw meta-version / tag, rootfs-vs-slot size with early warning, installer-embedded pubkey must match the signing key, factory-era fwup verification of the packed archive), then build, pack, sign, checksum, and stage forgefirm.fw + sha256sums.txt + forgefirm-image-glowforge.rootfs.wic.gz with the gh publish command (--publish runs it where gh is authenticated). release.sh --dev packs a dev-key-signed forgefirm-dev.fw from the release rootfs for the GUI upload path. Signing keys are always passed explicitly - no defaults. kas/README release order and the plan doc updated to match.
This commit is contained in:
@@ -9,5 +9,8 @@
|
|||||||
# kas
|
# kas
|
||||||
*.lock.yml.bak
|
*.lock.yml.bak
|
||||||
|
|
||||||
|
# release.sh staging output
|
||||||
|
/release-staging/
|
||||||
|
|
||||||
# Python bytecode
|
# Python bytecode
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
+12
-6
@@ -142,15 +142,21 @@ demonstrably untouched.*
|
|||||||
|
|
||||||
## Phase 3 — release pipeline
|
## Phase 3 — release pipeline
|
||||||
|
|
||||||
- `scripts/release.sh` (build host): kas build → size gate → pack
|
- `scripts/release.sh` (build host): gates → kas build → pack `.fw` →
|
||||||
`.fw` → sign → `sha256sums.txt` → `gh release create` → post-check
|
sign → `sha256sums.txt` → staged assets + `gh release create`
|
||||||
that asset names match what the installer, GUI updater, and (later)
|
command (`--publish` runs it where gh is authenticated). Gates:
|
||||||
recovery expect.
|
clean tree, version single-source, rootfs-vs-slot size
|
||||||
|
(warn ≥ 170 MiB / fail ≥ 195 MiB, under bitbake's own hard cap),
|
||||||
|
**installer-embedded pubkey must match the signing key**, and
|
||||||
|
factory-era fwup (0.14.2) verification of the packed archive.
|
||||||
- One version source: `FORGEFIRM_RELEASE` = git tag =
|
- One version source: `FORGEFIRM_RELEASE` = git tag =
|
||||||
`/etc/forgefirm-version` = `.fw` meta-version; the script enforces
|
`/etc/forgefirm-version` = `.fw` meta-version; the script enforces
|
||||||
agreement.
|
agreement.
|
||||||
- Dev builds emit a `.fw` too (dev-key or unsigned — see open
|
- `release.sh --dev` packs a **dev-key-signed** `forgefirm-dev.fw`
|
||||||
questions) for the GUI upload path.
|
from the release rootfs for the GUI upload path (decides open
|
||||||
|
question 4: dev archives are signed with the dev key, never
|
||||||
|
unsigned — the GUI exercises the same verification path either
|
||||||
|
way).
|
||||||
- GitHub Actions: per-push compile checks for grblHAL-glowforge and
|
- GitHub Actions: per-push compile checks for grblHAL-glowforge and
|
||||||
forgectrl (minutes, no Yocto); optional `workflow_dispatch`
|
forgectrl (minutes, no Yocto); optional `workflow_dispatch`
|
||||||
cold-Yocto reproducibility build whose only product is a checksum.
|
cold-Yocto reproducibility build whose only product is a checksum.
|
||||||
|
|||||||
+10
-4
@@ -109,10 +109,16 @@ config move in the right order. The sequence, with current status:
|
|||||||
base recipe) so a fresh clone is fully self-contained;
|
base recipe) so a fresh clone is fully self-contained;
|
||||||
- refresh `kas lock`, tag all repos, and prove self-containment by building
|
- refresh `kas lock`, tag all repos, and prove self-containment by building
|
||||||
from a **fresh clone**.
|
from a **fresh clone**.
|
||||||
5. **GitHub release**: upload the image asset under the exact name the
|
5. **GitHub release**: run `scripts/release.sh <version>` on the build
|
||||||
installer downloads — Scarthgap emits
|
host. It gates (version single-source, rootfs-vs-slot size,
|
||||||
`forgefirm-image-glowforge.rootfs.wic.gz`; align BUILD.md and
|
installer-embedded pubkey vs the signing key, factory-era fwup
|
||||||
`install-forgefirm.sh` to one name before the first release.
|
verification), builds, packs and signs `forgefirm.fw`, stages the
|
||||||
|
assets with `sha256sums.txt`, and prints the `gh release create`
|
||||||
|
command. Assets and their exact names (the installer and the update
|
||||||
|
manager download them verbatim): `forgefirm.fw`, `sha256sums.txt`,
|
||||||
|
`forgefirm-image-glowforge.rootfs.wic.gz`. The release tag
|
||||||
|
`v<version>` = `FORGEFIRM_RELEASE` = the rootfs `/etc/forgefirm-version`
|
||||||
|
= the `.fw` meta-version; `release.sh` enforces the agreement.
|
||||||
|
|
||||||
For gfhardware development, either bump the recipe pin per iteration or add a
|
For gfhardware development, either bump the recipe pin per iteration or add a
|
||||||
tracked externalsrc bbappend mirroring the kernel-module pattern.
|
tracked externalsrc bbappend mirroring the kernel-module pattern.
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# (C) Copyright 2020-2026
|
||||||
|
# Scott Wiederhold, s.e.wiederhold@gmail.com
|
||||||
|
# https://community.openglow.org
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# ForgeFIRM release pipeline (runs on the Yocto build host).
|
||||||
|
#
|
||||||
|
# release.sh <version> [--publish] full release: gates, build, pack,
|
||||||
|
# sign, checksums, stage, publish cmd
|
||||||
|
# release.sh --dev build + pack a dev-signed .fw for
|
||||||
|
# the GUI upload path; no staging
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# FWUP host fwup for packing (default: fwup in PATH)
|
||||||
|
# FWUP_COMPAT factory-era fwup 0.14.2 binary; when set, the
|
||||||
|
# packed archive is verified with it (raw-format
|
||||||
|
# key), replicating the factory-compat guarantee
|
||||||
|
# FORGEFIRM_SIGNING_KEY private key for release mode (REQUIRED - no
|
||||||
|
# default, so key choice is always deliberate)
|
||||||
|
# FORGEFIRM_DEV_KEY private key for --dev mode (REQUIRED for --dev)
|
||||||
|
# RELEASE_STAGING_DIR where release assets are staged
|
||||||
|
# (default: <repo>/release-staging)
|
||||||
|
#
|
||||||
|
# Version contract: <version> == FORGEFIRM_RELEASE in forgefirm-image.bb
|
||||||
|
# == /etc/forgefirm-version ("v<version>") in the built rootfs == .fw
|
||||||
|
# meta-version ("v<version>") == release tag ("v<version>").
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
DEPLOY="$REPO/build/tmp/deploy/images/glowforge"
|
||||||
|
IMAGE_BB="$REPO/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb"
|
||||||
|
INSTALLER="$REPO/scripts/install-forgefirm.sh"
|
||||||
|
WARN_BYTES=$((170 * 1024 * 1024))
|
||||||
|
FAIL_BYTES=$((195 * 1024 * 1024))
|
||||||
|
|
||||||
|
die () { echo "RELEASE FAILED: $*" >&2; exit 1; }
|
||||||
|
warn () { echo "WARNING: $*" >&2; }
|
||||||
|
|
||||||
|
VERSION=""
|
||||||
|
MODE=release
|
||||||
|
PUBLISH=0
|
||||||
|
for ARG in "$@"; do
|
||||||
|
case "$ARG" in
|
||||||
|
--dev) MODE=dev ;;
|
||||||
|
--publish) PUBLISH=1 ;;
|
||||||
|
-*) die "unknown option $ARG" ;;
|
||||||
|
*) VERSION="$ARG" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
FWUP="${FWUP:-fwup}"
|
||||||
|
command -v "$FWUP" >/dev/null || die "fwup not found (set FWUP=)"
|
||||||
|
command -v kas >/dev/null || die "kas not found on PATH"
|
||||||
|
|
||||||
|
build_images () {
|
||||||
|
echo "== building images =="
|
||||||
|
( cd "$REPO" && kas shell kas/forgefirm-glowforge.yml \
|
||||||
|
-c 'bitbake forgefirm-image forgefirm-image-dev' ) \
|
||||||
|
|| die "bitbake failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_ext4 () {
|
||||||
|
EXT4=$(readlink -f "$DEPLOY/forgefirm-image-glowforge.rootfs.ext4")
|
||||||
|
[ -s "$EXT4" ] || die "release ext4 not found in $DEPLOY"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_size () {
|
||||||
|
SZ=$(stat -c%s "$EXT4")
|
||||||
|
[ "$SZ" -lt "$FAIL_BYTES" ] \
|
||||||
|
|| die "rootfs is $SZ bytes - too close to the 200 MiB slot"
|
||||||
|
if [ "$SZ" -ge "$WARN_BYTES" ]; then
|
||||||
|
warn "rootfs is $((SZ / 1048576)) MiB - $(( (FAIL_BYTES - SZ) / 1048576 )) MiB of margin left before the release gate"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- dev mode -----------------------------------------------------------------
|
||||||
|
if [ "$MODE" = "dev" ]; then
|
||||||
|
KEY="${FORGEFIRM_DEV_KEY:?set FORGEFIRM_DEV_KEY to the dev signing key}"
|
||||||
|
build_images
|
||||||
|
resolve_ext4
|
||||||
|
check_size
|
||||||
|
REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB")
|
||||||
|
DEVVER="v${REL}-dev-$(date +%Y%m%d%H%M%S)"
|
||||||
|
OUT="$DEPLOY/forgefirm-dev.fw"
|
||||||
|
"$REPO/scripts/mkfw.sh" "$EXT4" "$DEVVER" "$OUT" "$KEY"
|
||||||
|
echo "== dev archive ready: $OUT ($DEVVER) =="
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- release mode -------------------------------------------------------------
|
||||||
|
[ -n "$VERSION" ] || die "usage: release.sh <version> [--publish] | release.sh --dev"
|
||||||
|
KEY="${FORGEFIRM_SIGNING_KEY:?set FORGEFIRM_SIGNING_KEY to the release signing key}"
|
||||||
|
PUB="${KEY%.priv}.pub"
|
||||||
|
[ -f "$KEY" ] || die "signing key '$KEY' not found"
|
||||||
|
[ -f "$PUB" ] || die "public key '$PUB' not found"
|
||||||
|
|
||||||
|
echo "== gates =="
|
||||||
|
|
||||||
|
# Repo state (informational when the build tree is not a git checkout).
|
||||||
|
if git -C "$REPO" rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
[ -z "$(git -C "$REPO" status --porcelain)" ] \
|
||||||
|
|| die "working tree is dirty - release from a clean tree"
|
||||||
|
if ! git -C "$REPO" diff --quiet "@{upstream}" 2>/dev/null; then
|
||||||
|
warn "HEAD differs from upstream - push before publishing"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "$REPO is not a git checkout - repo-state gates skipped (rsynced build tree)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Version single-source check.
|
||||||
|
BB_REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB")
|
||||||
|
[ "$BB_REL" = "$VERSION" ] \
|
||||||
|
|| die "FORGEFIRM_RELEASE in forgefirm-image.bb is '$BB_REL', not '$VERSION'"
|
||||||
|
|
||||||
|
# The installer must embed the pubkey matching the signing key, or every
|
||||||
|
# install will refuse the published archive.
|
||||||
|
INST_HEX=$(sed -n "s/^PUBKEY='\(.*\)'$/\1/p" "$INSTALLER" | tr -d '\\x')
|
||||||
|
KEY_HEX=$(base64 -d "$PUB" | xxd -p | tr -d '\n')
|
||||||
|
[ -n "$INST_HEX" ] || die "cannot extract the embedded pubkey from the installer"
|
||||||
|
[ "$INST_HEX" = "$KEY_HEX" ] \
|
||||||
|
|| die "installer's embedded pubkey does not match the signing key - update install-forgefirm.sh"
|
||||||
|
|
||||||
|
build_images
|
||||||
|
resolve_ext4
|
||||||
|
check_size
|
||||||
|
|
||||||
|
# Rootfs version stamp.
|
||||||
|
STAMP=$(debugfs -R "cat /etc/forgefirm-version" "$EXT4" 2>/dev/null)
|
||||||
|
[ "$STAMP" = "v$VERSION" ] \
|
||||||
|
|| die "rootfs stamp is '$STAMP', expected 'v$VERSION'"
|
||||||
|
|
||||||
|
echo "== pack + sign =="
|
||||||
|
STAGE="${RELEASE_STAGING_DIR:-$REPO/release-staging}/v$VERSION"
|
||||||
|
mkdir -p "$STAGE"
|
||||||
|
"$REPO/scripts/mkfw.sh" "$EXT4" "v$VERSION" "$STAGE/forgefirm.fw" "$KEY"
|
||||||
|
|
||||||
|
# Factory-era compat verification (fwup 0.14.2 wants raw 32-byte keys).
|
||||||
|
if [ -n "${FWUP_COMPAT:-}" ]; then
|
||||||
|
RAW=$(mktemp)
|
||||||
|
base64 -d "$PUB" > "$RAW"
|
||||||
|
"$FWUP_COMPAT" -V -i "$STAGE/forgefirm.fw" -p "$RAW" \
|
||||||
|
|| { rm -f "$RAW"; die "factory-era fwup rejects the archive"; }
|
||||||
|
rm -f "$RAW"
|
||||||
|
echo "factory-era fwup verification OK"
|
||||||
|
else
|
||||||
|
warn "FWUP_COMPAT not set - factory-era verification skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== stage assets =="
|
||||||
|
cp -L "$DEPLOY/forgefirm-image-glowforge.rootfs.wic.gz" "$STAGE/forgefirm-image-glowforge.rootfs.wic.gz"
|
||||||
|
( cd "$STAGE" && sha256sum forgefirm.fw forgefirm-image-glowforge.rootfs.wic.gz > sha256sums.txt )
|
||||||
|
ls -la "$STAGE"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
== release v$VERSION staged ==
|
||||||
|
|
||||||
|
Pre-publish checklist (kas/README.md "Push & release order" step 4):
|
||||||
|
- meta-openglow pushed; kas config flipped to the pinned-remote block
|
||||||
|
- externalsrc bbappend dropped; kas lock refreshed
|
||||||
|
- self-containment proven from a fresh clone
|
||||||
|
|
||||||
|
Publish (from a directory with an authenticated gh):
|
||||||
|
cd "$STAGE"
|
||||||
|
gh release create "v$VERSION" --repo ScottW514/forgefirm \\
|
||||||
|
--title "ForgeFIRM v$VERSION" --generate-notes \\
|
||||||
|
forgefirm.fw sha256sums.txt forgefirm-image-glowforge.rootfs.wic.gz
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$PUBLISH" = "1" ]; then
|
||||||
|
command -v gh >/dev/null || die "--publish requested but gh is not on PATH"
|
||||||
|
( cd "$STAGE" && gh release create "v$VERSION" --repo ScottW514/forgefirm \
|
||||||
|
--title "ForgeFIRM v$VERSION" --generate-notes \
|
||||||
|
forgefirm.fw sha256sums.txt forgefirm-image-glowforge.rootfs.wic.gz ) \
|
||||||
|
|| die "gh release create failed"
|
||||||
|
echo "== published v$VERSION =="
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user