Files
Oleksandr Shyshatskyi 80a4d64ffd Prepare next firmware release
Add Fluidd and custom boot scripts, migrate DC24V control, update the web UIs and filament macros, improve startup status reporting, and expand upgrade and replica coverage.
2026-09-24 11:37:28 +03:00

310 lines
16 KiB
YAML

name: ci
# The stock FlashForge firmware is proprietary and ~93MB, so it is never in
# the repo. The `test` job therefore runs only what needs no firmware at all:
# qa/static and the host-side pytest tree. That is enough for the static and
# packaging checks, but NOT enough to prove a package will not brick a
# printer.
#
# That proof needs the printer's real rootfs, and so it needs the stock
# package. `printer-sim` below now always runs, because the packages it needs
# are publicly mirrored and pinned in versions.env rather than hidden behind a
# secret nobody had set. It used to be gated on STOCK_URL_CREATOR5PRO, which meant
# that for its entire existence it never ran once: a grey dash beside two
# green ticks, reading as "CI passed" while the only gates that decide
# whether a package bricks a printer were never executed.
on:
push:
branches: [ main, master ]
pull_request:
workflow_dispatch:
# The stock packages are no longer fetched here. They are pinned by name and
# sha256 in versions.env like every other third-party source, and
# bin/fetch-assets.sh downloads them into vendor/ -- so `make vendor` below
# gets them the same way a person's checkout does, and this workflow no longer
# carries a second spelling of "fetch the firmware, check the hash" that could
# drift from the one humans run.
#
# The secrets that used to override the URLs (STOCK_URL_CREATOR5PRO and
# friends, for a fork testing its own firmware) are gone with them. A fork
# re-pins versions.env instead, which works locally too -- the secrets only
# ever worked in CI, and the local build had no equivalent at all.
#
# The factory image used to be fetched here too, for PROG_DUMP. Both are gone:
# the replica is PRINTER_IMAGE and nothing else, and that image carries the
# real /usr/prog already. PRINTER_IMAGE is pinned to a version tag for the
# same reason the sha256s are -- it is now the whole substrate the
# brick-safety gates run on, so it must not move on its own.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# gcc/pkg-config/perl/zlib1g-dev/patch/make are for the SAME reason
# docker/Dockerfile.build carries them: cross-building CPython 3.13
# needs a HOST build-python of its own to freeze modules and
# byte-compile the stdlib, and that one needs a host C compiler,
# pkg-config (CPython's configure probes libffi through it), perl
# (OpenSSL's Configure is a perl program), zlib1g-dev (the build-python
# needs a working zlib module or ensurepip cannot even unpack pip's
# bundled wheel) and patch (lmdb's setup.py shells out to
# /usr/bin/patch by absolute path). See that file's own comment for the
# long version.
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
openssl xz-utils unzip binutils file squashfs-tools \
python3-pytest python3-jinja2 python3-pyflakes \
gcc pkg-config perl zlib1g-dev patch make
# The shell parse and the bashism pass live in qa/static/test_shell_syntax.py
# now; this step is the repo-wide shellcheck, which is a wider net.
- name: shellcheck
run: |
set -euo pipefail
sudo apt-get install -y shellcheck
# FOUND, NOT GLOBBED, and the count is asserted. The list here used
# to be four literal globs, two of which stopped matching anything:
# pkgs/*/payload/init.d/S* went when s6-rc replaced the init
# scripts, and pkgs/*/prog/ moved under payload/. shellcheck exits 2
# on a path it cannot open, so this step failed on every run -- and
# had it not, the opposite failure was worse: a glob that quietly
# matches nothing checks nothing and still reports green.
mapfile -t PRINTER_SH < <(
find pkgs -path '*/payload/*' \
\( -name '*.sh' -o -name firmwareExe \) | sort)
PRINTER_SH+=(installer/runFirmwareExe.sh)
if [ "${#PRINTER_SH[@]}" -lt 5 ]; then
echo "::error title=Nothing to check::found ${#PRINTER_SH[@]} on-printer scripts; the find pattern is stale."
exit 1
fi
printf 'checking: %s\n' "${PRINTER_SH[@]}"
# Errors are a gate. These used to end in `|| true`, which meant
# shellcheck could report anything it liked and the job stayed green.
# payload scripts run under busybox ash on the printer.
shellcheck -s sh -S error "${PRINTER_SH[@]}"
shellcheck -s bash -S error bin/*.sh tools/replica/*.sh
# Warnings are advisory: printed, not enforced.
shellcheck -s sh -S warning "${PRINTER_SH[@]}" || true
# s6 and CPython 3.13 have no BUILD_ flag -- every package ships them
# (see bin/fetch-assets.sh) -- so this job builds them for real rather
# than stubbing around them the way it stubs Mainsail, HelixScreen and
# the Klipper fork. Cached on versions.env: an unchanged pin never
# re-fetches the ~203MB Ingenic toolchain and never recompiles either
# tree. Both are recipes under work/pkg now -- four for s6, nineteen for
# CPython and its site-packages -- and every one carries pkg_stamp's key.
#
# work/pkg is every recipe under pkgs/ at once -- one cache entry instead
# of one per package, because pkgs/lib.sh derives the output directory
# from the recipe name rather than naming each one in bin/common.sh. A
# new recipe therefore needs no edit here, which is the same property
# that makes adding a package one directory. work/packages is the feed
# built out of those trees, cached beside them for the same reason.
- name: Cache the s6 + CPython 3.13 build inputs and outputs
uses: actions/cache@v4
with:
path: |
vendor
work/pkg
work/packages
work/host
key: fixture-py-${{ hashFiles('versions.env') }}
- name: Fetch the s6 + CPython 3.13 build inputs
run: |
set -euo pipefail
# This config.env exists purely to let fetch-assets.sh see a
# config at all; it skips Mainsail and HelixScreen (still gated on
# their own BUILD_ flags, unset here) and fetches what s6, CPython
# and the Klipper fork need, none of which has a BUILD_ flag of its
# own any more -- BUILD_KLIPPER is gone and the fork is the only
# Klipper, so its 27MB tarball comes down here with the rest. This
# config.env is KEPT rather than removed: the packaging step below
# reads it too, and unlike payload.sh, bin/build-packages.sh needs no
# stock package -- which is the whole reason the feed can be built
# in this job at all.
#
# s6 and CPython are not cross-built by payload.sh any more; there are
# no sections 5b/5c. They are recipes under pkgs/, built by
# build-packages.sh like every other one.
cat > config.env <<'CFG'
BUILD_MAINSAIL=0
BUILD_FLUIDD=0
BUILD_HELIX=0
BUILD_MOONRAKER=0
CFG
./bin/fetch-assets.sh
# BEFORE the static lane, because a good part of what that lane asks is
# about .apk files, and those questions do not fail on a checkout with
# no feed -- they return, quietly, and the lane reports a pass having
# never opened a package. Building the feed here is what makes that
# answer mean something, and it needs no stock firmware to do it.
- name: Build the package feed
run: ./bin/build-packages.sh
# The qa suite's static lane: parses, bashisms, names, recipe layout,
# and -- now that the feed above exists -- what is inside the .apk files.
#
# No strictness flag: a missing shellcheck or pyflakes fails on its own,
# because there is no configuration in which running this lane without
# them is a complete run. Only the static lane here -- this job has no
# stock firmware on purpose, and printer-sim below is where the replica
# is proved.
- name: qa -- static lane
run: python3 -m pytest ./qa/static -q
# The real check: install the package into a replica of the printer -- the
# actual rootfs.squashfs under qemu-mipsel, on the printer's own busybox --
# and assert the machine still boots. No `if:` guard: this job is the point
# of the repo, and a version of it that can quietly not run is worse than
# not having it.
printer-sim:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Write test.env (the replica)
env:
# THE replica. It carries the printer's rootfs, its real /usr/prog
# and /usr/data, and the stock package already installed on top of
# them -- about a minute of setup saved on EVERY replica case, which
# over a full suite is the difference between this job and a coffee
# break. vars.PRINTER_IMAGE overrides it for a fork building its own.
#
# A VERSION TAG, NOT :latest. This image is now the entire substrate
# the brick-safety gates run on -- there is no local fallback left
# to build one from a stock package -- so a floating tag would let
# it change under a green suite. That is precisely the drift the
# sha256 pins above exist to prevent, and it would be worse here,
# because nothing in the run would even record what was tested.
PRINTER_IMAGE: ${{ vars.PRINTER_IMAGE || 'monstrofil/creator5-printer:1.9.7-1.2.9-20260810' }}
run: |
set -euo pipefail
if [ -z "${PRINTER_IMAGE:-}" ]; then
echo "::error title=No replica::PRINTER_IMAGE is empty and there is no other way to get a replica."
exit 1
fi
echo "PRINTER_IMAGE=\"$PRINTER_IMAGE\"" > test.env
cat test.env
- name: Write config.env
run: |
cat > config.env <<'CFG'
MOD_NAME=anvil
# 0, NOT "ci": MOD_VER is anvil-core's PKG_VERSION, and apk's version
# grammar wants a number first -- `ci` is rejected by mkpkg with
# "package version is invalid" and no clue which package or field.
# It is pinned rather than left to default so two runs of one tree
# produce the same bytes; 0 says "not a release" as plainly as the
# word did, and orders below every real one.
MOD_VER=0
TARGET_MACHINE=Creator5Pro
# No stock paths: bin/common.sh defaults them into vendor/, where
# `make vendor` puts the pinned download. Setting them here would be
# the override that suppresses the checksum.
STOCK_TGZ=""
# No Klipper source knob any more: pkgs/klipper builds from the
# commit pinned in versions.env and nothing else. The variable that
# used to live here, KLIPPER_FORK, is what let v20260824 ship the
# stock klippy tree by being set to "" in this very file.
ROOT_PW_HASH='$6$ci$abcdefghijklmnopqrstuvwxyz'
FF_KEY='FFP0331&*%root'
CFG
# The replica gates run ./bin/payload.sh, which needs the real Mainsail
# zip and HelixScreen tarball in vendor/ -- payload.sh does not fetch them
# itself, bin/build.sh does, and the gates do not go through build.sh.
# Nothing here noticed until this job ran for the first time, because
# every local run already had a populated vendor/. All are public,
# sha256-pinned in versions.env, and re-download only on a version bump.
#
# Cached because the set now includes the Klipper fork and the ~203MB
# MIPS toolchain that compiles its chelper -- this job runs on every
# push, and the toolchain host should not pay for that.
- name: Cache the pinned vendor assets
uses: actions/cache@v4
with:
path: vendor
key: vendor-${{ hashFiles('versions.env') }}
# `make vendor` is --all --stock, which now also brings down both models'
# stock firmware (~186MB) -- this job builds Creator5Pro but the qa lane
# reads both baselines. Same cache, keyed on versions.env, so the pins and
# the firmware they name move together and an unchanged pin costs nothing.
# The `test` job above calls bin/fetch-assets.sh bare and so still gets
# no firmware at all, which is what keeps its feed build honest.
- name: Fetch the pinned vendor assets and the stock firmware
run: make vendor ASSET_ROOT=$PWD
# THE FEED, and it is not optional on the release path. bin/payload.sh
# builds the payload by INSTALLING the feed, so it refuses to start
# without one, and the python recipes resolve PKG_BUILD_DEPENDS out of
# work/packages. These two steps ran as one unpack/patch/pack with no
# feed for a while, which could not have passed.
#
# Unlike the build below, this needs no stock firmware -- which is most
# of the point: packaging has to be runnable on a bare checkout, or the
# gate only runs where the proprietary package is.
- name: Build the package feed
run: make packages ASSET_ROOT=$PWD
# BUILD THE PACKAGE THE SUITE INSTALLS. The qa replica lane installs for
# real -- the printer's own app_startup.sh, off a FAT stick -- so it
# needs a package in work/out, and it never builds one on demand: that
# would turn "the suite is slow today" into a mystery.
#
# It also supplies the cross-built s6 and CPython, because both ship
# INSIDE the package; there is no separate build tree to track.
#
# Through `make`, not around it. `make build` runs bin/build.sh on the
# RUNBUILD lane -- as the runner user, with the docker socket and the
# socket's own gid -- because bin/payload.sh assembles the payload inside
# the replica. Hand-rolling the `docker run` here meant CI exercised an
# entry point no human uses, so a mistake in one of the Makefile's own
# runners could never turn this job red.
- name: Build the package
run: make build ASSET_ROOT=$PWD
# The brick-safety gate. No ALLOW_SKIP and no REQUIRE_PRINTER_SIM: qa has
# no skip mode at all, so a missing docker, daemon, image or package
# fails at the point that needs it rather than being reported as a gap
# somebody has to remember to accept.
#
# The replica starts sibling containers through the docker socket and
# needs loop devices for the simulated USB stick; both work on a stock
# ubuntu-latest runner.
- name: qa suite on the printer replica
run: make qa ASSET_ROOT=$PWD
# The docs make claims about a machine that can be left unbootable, and the
# pages cross-link each other and the payload sources heavily -- 30+ links
# between README.md, docs/*.md and docs/notes/. This is the cheap half of
# keeping them true: --strict fails on a link whose target has moved or been
# deleted, so a rename in payload/ cannot quietly leave a doc pointing at
# nothing. It cannot check that the prose is still right; only a reader can.
#
# Read the Docs builds the same mkdocs.yml with fail_on_warning, so this job
# going red is also the warning that the published site is about to fail.
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12" # what .readthedocs.yaml builds with
- name: Install the docs toolchain
run: pip install -r docs/requirements.txt
- name: Build the site
run: mkdocs build --strict