mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
The rootfs mounted read-write, so a slot ran with its own files open to change, and the factory-slot mounts rode along on the release image. Both images now carry the read-only-rootfs feature: the ro root line and the rcS default, the volatile links made at rootfs time, a writable copy of /var/lib at boot, a build failure for a post-install that needs the machine, and the removal of shadow, base-passwd, update-rc.d and update-alternatives. What must last or change at run time is handled file by file: - forgefirm-users renders the four account files from the record into /run/forgefirm/accounts and bind-mounts each copy over its /etc file (useradd and the rest are gone with shadow); a render writes through the mount, and the image's own files apply until the first render. - forgefirm-banner bind-mounts a copy of /etc/issue and writes the address block through it. - sshd keeps its host keys under /data/forgefirm/ssh, so the fingerprint survives updates; both sshd configs carry the same HostKey lines. - forgefirm-logging passes logrotate a state file under /var/run (logrotate refuses to run without one). - forgefirm-persist points the boot timestamp and the random seed at /data/forgefirm. The dev image appends the /factory slot mounts, without nofail (busybox mount hands it to the kernel, which rejects it). The rootfs command entries lose their semicolons: on scarthgap the value is the task's vardeps, split on whitespace, so "name;" left the function body out of the signature and a changed body did not remake the rootfs; with the bodies tracked, the dev image's DATETIME string needs a vardepsexclude. release.sh gains the read-only gate (root ro, no /factory line, ROOTFS_READ_ONLY=yes, host keys on /data). image.health checks the mounts, the account binds, the banner bind, the host keys and the dev-only /factory mounts. Proven on the bench reference (dev image 20260909140901): / ro, /data rw, /var/lib a tmpfs copy, the four account files and /etc/issue bound from tmpfs, the host keys in /data/forgefirm/ssh, no "Read-only file system" line in any log; forgectrl.auth and commission.account-login (a temporary account rendered, logged in over HTTPS and removed again), kernel.latch-locked-idle and motion.liveness-probe PASS; logrotate runs with the volatile state. forgetest unit tests 335 OK; both images build clean, and debugfs on the built rootfs shows every setting above.
156 lines
8.6 KiB
BlitzBasic
156 lines
8.6 KiB
BlitzBasic
require recipes-glowforge/images/glowforge-image.bb
|
|
|
|
# /etc/forgefirm-manifest.json: the build-input identity the acceptance tool
|
|
# and the release gate compare (forgefirm-image-manifest.bbclass).
|
|
inherit forgefirm-image-manifest
|
|
|
|
DESCRIPTION = "OpenGlow/ForgeFIRM image for Glowforge"
|
|
|
|
# ForgeFIRM cuts the cloud dependency: drop the Glowforge cloud client
|
|
# (gfui-client connects to Glowforge's servers). Removing it here (override
|
|
# only; the shared glowforge-image base is untouched) also sidesteps its
|
|
# do_package failure. Its role is filled locally by forgectrl and the
|
|
# controllers below. nano is a bench convenience: on the release rootfs it
|
|
# costs 8.7 MB, most of it libmagic and its database, which nothing else
|
|
# here uses. The dev image blanks FORGEFIRM_RELEASE_TRIM to keep it (a
|
|
# removal spec is expanded when applied, so the variable, not the removal,
|
|
# is what a requiring recipe can override). python3 is the meta-package:
|
|
# it installs every standard-library module (tkinter, idle, 2to3, pydoc,
|
|
# ensurepip, venv, the debugger, asyncio, multiprocessing, xmlrpc), which
|
|
# nothing here imports. Each Python recipe declares the module packages it
|
|
# imports (python3-core plus the few it uses), so the meta-package goes.
|
|
FORGEFIRM_RELEASE_TRIM ?= "nano"
|
|
IMAGE_INSTALL:remove = "python3 ${FORGEFIRM_RELEASE_TRIM}"
|
|
|
|
# grblhal-glowforge: the grblHAL motion controller (Grbl over TCP:23).
|
|
# forgectrl: the ForgeFIRM machine-services daemon (HTTP :80, HTTPS :443
|
|
# with a self-signed certificate): controller supervisor, pulse-device
|
|
# broker, cooling engine, cameras, telemetry, settings, diagnostics, web
|
|
# control panel, and A/B updates.
|
|
# gfhome: one-shot Glowforge web-service homing, invoked by the controller
|
|
# for $H when homing_mode = gfcloud (/data/forgefirm.conf).
|
|
# gfcloud: full Glowforge web-service controller daemon (the factory cloud
|
|
# experience), started when controller_mode = cloud - mutually exclusive with
|
|
# grblHAL. Pulls python3-ffmachine (shared web-service machine glue).
|
|
# v4l-utils provides media-ctl / v4l2-ctl for the imx-media pipeline (also a
|
|
# forgectrl runtime dependency, kept explicit here for bring-up use).
|
|
# fwup: applies signed .fw archives (ForgeFIRM upgrades + factory restore)
|
|
# to the inactive rootfs slot.
|
|
# ffboot: boot-slot inventory and switching (also ships fw_env.config).
|
|
# slotmigrate: boot-time reclaim of the legacy p4 layout (grows /data).
|
|
# forgefirm-logging: the ForgeFIRM logging tree - renders the per-logger
|
|
# rsyslog rules from the settings before rsyslog starts, and drives
|
|
# size-capped rotation (boot + hourly; a full /data breaks settings,
|
|
# updates, and controller writes). rsyslog itself comes in through
|
|
# VIRTUAL-RUNTIME_base-utils-syslog (conf/distro/forgefirm.conf).
|
|
IMAGE_INSTALL:append = " grblhal-glowforge forgectrl gfhome gfcloud v4l-utils fwup ffboot slotmigrate forgefirm-logging"
|
|
|
|
# forgefirm-users: renders the operator account record
|
|
# (/data/forgefirm/users, written by forgectrl) into the system account
|
|
# files at boot, before sshd, and on reload; also installs the warning an
|
|
# interactive root shell prints. forgefirm-banner: keeps the control
|
|
# panel addresses in the serial-console banner (/etc/issue).
|
|
# forgefirm-persist: the boot timestamp and the random seed on /data.
|
|
# avahi-daemon: mDNS, so the panel answers at https://forgefirm.local/
|
|
# and shows up in service browsers. The daemon is installed by name (the
|
|
# zeroconf distro feature stays off: it would bring libnss-mdns); the
|
|
# build options and the configuration are in conf/distro/forgefirm.conf
|
|
# and recipes-connectivity/avahi.
|
|
IMAGE_INSTALL:append = " forgefirm-users forgefirm-banner forgefirm-persist avahi-daemon"
|
|
|
|
# The rootfs mounts read-only on both images; /data (p3) is the writable
|
|
# partition. read-only-rootfs is poky's feature for it: the root line of
|
|
# /etc/fstab (the BSP's, already ro) and ROOTFS_READ_ONLY in
|
|
# /etc/default/rcS, the volatile links made at rootfs time
|
|
# (populate-volatile.sh: /etc/resolv.conf, /tmp), a writable copy of
|
|
# /var/lib at boot (read-only-rootfs-hook.sh), a build failure for a
|
|
# package whose post-install must run on the machine, and the removal of
|
|
# the packages a read-only rootfs cannot use (shadow, base-passwd,
|
|
# update-rc.d, update-alternatives; the account files stay). What must
|
|
# last or change at run time is handled file by file: the account files
|
|
# and /etc/issue (forgefirm-users, forgefirm-banner), the sshd host keys
|
|
# (recipes-connectivity/openssh), the timestamp and the random seed
|
|
# (forgefirm-persist). The facts are on the docs site,
|
|
# technical/forgefirm/image-and-bsp; scripts/release.sh checks the built
|
|
# rootfs for this state.
|
|
IMAGE_FEATURES += "read-only-rootfs"
|
|
|
|
# Root policy. root has no password and logs in at the serial console
|
|
# only: that is the recovery path when the network, the panel or an
|
|
# account is broken, and the console is behind the case. Over the
|
|
# network, sshd refuses root (PermitRootLogin no) and any account without
|
|
# a password (PermitEmptyPasswords no), both set by
|
|
# recipes-connectivity/openssh, and sshd runs only when the panel turns
|
|
# it on. Operator logins are the accounts in the record (forgefirm-users).
|
|
# empty-root-password keeps the rootfs postprocess from locking root
|
|
# (zap_empty_root_password in rootfs-postcommands.bbclass); it is not
|
|
# debug-tweaks, which belongs to the dev image alone and would open SSH.
|
|
# scripts/release.sh checks the built rootfs for exactly this state.
|
|
IMAGE_FEATURES += "empty-root-password"
|
|
|
|
# Mesa GLES2/EGL on etnaviv for forgectrl's GPU demosaic (loaded with
|
|
# dlopen at runtime; forgectrl itself has no build-time GL dependency,
|
|
# and without these packages it falls back to the NEON path).
|
|
IMAGE_INSTALL:append = " libegl-mesa libgles2-mesa libgbm mesa-megadriver"
|
|
|
|
# NXP's firmware EULA covers the i.MX VPU/EPDC blobs the BSP installs, so the
|
|
# image ships the license text with them (/usr/share/licenses/firmware-imx).
|
|
# The SDMA firmware brings its own -license package through linux-firmware.
|
|
IMAGE_INSTALL:append = " firmware-imx-lic"
|
|
|
|
# The release rootfs must fit a 200 MiB factory eMMC slot (409600 blocks).
|
|
# Sizing: content + 40 MiB working space, hard-capped at the slot size:
|
|
# the build fails rather than emit an unflashable image. The raw ext4 is
|
|
# deployed alongside the wic; scripts/mkfw.sh packs it into the signed
|
|
# .fw release artifact.
|
|
IMAGE_FSTYPES:append = " ext4"
|
|
IMAGE_OVERHEAD_FACTOR = "1.0"
|
|
IMAGE_ROOTFS_EXTRA_SPACE = "40960"
|
|
IMAGE_ROOTFS_MAXSIZE = "204800"
|
|
|
|
# Version stamp: /etc/forgefirm-version (machine-readable), echoed on the
|
|
# serial-console login prompt (/etc/issue) and at SSH login (motd).
|
|
# Release images carry the release version; the dev image overrides the
|
|
# string with the build timestamp (the same DATETIME as the artifact
|
|
# name) plus a dev tag.
|
|
FORGEFIRM_RELEASE ?= "0.0.1"
|
|
FORGEFIRM_VERSION_STRING ?= "v${FORGEFIRM_RELEASE}"
|
|
|
|
write_forgefirm_version() {
|
|
echo "${FORGEFIRM_VERSION_STRING}" > ${IMAGE_ROOTFS}${sysconfdir}/forgefirm-version
|
|
echo "ForgeFIRM ${FORGEFIRM_VERSION_STRING}" >> ${IMAGE_ROOTFS}${sysconfdir}/issue
|
|
echo "" >> ${IMAGE_ROOTFS}${sysconfdir}/issue
|
|
echo "ForgeFIRM ${FORGEFIRM_VERSION_STRING}" > ${IMAGE_ROOTFS}${sysconfdir}/motd
|
|
}
|
|
write_forgefirm_version[vardepsexclude] += "DATETIME"
|
|
# No semicolon after a function name here or below (the vardeps rule in
|
|
# classes/forgefirm-image-manifest.bbclass).
|
|
ROOTFS_POSTPROCESS_COMMAND += "write_forgefirm_version "
|
|
|
|
# The license texts ride with the software. The license class writes
|
|
# the image's license manifest (every installed package with its
|
|
# license) and copies each package's license texts into
|
|
# /usr/share/common-licenses, one copy of each generic text and
|
|
# symlinks to it per package. That tree costs several megabytes of
|
|
# small files on a raw ext4 rootfs, so it is packed into one
|
|
# reproducible tar.gz (sorted names, no timestamps, no owners) at
|
|
# /usr/share/forgefirm/licenses.tar.gz and the tree is removed. The
|
|
# control panel serves the bundle and its manifest (GET /system/licenses,
|
|
# GET /system/licenses/manifest). The license class runs first
|
|
# (license_create_manifest is prepended to this list); this step is
|
|
# appended, so it runs after.
|
|
COPY_LIC_MANIFEST = "1"
|
|
COPY_LIC_DIRS = "1"
|
|
|
|
pack_licenses() {
|
|
d="${IMAGE_ROOTFS}${datadir}/common-licenses"
|
|
[ -d "$d" ] || bbfatal "pack_licenses: $d is missing (COPY_LIC_DIRS off?)"
|
|
[ -f "$d/license.manifest" ] || bbfatal "pack_licenses: no license.manifest in $d"
|
|
install -d "${IMAGE_ROOTFS}${datadir}/forgefirm"
|
|
tar -C "${IMAGE_ROOTFS}${datadir}" --sort=name --mtime=@0 --owner=0 --group=0 \
|
|
--numeric-owner -cf - common-licenses | gzip -9 -n \
|
|
> "${IMAGE_ROOTFS}${datadir}/forgefirm/licenses.tar.gz"
|
|
rm -rf "$d"
|
|
}
|
|
ROOTFS_POSTPROCESS_COMMAND += "pack_licenses "
|