mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-21 21:02:16 -05:00
There is no RTC and no battery, so every boot starts at whatever the kernel was built with and stays there: job timestamps, log ordering and certificate validity are all wrong on a machine that has been unplugged. Nothing on the stock rootfs could fix it -- FlashForge's busybox is v1.31.1 built without the ntpd applet, and its rdate speaks RFC 868. anvil-sntpd is troglobit's sntpd, a fork of Doolittle's ntpclient: one small binary, servers and interval on the command line, so there is no config file, no privsep user and no chroot to place on a read-only rootfs. It ships $MODDIR/sbin/sntpd and nothing else. A LONGRUN, NOT A HOOK ON THE WIFI ONESHOT. That oneshot runs once, at boot, and returns before wlan0 has associated -- so a sync hung off its end would fire too early, never retry, and never fire at all for the owner who joins a network from the HelixScreen panel an hour later. And with no RTC a clock is not set once but maintained. sntpd keeps its schedule with the network down instead of exiting, which is what lets this service need no wifi dependency -- just as well, since nothing may depend on that oneshot. The poll interval is 300 rather than upstream's 600 because it is also the ceiling on the down-network retry backoff, and so bounds how long a printer that joined wifi late keeps the wrong time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149y4iyLpEwYKh2PS4fmf4u
106 lines
4.9 KiB
Bash
Executable File
106 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 2/3 -- build the mod payload from the package feed. Idempotent.
|
|
#
|
|
# Install the feed with the printer's own opkg, seed the user's
|
|
# moonraker-custom.conf, write the manifest. Everything it produces goes to
|
|
# $MODDIR on the data partition and nothing to /usr/prog, so it needs the
|
|
# package feed and no stock FlashForge package at all.
|
|
set -euo pipefail
|
|
. "$(dirname "$0")/common.sh"
|
|
. "$ROOT/pkgs/lib.sh"
|
|
|
|
# Counting .ipk, not testing the directory: a cleaned tree leaves it empty.
|
|
if [ -z "$(ls "$PKG_FEED"/*.ipk 2>/dev/null)" ]; then
|
|
echo "no package feed at $PKG_FEED" >&2
|
|
echo " the recipes this script runs build against it -- run 'make packages' first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
say() { printf '>> %s\n' "$*"; }
|
|
skip() { printf ' (skip) %s\n' "$*"; }
|
|
|
|
# Everything goes to /usr/data: /usr/prog is the firmware partition and would
|
|
# overflow on ~100MB of Mainsail and HelixScreen.
|
|
rm -rf "$PAYLOAD_ROOT"
|
|
|
|
# --- 1. the payload, installed
|
|
# Installed by the printer's own opkg inside the replica, so `opkg
|
|
# list-installed` answers what a release installs off the payload itself.
|
|
say "payload: installing the feed with the printer's own opkg"
|
|
|
|
# Not model-specific; TARGET_MACHINE names only the OUTPUT file, because
|
|
# runFirmwareExe.sh refuses a package whose machine is not its own.
|
|
|
|
# The roots, not the closure: Depends brings the rest, so the metadata is
|
|
# exercised on every build. A root with no .ipk is skipped -- that is how the
|
|
# PKG_WHEN gates reach here -- but a missing DEPENDENCY is still opkg's error.
|
|
# The two loose python packages are Recommends, which opkg has no field for.
|
|
#
|
|
# greenlet and cffi are deliberately NOT here: the klipper service execs
|
|
# $FF_PYTHON, so they are ordinary Depends of anvil-klipper now. Listed here
|
|
# they were installed by every build and by no `opkg install anvil-klipper`,
|
|
# which is the one command that has to work on a printer.
|
|
MOD_ROOTS="anvil-core anvil-opkg anvil-s6-rc anvil-klipper
|
|
anvil-moonraker anvil-python-pillow anvil-python-preprocess-cancellation
|
|
anvil-mainsail anvil-helixscreen anvil-busybox anvil-sntpd"
|
|
|
|
# Named, not versioned -- except anvil-core, whose PKG_VERSION is MOD_VER, so a
|
|
# feed built yesterday would install yesterday's anvil-core.
|
|
[ -f "$(pkg_ipk anvil-core)" ] || pkg_die \
|
|
"the feed has no $(basename "$(pkg_ipk anvil-core)") -- rerun ./bin/build-packages.sh"
|
|
|
|
MOD_INSTALL=""
|
|
for _p in $MOD_ROOTS; do
|
|
for _f in "$PKG_FEED/${_p}_"*.ipk; do
|
|
if [ -f "$_f" ]; then MOD_INSTALL="$MOD_INSTALL $_p"; fi
|
|
break
|
|
done
|
|
done
|
|
|
|
# The machine's own opkg installs the feed onto its own filesystem: needs a
|
|
# privileged container, hence `make build`'s docker lane.
|
|
# shellcheck disable=SC2086
|
|
./bin/build-payload.py $MOD_INSTALL
|
|
|
|
say "payload: $(grep -c '^Package:' "$PAYLOAD_DIR/var/lib/opkg/status") packages installed (both chamber configs; the printer picks)"
|
|
|
|
# --- 2. Klipper
|
|
# Nothing to do, and nothing to assert. anvil-klipper installs the whole klippy
|
|
# tree at $MODDIR/klipper/klippy and the klipper s6-rc service execs it there on
|
|
# our own $FF_PYTHON, so the package IS the printer's Klipper. /usr/prog/klipper
|
|
# keeps FlashForge's stock klippy, unread -- the only file still read there is
|
|
# klipper_pri.sh, their SCHED_FIFO helper. klipperDaemon at that path is a
|
|
# symlink to our shim, pointed there by anvil-link-prog.sh.
|
|
#
|
|
# A gate here would ask whether opkg had installed the package it was just
|
|
# handed, of a staging root three lines after it was filled.
|
|
# qa/replica/test_install.py::test_klippy_is_present asks it of an installed
|
|
# filesystem instead, which is the vehicle that reaches a printer.
|
|
|
|
# --- 3. the user's seams
|
|
# In the payload and in NO package: a package member is overwritten on every
|
|
# upgrade by definition, and this is the user's own file.
|
|
[ -f pkgs/moonraker/seed/moonraker-custom.conf ] \
|
|
&& cp -f pkgs/moonraker/seed/moonraker-custom.conf "$PAYLOAD_DIR/config/moonraker-custom.conf"
|
|
|
|
# moonraker.conf ships with pkgs/moonraker, not anvil-core: unconditional, a
|
|
# BUILD_MOONRAKER=0 build overwrote the config of a server it did not install.
|
|
|
|
# --- 4. the s6-rc database
|
|
# Compiled in the replica by case-build-payload.sh, with the s6-rc-compile
|
|
# anvil-s6-rc ships, straight after opkg installs the feed. Compiling with the
|
|
# compiler we SHIP is what makes the #! baked into the oneshot runner point at
|
|
# the execline we ship rather than the build host's -- a native second build of
|
|
# the same tarballs had to be held to --prefix=$MODDIR by hand to get that
|
|
# right. (Byte-identical either way: 80-s6-migration.md, measured 2026-08-28.)
|
|
#
|
|
# Nothing to assert here either. qa/replica/test_s6rc.py boots the database on
|
|
# a machine: s6-rc-init reads it, the boot set comes up, and a database moved
|
|
# aside is reported.
|
|
|
|
echo
|
|
echo "Payload built."
|
|
echo " mod payload: $(du -sh "$PAYLOAD_DIR" | cut -f1) (-> /usr/data/anvil, data partition)"
|
|
echo " nothing goes to /usr/prog: no software component is shipped."
|
|
echo "Now run ./bin/pack.sh"
|