Files
firmware/.github/workflows/release.yml
T
Oleksandr ShyshatskyiandClaude Opus 5 f70369155b Run the brick-safety gates in CI, and stop skipping quietly
The replica gates -- the only ones that decide whether a package bricks a
printer -- had never run in CI. They were gated on a STOCK_URL_CREATOR5PRO
secret that was never set, so printer-sim rendered as a grey dash beside
two green ticks and read as "CI passed".

The packages it needs are already public. ghzserg/FF publishes both stock
packages and the factory image, and this repo already points at that
release in docs/printer-replica.md. All three are now pinned in ci.yml by
sha256 and verified byte-for-byte against the copies used locally:

  Creator5Pro-1.9.7-1.2.9-20260810.tgz  1f3252fe...  93MB
  Creator5-1.9.7-1.2.9-20260810.tgz     72009be1...  93MB
  Creator5Pro-factory.tar.xz            45657924...  190MB

Nothing proprietary is redistributed from here; the workflow fetches what
is published elsewhere, cached on their checksums so an unchanged pin
costs that archive nothing. Secrets still override for a fork with its own
firmware -- but a URL override without a matching checksum now refuses to
run rather than downloading something nobody verified. have-firmware is
gone with the `if:` it fed.

Rehearsing that job locally found the reason it would have failed anyway.
The replica gates call ./bin/patch.sh directly, and patch.sh expects
vendor/ already populated -- bin/build.sh is what fetches Mainsail and
HelixScreen, and the gates do not go through build.sh. No local run ever
noticed, because vendor/ was always already there. Both workflows now
fetch assets before the suite; in release.yml that step has to precede the
gates, since `make release` would otherwise fetch them one step too late.

Second half: a skip must be legible.

ALLOW_SKIP=1 silenced the explanation as well as the exit code -- the
summary printed "2 skipped" and then nothing about what or why. That is
the disease this harness exists to treat, reintroduced by the flag meant
to manage it. Now:

  - Every skip is listed again in the summary with its reason and whether
    it was accepted, whatever ALLOW_SKIP says, and becomes a GitHub
    annotation. Nobody reads a green job's log.
  - ALLOW_SKIP takes names: ALLOW_SKIP="pytest,the printer replica". `1`
    still accepts everything, but it is a standing promise never to notice
    a skip again, so CI uses the list and a third skip fails there.
  - The pytest gate names the tests that skipped instead of counting them.
    That immediately showed the exec-bit test skipping wherever .git is
    absent -- a bare "6 skipped" would not have.

VERIFIED:

  - printer-sim rehearsed end to end on the actual published bytes, real
    git checkout, REQUIRE_PRINTER_SIM=1 and no ALLOW_SKIP: 12 passed,
    0 failed, 0 skipped, exit 0, in about four minutes. Before the vendor
    step it failed at patch.sh, which is what CI would have done.
  - The four ALLOW_SKIP cases, in the build image with no firmware: none
    set -> exit 1 naming both gaps; "pytest" only -> exit 1, replica still
    NOT ACCEPTED; both named -> exit 0, both still printed; and under
    GITHUB_ACTIONS the ::warning:: annotations appear.
  - pytest 30 passed. Both workflows parse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:17:57 +03:00

159 lines
6.4 KiB
YAML

name: release
# A release is blocked on the printer replica passing.
#
# The replica is the printer's real rootfs.squashfs, chrooted under
# qemu-mipsel, with /usr/prog installed by FlashForge's own updater. The
# installer under test runs on the printer's busybox, tar, md5sum and unTar --
# not on Debian stand-ins. If that fails, nothing ships.
#
# The stock FlashForge packages are proprietary and ~93MB each, so they are
# never in the repo. Point these repository secrets at them (pre-signed URLs,
# a private release asset, an S3 object -- anything curl can fetch):
#
# STOCK_URL_CREATOR5PRO required
# STOCK_URL_CREATOR5 required
# FACTORY_URL required -- the factory image, which is the
# replica's real /usr/prog and /usr/data
# STOCK_SHA256_CREATOR5PRO optional but recommended
# STOCK_SHA256_CREATOR5 optional but recommended
#
# Without them the job FAILS. It does not skip: a release that skipped the
# only test that can catch a brick is worse than no release.
on:
push:
tags: [ 'v*' ]
workflow_dispatch:
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check the stock packages are configured
env:
PRO: ${{ secrets.STOCK_URL_CREATOR5PRO }}
STD: ${{ secrets.STOCK_URL_CREATOR5 }}
FACTORY: ${{ secrets.FACTORY_URL }}
run: |
missing=
[ -n "$PRO" ] || missing="$missing STOCK_URL_CREATOR5PRO"
[ -n "$STD" ] || missing="$missing STOCK_URL_CREATOR5"
[ -n "$FACTORY" ] || missing="$missing FACTORY_URL"
if [ -n "$missing" ]; then
echo "::error::missing secrets:$missing"
echo "The printer replica cannot run without the stock packages, and"
echo "a release is not allowed to skip it. See the comment at the top"
echo "of .github/workflows/release.yml."
exit 1
fi
- name: Fetch the stock firmware
env:
PRO: ${{ secrets.STOCK_URL_CREATOR5PRO }}
STD: ${{ secrets.STOCK_URL_CREATOR5 }}
PRO_SHA: ${{ secrets.STOCK_SHA256_CREATOR5PRO }}
STD_SHA: ${{ secrets.STOCK_SHA256_CREATOR5 }}
run: |
set -euo pipefail
mkdir -p stock
curl -fsSL "$PRO" -o stock/Creator5Pro-stock.tgz
curl -fsSL "$STD" -o stock/Creator5-stock.tgz
[ -z "$PRO_SHA" ] || echo "$PRO_SHA stock/Creator5Pro-stock.tgz" | sha256sum -c -
[ -z "$STD_SHA" ] || echo "$STD_SHA stock/Creator5-stock.tgz" | sha256sum -c -
ls -lh stock
- name: Write test.env (the replica's /usr/prog)
env:
FACTORY: ${{ secrets.FACTORY_URL }}
# Optional repository variable: a prebuilt replica image, e.g.
# monstrofil/creator5-printer:latest. It carries the factory
# partitions AND the stock package already installed, which is about
# a minute of setup saved on every replica case. The end-to-end test
# reinstalls the baseline itself when the package under test is for
# the other model, so this only ever changes how long it takes.
PRINTER_IMAGE: ${{ vars.PRINTER_IMAGE }}
run: |
set -euo pipefail
curl -fsSL "$FACTORY" -o stock/factory.tar.xz
echo "PROG_DUMP=\"$PWD/stock/factory.tar.xz\"" > test.env
[ -z "${PRINTER_IMAGE:-}" ] || echo "PRINTER_IMAGE=\"$PRINTER_IMAGE\"" >> test.env
cat test.env
- name: Write config.env
run: |
cat > config.env <<CFG
MOD_NAME=anvil
# Tags are release dates: v20260823. A manual run has no tag -- and
# GITHUB_REF_NAME would be the branch there -- so leave it empty and
# let the build stamp today's UTC date itself.
MOD_VER=$(case "$GITHUB_REF" in refs/tags/*) echo "${GITHUB_REF_NAME#v}";; *) echo "";; esac)
SW_VER=""
TARGET_MACHINE=Creator5Pro
STOCK_TGZ_CREATOR5PRO="$PWD/stock/Creator5Pro-stock.tgz"
STOCK_TGZ_CREATOR5="$PWD/stock/Creator5-stock.tgz"
STOCK_TGZ=""
KLIPPER_FORK=""
ROOT_PW_HASH='${{ secrets.ROOT_PW_HASH }}'
FF_KEY='FFP0331&*%root'
CFG
# Before the suite, not after: the replica gates run ./bin/patch.sh
# directly and it expects vendor/ already populated. `make release`
# further down would have fetched them, but that runs after the gates.
- name: Fetch the pinned Mainsail and HelixScreen assets
run: make vendor ASSET_ROOT=$PWD
- name: Extract the printer rootfs
run: make rootfs ASSET_ROOT=$PWD
- name: Brick-safety suite, replica required
run: |
docker build -q -t creator5-fw-build -f docker/Dockerfile.build docker
docker run --rm \
-v "$PWD":"$PWD" -w "$PWD" \
-v /var/run/docker.sock:/var/run/docker.sock \
-e REQUIRE_PRINTER_SIM=1 \
creator5-fw-build ./test/run-tests.py
- name: Build both models
run: make release ASSET_ROOT=$PWD
- name: End-to-end update test on the packages that will actually ship
# The suite above tested a package built from the same tree; this
# tests the exact files that get attached to the release.
env:
REQUIRE_PRINTER_SIM: 1 # a skip here must fail the release
run: |
for p in dist/*.tgz; do
echo "=== $p ==="
./test/integration/sim-install.py "$p" || exit 1
done
- uses: actions/upload-artifact@v4
with:
name: firmware-packages
path: dist/*.tgz
retention-days: 30
- name: Publish
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: dist/*.tgz
body: |
Copy the file matching YOUR printer to the root of a FAT32 USB
stick and power the machine on. A `Creator5Pro-` package will not
install on a Creator 5 and vice versa.
Every package here was installed into a replica of the printer --
the real rootfs running under qemu-mipsel, with the package on a
real FAT filesystem that the machine's own app_startup.sh found
and installed -- and the machine was booted again afterwards and
checked to come up.