commissioning: the layer, the acceptance tests, the harness rule, the docs, and the bench drills

meta-forgefirm: the forgefirm-users init replays the account at boot;
sshd refuses root and empty passwords and runs only while the panel
turns it on; the release image keeps an empty root password for the
console; the console banner; avahi announces forgefirm.local; https in
libmicrohttpd and ulfius; the panel on 80 and 443; the license bundle on
the rootfs; release.sh checks the root policy on the built rootfs.

forgetest: the commission suites (commission, commission_dark,
commission_sheet: 23 cases); the runner turns cloud mode on with the
typed phrase for a test that declares it; the baseline's motor_lock is
0; the log-export test checks the bundle for the camera key; the record
helpers write bytes as given and join the daemon's paths as POSIX. The
stream harness gains rule 24: a hold verdict is held again after a
resume. Bench drills: lens_travel.py and lens_stop_accel.py.

Docs: BRINGUP carries the present state; CAMPAIGN-LOG carries the dated
record.
This commit is contained in:
ScottW514
2026-09-06 19:56:05 -04:00
parent ff9796cde6
commit 97287aa6a9
59 changed files with 15113 additions and 9139 deletions
+30
View File
@@ -8,6 +8,36 @@ DISTRO_FEATURES:remove = " \
3g alsa avahi bluetooth bluez5 ext2 irda nfc nfs pci pcmcia \
pulseaudio vulkan wayland x11 zeroconf "
# mDNS: the image installs avahi-daemon by name (forgefirm-image.bb), so
# the control panel answers at forgefirm.local. The avahi and zeroconf
# features stay removed above: zeroconf would install
# packagegroup-base-zeroconf (the daemon plus libnss-mdns), and avahi
# would switch other recipes' avahi options on. The build is trimmed to
# the daemon: no D-Bus (so no libavahi-client, no avahi-utils, no bus
# activation; the daemon reads /etc/avahi/services itself), no GTK or Qt
# front ends, no libdns_sd compatibility library, no libevent binding.
# Python bindings are off in the recipe itself.
PACKAGECONFIG:pn-avahi = ""
# libnss-mdns is what avahi-daemon recommends: an NSS module so the
# machine itself resolves .local names. Nothing on the machine does.
BAD_RECOMMENDATIONS += "libnss-mdns"
# TLS: forgectrl serves HTTPS on 443 with a self-signed certificate
# through libmicrohttpd and ulfius, and links GnuTLS itself. GnuTLS is
# trimmed to the library:
# no seccomp the distro default adds libseccomp for the gnutls test
# programs; the library does not use it
# no libidn internationalized host names in certificates; the
# machine's own certificate is plain ASCII (saves libidn2)
# no p11-kit PKCS#11 tokens and the system trust store; the machine
# presents its own certificate and verifies no peer
# through GnuTLS (saves p11-kit and its modules)
# libtasn1 stays a shared library: the bundled copy is the same
# code without poky's CVE patches
# no tpm, fips, dane, and no certificate compression (brotli, zlib,
# zstd), none of which the panel uses
PACKAGECONFIG:pn-gnutls = "libtasn1"
# opengl stays: forgectrl's camera demosaic runs as GLES2 fragment
# shaders on the GC880 (etnaviv), reached through surfaceless EGL with
# no display stack. Mesa is trimmed to exactly that: the etnaviv
@@ -0,0 +1,33 @@
# ForgeFIRM avahi-daemon configuration (avahi-daemon.conf(5)).
# The machine answers forgefirm.local on the WiFi link (and on eth0 when
# the machine has one), over IPv4 and IPv6. It publishes its addresses
# and the services in /etc/avahi/services, nothing else: no workstation
# record, no host information, no wide-area lookups, no reflector.
[server]
host-name=forgefirm
use-ipv4=yes
use-ipv6=yes
allow-interfaces=wlan0,eth0
ratelimit-interval-usec=1000000
ratelimit-burst=1000
[wide-area]
enable-wide-area=no
[publish]
publish-hinfo=no
publish-workstation=no
publish-addresses=yes
publish-domain=yes
[reflector]
enable-reflector=no
[rlimits]
rlimit-core=0
rlimit-data=8388608
rlimit-fsize=0
rlimit-nofile=768
rlimit-stack=8388608
rlimit-nproc=3
@@ -0,0 +1,21 @@
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<!-- ForgeFIRM control panel: HTTPS on 443 and HTTP on 80, served by
forgectrl. %h is the host name (avahi.service(5)). -->
<service-group>
<name replace-wildcards="yes">ForgeFIRM on %h</name>
<service>
<type>_https._tcp</type>
<port>443</port>
</service>
<service>
<type>_http._tcp</type>
<port>80</port>
</service>
</service-group>
@@ -0,0 +1,17 @@
FILESEXTRAPATHS:prepend := "${THISDIR}/${BPN}:"
# mDNS for the control panel: the machine answers forgefirm.local and
# advertises the panel on HTTPS 443 and HTTP 80. Only avahi-daemon is
# installed (forgefirm-image.bb); the build options that keep it to the
# daemon are in conf/distro/forgefirm.conf. The daemon reads the service
# file itself: no D-Bus is involved.
SRC_URI += " \
file://avahi-daemon.conf \
file://forgefirm.service \
"
do_install:append() {
install -m 0644 ${WORKDIR}/avahi-daemon.conf ${D}${sysconfdir}/avahi/avahi-daemon.conf
install -d ${D}${sysconfdir}/avahi/services
install -m 0644 ${WORKDIR}/forgefirm.service ${D}${sysconfdir}/avahi/services/forgefirm.service
}
@@ -0,0 +1,39 @@
# ForgeFIRM SSH policy, set in the installed files so the release image
# carries it as built:
# PermitRootLogin no root logs in at the serial console only
# PermitEmptyPasswords no an account without a password cannot log in
# PasswordAuthentication yes operator accounts log in with a password
# The dev image's debug-tweaks turns PermitRootLogin and
# PermitEmptyPasswords back to yes at rootfs time (ssh_allow_root_login
# and ssh_allow_empty_password in rootfs-postcommands.bbclass match the
# active lines too), so the bench keeps root over SSH.
#
# The init script starts sshd only when the control panel has turned it
# on (/run/forgefirm/ssh-enabled, tmpfs, gone at reboot) or on the dev
# image (/etc/forgefirm-dev). The guard sits in check_for_no_start, which
# start, reload and restart call; stop is never gated.
do_install:append() {
for config in sshd_config sshd_config_readonly; do
f=${D}${sysconfdir}/ssh/$config
[ -e "$f" ] || continue
sed -i \
-e 's/^[#[:space:]]*PermitRootLogin .*/PermitRootLogin no/' \
-e 's/^[#[:space:]]*PermitEmptyPasswords .*/PermitEmptyPasswords no/' \
-e 's/^[#[:space:]]*PasswordAuthentication .*/PasswordAuthentication yes/' \
"$f"
grep -q '^PermitRootLogin no$' "$f" \
&& grep -q '^PermitEmptyPasswords no$' "$f" \
&& grep -q '^PasswordAuthentication yes$' "$f" \
|| bbfatal "$config: the ForgeFIRM policy lines did not land"
done
init=${D}${sysconfdir}/init.d/sshd
sed -i '/^check_for_no_start() {$/a\
[ -e /run/forgefirm/ssh-enabled ] || [ -e /etc/forgefirm-dev ] || {\
echo "sshd: not enabled (turn it on from the ForgeFIRM control panel)"\
exit 0\
}' "$init"
grep -q 'forgefirm/ssh-enabled' "$init" \
|| bbfatal "init.d/sshd: the check_for_no_start anchor was not found"
}
@@ -23,12 +23,14 @@ S = "${WORKDIR}/git"
inherit cmake pkgconfig
# curl backs the default-ON WITH_CURL client-request API (find_package REQUIRED).
DEPENDS = "jansson libmicrohttpd curl orcania yder"
# GnuTLS backs the secure framework: forgectrl serves HTTPS on 443 with
# a self-signed certificate.
DEPENDS = "jansson libmicrohttpd curl orcania yder gnutls"
# No systemd journald on the forgefirm image. forgectrl serves plain HTTP
# and no websockets: no GnuTLS (which keeps nettle, gmp, libunistring and
# libtasn1 off the rootfs) and no websocket support, which depends on it.
EXTRA_OECMAKE += "-DWITH_JOURNALD=off -DWITH_GNUTLS=off -DWITH_WEBSOCKET=off"
# No systemd journald on the forgefirm image. GnuTLS is on for HTTPS
# (the trimmed GnuTLS build is set in conf/distro/forgefirm.conf); no
# websocket support, which forgectrl does not use.
EXTRA_OECMAKE += "-DWITH_JOURNALD=off -DWITH_GNUTLS=on -DWITH_WEBSOCKET=off"
# ulfius builds itself with -Werror -Wconversion; under Yocto's arm32
# time64 ABI (-D_TIME_BITS=64) a long-long-to-long time conversion in
@@ -14,7 +14,9 @@ S = "${WORKDIR}/git"
inherit cmake update-rc.d forgefirm-manifest
DEPENDS += "ulfius jpeg"
# jansson (settings, API bodies) and gnutls (the panel certificate) are
# linked directly, not only through ulfius.
DEPENDS += "ulfius jpeg jansson gnutls libxcrypt"
# media-ctl / v4l2-ctl configure the imx-media pipeline at runtime;
# the update manager drives ffboot + fwup and verifies against the
# shipped keyring; release checks and downloads use curl; the WiFi
@@ -0,0 +1,12 @@
#!/bin/sh
# udhcpc hook: /usr/share/udhcpc/default.script runs every file in
# /etc/udhcpc.d with the lease event as $1; 50default applies the lease
# first. Refresh the address block of the console banner on every event.
case "$1" in
bound|renew|deconfig|leasefail|nak)
/usr/sbin/forgefirm-banner
;;
esac
exit 0
@@ -0,0 +1,63 @@
#!/bin/sh
# Rewrites the address block of /etc/issue, the serial-console login
# banner: the control panel by mDNS name, then one https:// URL per
# global address of wlan0 (and eth0 when the machine has one). Every
# other line of /etc/issue stays as the image build wrote it. The block
# sits between the marker lines "# ForgeFIRM addresses" and "# end" and
# is appended when absent. Called by the init script at boot and by the
# udhcpc hook on every lease event. Idempotent: the file is written only
# when the block changes.
PATH=/sbin:/usr/sbin:/bin:/usr/bin
ISSUE=/etc/issue
MARK_BEGIN='# ForgeFIRM addresses'
MARK_END='# end'
PANEL='Control panel: https://forgefirm.local/'
# One URL per global address; an IPv6 address gets its URL brackets.
# Tentative, deprecated and temporary addresses are left out.
addresses () {
command -v ip >/dev/null 2>&1 || return 0
for dev in wlan0 eth0; do
[ -d "/sys/class/net/$dev" ] || continue
ip addr show dev "$dev" 2>/dev/null | awk '
($1 == "inet" || $1 == "inet6") && / scope global/ \
&& !/tentative/ && !/deprecated/ && !/temporary/ {
a = $2
sub(/\/.*/, "", a)
if ($1 == "inet6") a = "[" a "]"
print "https://" a "/"
}'
done
}
block () {
echo "$MARK_BEGIN"
echo "$PANEL"
addrs=$(addresses)
if [ -n "$addrs" ]; then
echo "$addrs"
else
echo "no network address yet"
fi
echo "$MARK_END"
}
[ -f "$ISSUE" ] || exit 0
new=$(block)
old=$(awk -v b="$MARK_BEGIN" -v e="$MARK_END" \
'$0 == b { p = 1 } p { print } $0 == e { p = 0 }' "$ISSUE")
[ "$new" = "$old" ] && exit 0
tmp="$ISSUE.tmp.$$"
awk -v b="$MARK_BEGIN" -v e="$MARK_END" -v blk="$new" '
$0 == b { print blk; seen = 1; skip = 1; next }
$0 == e && skip { skip = 0; next }
!skip { print }
END { if (!seen) print blk }
' "$ISSUE" > "$tmp" || { rm -f "$tmp"; exit 1; }
chmod 0644 "$tmp"
mv -f "$tmp" "$ISSUE"
exit 0
@@ -0,0 +1,26 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: forgefirm-banner
# Required-Start: $network
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: ForgeFIRM console banner: control panel addresses
### END INIT INFO
# Writes the address block of /etc/issue once at boot; the udhcpc hook
# (/etc/udhcpc.d/60forgefirm-banner) keeps it current afterward.
case "$1" in
start|restart|reload|force-reload)
/usr/sbin/forgefirm-banner
;;
stop)
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0
@@ -0,0 +1,30 @@
SUMMARY = "ForgeFIRM console banner: the control panel addresses in /etc/issue"
DESCRIPTION = "Keeps an address block in the serial-console login banner \
(/etc/issue): the control panel by mDNS name and by every global address \
of wlan0 and eth0. Refreshed at boot and on every DHCP lease event."
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = " \
file://forgefirm-banner \
file://forgefirm-banner.init \
file://60forgefirm-banner \
"
S = "${WORKDIR}"
inherit update-rc.d
INITSCRIPT_NAME = "forgefirm-banner"
# 99: after networking (S01); an address the lease brings later arrives
# through the udhcpc hook.
INITSCRIPT_PARAMS = "start 99 2 3 4 5 ."
do_install() {
install -Dm 0755 ${WORKDIR}/forgefirm-banner ${D}${sbindir}/forgefirm-banner
install -Dm 0755 ${WORKDIR}/forgefirm-banner.init ${D}${sysconfdir}/init.d/forgefirm-banner
# busybox udhcpc runs /etc/udhcpc.d/* (run-parts) on every lease event
install -Dm 0755 ${WORKDIR}/60forgefirm-banner ${D}${sysconfdir}/udhcpc.d/60forgefirm-banner
}
FILES:${PN} += "${sysconfdir}/udhcpc.d"
@@ -0,0 +1,11 @@
# ForgeFIRM: an interactive root shell (the serial console, or SSH on
# the dev image) starts with a warning. Non-interactive shells (scp,
# rsync, ssh with a command) print nothing.
case "$-" in
*i*)
if [ "$(id -u 2>/dev/null)" = "0" ]; then
echo "You are root on a laser cutter."
echo "A wrong command here can damage the machine or hurt someone. Take care."
fi
;;
esac
@@ -0,0 +1,152 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: forgefirm-users
# Required-Start: $local_fs
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: ForgeFIRM accounts: replay the record into the system files
### END INIT INFO
# The account record is the source of truth for the operator accounts;
# the account files on the rootfs are rebuilt from it. forgectrl writes
# the record and runs "reload" here. At boot this runs at S05: /data is
# mounted (mountall, rcS) and sshd (S09) is not up yet.
#
# Record: /data/forgefirm/users, one line per account
# name:hash:uid
# hash is a sha512-crypt string ($6$...), uid is 1000 or more.
# Home directory /data/forgefirm/home/<name>, mode 0700. Shell /bin/sh.
#
# Rules:
# - idempotent: a second run changes nothing;
# - no record: nothing happens (a machine before the first-run wizard,
# or a bench image without /data);
# - every local account with a uid from 1000 to 65533 that the record
# does not name is removed, so an account reset removes the old
# account; root and the system accounts are never touched.
PATH=/sbin:/usr/sbin:/bin:/usr/bin
RECORD=/data/forgefirm/users
HOMES=/data/forgefirm/home
LOGIN_SHELL=/bin/sh
UID_LOW=1000
UID_HIGH=65533
log () {
echo "forgefirm-users: $*"
{ echo "forgefirm-users: $*" > /dev/kmsg; } 2>/dev/null
}
# A portable account name: a letter or underscore, then letters, digits,
# underscore, dash.
valid_name () {
case "$1" in
''|[!a-z_]*|*[!a-z0-9_-]*) return 1 ;;
esac
return 0
}
valid_uid () {
case "$1" in
''|*[!0-9]*) return 1 ;;
esac
[ "$1" -ge "$UID_LOW" ] && [ "$1" -le "$UID_HIGH" ]
}
passwd_uid () {
awk -F: -v n="$1" '$1 == n { print $3; exit }' /etc/passwd
}
group_exists () {
awk -F: -v n="$1" '$1 == n { f = 1 } END { exit !f }' /etc/group
}
# ensure_account name hash uid
ensure_account () {
name=$1
hash=$2
uid=$3
home="$HOMES/$name"
cur=$(passwd_uid "$name")
if [ -n "$cur" ] && [ "$cur" != "$uid" ]; then
log "account $name has uid $cur, the record says $uid: recreating it"
userdel -f "$name" >/dev/null 2>&1
groupdel "$name" >/dev/null 2>&1
cur=""
fi
if ! group_exists "$name"; then
groupadd -g "$uid" "$name" || { log "groupadd $name failed"; return 1; }
fi
if [ -z "$cur" ]; then
useradd -M -u "$uid" -g "$uid" -d "$home" -s "$LOGIN_SHELL" "$name" \
|| { log "useradd $name failed"; return 1; }
log "account $name created (uid $uid)"
fi
if [ ! -d "$home" ]; then
mkdir -p "$home" && chmod 0700 "$home"
fi
chown "$uid:$uid" "$home"
# -p stores the hash as it is. Home and shell are set again so an
# account file edited by hand converges on the record.
usermod -d "$home" -s "$LOGIN_SHELL" -p "$hash" "$name" \
|| log "usermod $name failed"
}
# Remove every local account in the operator uid range that the record
# does not name. KEEP holds the record's names, space separated.
prune () {
for name in $(awk -F: -v lo="$UID_LOW" -v hi="$UID_HIGH" \
'$3 + 0 >= lo && $3 + 0 <= hi { print $1 }' /etc/passwd); do
[ "$name" = root ] && continue
case " $KEEP " in
*" $name "*) continue ;;
esac
log "removing account $name (not in the record)"
userdel -f "$name" >/dev/null 2>&1 || log "userdel $name failed"
groupdel "$name" >/dev/null 2>&1
done
}
replay () {
[ -f "$RECORD" ] || return 0
mkdir -p "$HOMES"
KEEP=""
while IFS=: read -r name hash uid rest; do
[ -n "$name" ] || continue
case "$name" in '#'*) continue ;; esac
if ! valid_name "$name" || ! valid_uid "$uid" || [ -z "$hash" ]; then
log "skipping a malformed record line ($name)"
continue
fi
if [ "$name" = root ]; then
log "ignoring a root line in the record"
continue
fi
ensure_account "$name" "$hash" "$uid" && KEEP="$KEEP $name"
done < "$RECORD"
prune
}
case "$1" in
start|reload|restart|force-reload)
replay
;;
stop)
;;
status)
awk -F: -v lo="$UID_LOW" -v hi="$UID_HIGH" \
'$3 + 0 >= lo && $3 + 0 <= hi { print $1 " (uid " $3 ")" }' /etc/passwd
;;
*)
echo "Usage: $0 {start|stop|reload|restart|status}"
exit 1
;;
esac
exit 0
@@ -0,0 +1,29 @@
SUMMARY = "ForgeFIRM operator accounts: record replay and the root shell warning"
DESCRIPTION = "Replays the account record (/data/forgefirm/users, written \
by forgectrl) into the system account files at boot and on reload, \
removes the local accounts the record does not name, and installs the \
warning an interactive root shell prints."
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = " \
file://forgefirm-users.init \
file://forgefirm-root.sh \
"
S = "${WORKDIR}"
inherit update-rc.d
INITSCRIPT_NAME = "forgefirm-users"
# 05: rcS has run (mountall mounted /data at S03) and sshd starts at
# S09, so the accounts exist before the first login can arrive.
INITSCRIPT_PARAMS = "start 05 2 3 4 5 ."
# useradd, groupadd, usermod, userdel, groupdel
RDEPENDS:${PN} += "shadow"
do_install() {
install -Dm 0755 ${WORKDIR}/forgefirm-users.init ${D}${sysconfdir}/init.d/forgefirm-users
install -Dm 0644 ${WORKDIR}/forgefirm-root.sh ${D}${sysconfdir}/profile.d/forgefirm-root.sh
}
@@ -18,7 +18,7 @@
DAEMON_BIN=/usr/bin/grblHAL_glowforge
TOKEN_FILE=/data/forgefirm/panel.token
CTRL_URL="http://127.0.0.1:${FORGECTRL_PORT:-8080}"
CTRL_URL="http://127.0.0.1:${FORGECTRL_PORT:-80}"
super_post() {
[ -r "$TOKEN_FILE" ] || return 1
@@ -43,3 +43,13 @@ IMAGE_ROOTFS_EXTRA_SPACE = "262144"
# Dev builds identify by build timestamp (matches the artifact name),
# tagged so a bench machine is never mistaken for a release.
FORGEFIRM_VERSION_STRING = "${DATETIME} (dev)"
# /etc/forgefirm-dev marks a dev image on the rootfs; its content is the
# version string. The sshd init script starts sshd on a dev image without
# the panel's enable flag (/run/forgefirm/ssh-enabled), and forgectrl
# reads the same marker. A release image has no such file.
write_forgefirm_dev_marker() {
echo "${FORGEFIRM_VERSION_STRING}" > ${IMAGE_ROOTFS}${sysconfdir}/forgefirm-dev
}
write_forgefirm_dev_marker[vardepsexclude] += "DATETIME"
ROOTFS_POSTPROCESS_COMMAND += "write_forgefirm_dev_marker;"
@@ -23,9 +23,10 @@ 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 :8080): controller
# supervisor, pulse-device broker, cooling engine, cameras, telemetry,
# settings, diagnostics, web control panel, and A/B updates.
# 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
@@ -44,6 +45,31 @@ IMAGE_INSTALL:remove = "python3 ${FORGEFIRM_RELEASE_TRIM}"
# 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: replays 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).
# 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 avahi-daemon"
# 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).
@@ -80,3 +106,30 @@ write_forgefirm_version() {
}
write_forgefirm_version[vardepsexclude] += "DATETIME"
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;"
@@ -1,3 +1,8 @@
# forgectrl serves plain HTTP on the LAN: no TLS in the HTTP library, so
# the rootfs carries no GnuTLS stack for it.
PACKAGECONFIG:remove = "https"
# forgectrl serves HTTPS on 443 with a self-signed certificate through
# this library, so the https option (GnuTLS) is on; curl is the poky
# default (it builds the library's own test client). libgcrypt is a
# build-time dependency of the https option in the poky recipe;
# libmicrohttpd links it only for a GnuTLS older than 2.12, so it does
# not reach the rootfs. The GnuTLS build options are in
# conf/distro/forgefirm.conf.
PACKAGECONFIG = "curl https"