Files
Andrei Ovcharenko 1bb94b900f prplmesh: add package
Add the prplMesh 6.0.1 EasyMesh controller and agent for stock OpenWrt.
The package keeps the installed hostapd/wpad stack and drives it over
the existing UCI, ubus, nl80211 and hostapd control sockets.

The hostapd control interface client is a small self-contained
implementation added by patch 120, so no hostapd source tree is
downloaded, bundled or duplicated at build time and the system hostapd
stays the only hostapd. Binaries install under /usr/libexec/prplmesh,
read-only data under /usr/share/prplmesh, shared libraries under
/usr/lib, configuration under /etc/config/prplmesh. Of the prplOS
helper scripts only prplmesh_utils.sh is installed: patch 161 teaches
its platform guard stock OpenWrt and the init's daily log roller
invokes it; the other helpers stay uninstalled. Its option parsing
needs util-linux getopt (the busybox applet is off by default), hence
the getopt dependency.
At build time prplMesh generates its IEEE 1905 / EasyMesh TLV classes
from YAML definitions with its own Python generator, hence the host
Python and PyYAML build dependencies.

Stock builds run without NBAPI, so the controller skips the
CurrentOperatingClassProfile data-model cleanup for radios without a
data model path, and legacy vendor diagnostic measurements default to
off while standard EasyMesh metrics stay enabled. The controller also
registers radios that appear only in Topology Responses: some stock
agents never run WSC onboarding for a band (Wi-Fi 7 units and their
6 GHz radio), and such radios, their BSSes and their clients stayed
invisible otherwise. Agents in turn import clients that are already
associated when the agent starts: patch 150 walks hostapd's
STA-FIRST/STA-NEXT list on the nl80211 AP and monitor HALs and
reconciles the result every 60 seconds, so stations that associated
while the agent was down appear without waiting for a reassociation,
and stale entries are disconnected only after a cleanly enumerated
sweep. The init preflights every binary the selected management mode
needs before registering any procd instance, and the example
credentials ship empty so an enabled but unconfigured service fails
validation loudly instead of deploying a published passphrase.
A test-version.sh verifies the installed version from the shipped
version file instead of probing daemons, and a test.sh checks the
shipped log levels and the roll-helper install.

Production log levels default to error, info, warning and fatal for
file and syslog output of both daemons. Log retention is enforced by
the upstream roll helper (its platform guard now accepts openwrt):
the init prunes previous-boot logs once per boot before the daemons
come up (a /var/run stamp keeps procd reload triggers, which re-enter
start_service with the daemons live, from re-rolling their logs) and
a procd-supervised roller repeats the roll daily; an image stripped
of the helper runs without rotation instead of refusing to start.
Link-metrics reporting no longer warns about wireless-capable radios
deliberately not used for backhaul.

An earlier revision of this integration, differing only in the wpa_ctrl
client origin and the install prefix, runs on OpenWrt 25.12.5
mediatek/mt7622 with two Ethernet-connected EasyMesh agents.

Ref: https://gitlab.com/prpl-foundation/prplmesh/prplMesh
Ref: https://gitlab.com/kreout/prpl-mesh-mercusys/-/merge_requests/1
Signed-off-by: Andrei Ovcharenko <a@krot.name>
2026-09-14 11:45:49 +03:00

134 lines
3.8 KiB
Bash

#!/bin/sh /etc/rc.common
# shellcheck disable=SC2034
# shellcheck disable=SC3043 # ash supports local
START=99
STOP=10
USE_PROCD=1
PRPLMESH_DIR=/usr/libexec/prplmesh
LOG_ROLL_INTERVAL_SECONDS=86400
mesh_enabled() {
[ "$(uci -q get prplmesh.config.enabled)" = "1" ]
}
register_process() {
local name="$1"; shift
procd_open_instance "$name"
procd_set_param command "$@"
procd_set_param env PRPLMESH_DIR="$PRPLMESH_DIR"
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
register_log_roller() {
local helper="$PRPLMESH_DIR/scripts/prplmesh_utils.sh"
procd_open_instance log-roller
# shellcheck disable=SC2016
procd_set_param command /bin/sh -c '
helper=$1
interval=$2
while sleep "$interval"; do
"$helper" roll_logs || logger -t prplmesh "log roll failed"
done' sh "$helper" "$LOG_ROLL_INTERVAL_SECONDS"
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
start_service() {
local mode need_agent=1 need_controller=1 log_roller=1
mesh_enabled || return 0
# A standalone controller runs without local radios, a plain agent
# without the controller; everything else keeps both.
mode="$(uci -q get prplmesh.config.management_mode)"
case "$mode" in
Multi-AP-Controller) need_agent=0 ;;
Multi-AP-Agent) need_controller=0 ;;
esac
[ -x "$PRPLMESH_DIR/bin/ieee1905_transport" ] || {
logger -t prplmesh "ieee1905_transport is missing or not executable"
return 1
}
# Log rotation is not worth keeping the mesh down for: run without
# it when the helper is stripped from the image.
[ -x "$PRPLMESH_DIR/scripts/prplmesh_utils.sh" ] || {
logger -t prplmesh "log-roll helper is missing or not executable; log rotation disabled"
log_roller=0
}
if [ "$need_controller" = 1 ]; then
[ -x "$PRPLMESH_DIR/bin/beerocks_controller" ] || {
logger -t prplmesh "beerocks_controller is missing or not executable"
return 1
}
fi
if [ "$need_agent" = 1 ]; then
[ -x "$PRPLMESH_DIR/bin/beerocks_agent" ] || {
logger -t prplmesh "beerocks_agent is missing or not executable"
return 1
}
[ -n "$(uci -q get prplmesh.config.mandatory_interfaces)" ] || {
logger -t prplmesh "mandatory_interfaces not set; agent left stopped"
return 1
}
fi
if [ "$log_roller" = 1 ] && [ ! -e /var/run/prplmesh.boot_roll ]; then
# Prune previous-boot logs before the daemons open new ones: the
# helper only deletes old rotations and signals running daemons,
# and none of ours are up yet, so nothing is rotated out early.
# Reload triggers re-enter start_service with the daemons up
# (procd reload falls through to start), so stamp the roll as
# done for this boot; /var/run is tmpfs and clears the stamp.
"$PRPLMESH_DIR/scripts/prplmesh_utils.sh" roll_logs >/dev/null 2>&1 \
|| logger -t prplmesh "startup log roll failed"
touch /var/run/prplmesh.boot_roll
fi
register_process transport "$PRPLMESH_DIR/bin/ieee1905_transport"
if [ "$log_roller" = 1 ]; then
register_log_roller
fi
if [ "$need_controller" = 1 ]; then
register_process controller "$PRPLMESH_DIR/bin/beerocks_controller"
fi
if [ "$need_agent" = 1 ]; then
# hostapd recreates its control sockets on boot and on wireless
# reloads, so wait for them under procd supervision instead of
# failing the whole start on a transient race.
# shellcheck disable=SC2016
register_process agent /bin/sh -c '
while :; do
ok=1
IFS=","
for interface in $(uci -q get prplmesh.config.mandatory_interfaces); do
unset IFS
[ -n "$interface" ] || continue
[ -S "/var/run/hostapd/$interface" ] || { ok=0; break; }
done
unset IFS
[ "$ok" = 1 ] && break
sleep 5
done
exec "$PRPLMESH_DIR/bin/beerocks_agent"'
fi
}
service_triggers() {
procd_add_reload_trigger prplmesh wireless network
}