Files
ssmithxandClaude Opus 5.5 630f8dff17 nodogsplash from OpenWrt's init script; firewall never blocks PID 1
Packages: nodogsplash 5.0.2 (on libmicrohttpd without TLS, as OpenWrt's
no-ssl variant), openwrt-nodogsplash (init script, stock config,
uci-defaults adding the firewall include, restart.sh detached),
iptables-nft/cross (Guix's iptables-nft points at an x86 iptables in
cross builds).  nodogsplash-service-type: openwrt-init-script, seed
with `enabled' off by default, mutable /etc/nodogsplash/htdocs, ndsctl
and iptables in the profile.

firewall-service-type forks fw4 outside the service context instead of
waiting for it (includes may call herd).  openwrt-rc idles when the
init script opens no instance, instead of exiting into Shepherd's
respawn limit; the init.d shims enable before start/restart.

Verified: splash redirect, ndsctl auth/deauth, firewall reload with the
portal running (PITFALLS #46).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:46:27 -04:00

142 lines
4.4 KiB
Bash
Executable File

#!/bin/sh
# openwrt-rc: run an OpenWrt procd init script (USE_PROCD=1) without procd,
# for one Shepherd service. The script is sourced with procd's functions
# stubbed; the first instance it opens is the one that runs.
#
# NAME is the service's OpenWrt name (/etc/init.d/NAME), SCRIPT the file:
# openwrt-rc NAME SCRIPT exec start_service, then exec the instance's
# command (Shepherd's process)
# openwrt-rc NAME SCRIPT reload PID
# reload_service (or start_service again);
# if the instance changed -- command, env,
# contents of its `file's -- SIGTERM PID
# (Shepherd respawns it), as procd restarts
# changed instances
# openwrt-rc NAME SCRIPT print print the instance's command line
#
# Shepherd must only fork this, never wait for it (PITFALLS #43): scripts
# call ubus, udhcpc, logger...
name="$1" SCRIPT="$2" MODE="$3" PID="$4"
STATE=/var/run/openwrt-rc
initscript="$SCRIPT"
. @base@/lib/functions.sh
_rc_log() { logger -t "openwrt-rc" "$name: $*"; }
# Shell-quote one argument.
_rc_q() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; }
_rc_instances=0 _rc_cur=0
_rc_instance= _rc_cmd= _rc_env= _rc_files= _rc_user=
procd_open_instance() {
_rc_instances=$((_rc_instances + 1))
_rc_cur=$_rc_instances
[ "$_rc_cur" = 1 ] && _rc_instance="${1:-instance1}"
return 0
}
_rc_param() {
local append="$1" key="$2" a
shift 2
[ "$_rc_cur" = 1 ] || return 0
case "$key" in
command)
[ "$append" = 1 ] || _rc_cmd=
for a in "$@"; do _rc_cmd="$_rc_cmd $(_rc_q "$a")"; done ;;
env)
[ "$append" = 1 ] || _rc_env=
for a in "$@"; do _rc_env="$_rc_env $(_rc_q "$a")"; done ;;
file)
[ "$append" = 1 ] || _rc_files=
_rc_files="$_rc_files $*" ;;
user)
_rc_user="$1" ;;
esac
return 0
}
procd_set_param() { _rc_param 0 "$@"; }
procd_append_param() { _rc_param 1 "$@"; }
procd_close_instance() { _rc_cur=0; }
# procd_send_signal SERVICE [INSTANCE] [SIGNAL]
procd_send_signal() {
[ -n "$PID" ] && kill -"${3:-HUP}" "$PID" 2>/dev/null
return 0
}
procd_running() { [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; }
procd_kill() { [ -n "$PID" ] && kill "$PID" 2>/dev/null; return 0; }
rc_procd() { "$@"; }
# Triggers, jails, data, validation: procd features with no counterpart
# here (reload triggers come from procd-compat, hotplug from netifd).
for f in procd_open_service procd_close_service procd_open_trigger \
procd_close_trigger procd_add_reload_trigger procd_add_config_trigger \
procd_add_interface_trigger procd_add_reload_interface_trigger \
procd_add_raw_trigger procd_add_restart_mount_trigger \
procd_add_reload_mount_trigger procd_add_action_mount_trigger \
procd_add_jail procd_add_jail_mount procd_add_jail_mount_rw \
procd_set_config_changed procd_open_data procd_close_data \
procd_open_validate procd_close_validate procd_add_validation \
procd_add_mdns procd_add_mdns_service procd_add_instance \
procd_add_service_data; do
eval "$f() { return 0; }"
done
. "$SCRIPT"
_rc_signature() {
{
echo "$_rc_cmd"; echo "$_rc_env"; echo "$_rc_user"
for f in $_rc_files; do [ -f "$f" ] && md5sum < "$f"; done
} | md5sum | cut -d' ' -f1
}
case "$MODE" in
exec|print)
start_service ;;
reload)
if type reload_service 2>/dev/null | grep -q function; then
reload_service
else
start_service
fi ;;
*)
echo "usage: $0 NAME SCRIPT exec|reload PID|print" >&2
exit 1 ;;
esac
if [ -z "$_rc_cmd" ]; then
# No instance (disabled in its UCI config, say): procd keeps such a
# service with no instances. Idle, so Shepherd doesn't respawn us into
# its respawn limit; a reload that finds an instance restarts us.
case "$MODE" in
exec)
_rc_log "no instance to run; idle until a reload finds one"
mkdir -p "$STATE"
_rc_signature > "$STATE/$name"
exec sleep 2147483647 ;;
print)
exit 1 ;;
esac
fi
[ "$_rc_instances" -gt 1 ] &&
_rc_log "$_rc_instances instances; only the first ($_rc_instance) runs"
case "$MODE" in
exec)
mkdir -p "$STATE"
_rc_signature > "$STATE/$name"
[ -n "$_rc_user" ] && [ "$_rc_user" != root ] &&
_rc_log "procd user '$_rc_user' not applied: runs as root"
eval "exec env $_rc_env $_rc_cmd" ;;
reload)
if [ "$(_rc_signature)" != "$(cat "$STATE/$name" 2>/dev/null)" ]; then
_rc_log "instance changed: restarting"
kill -TERM "$PID" 2>/dev/null
fi ;;
print)
echo "${_rc_env:+env$_rc_env}$_rc_cmd" ;;
esac