mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user