infra: FLY_FEED_VIA is validated at deploy and read case-blind by the watchdog; flyedge gets the page's CPUs

Review round 1. flysim reads FLY_FEED_VIA case-insensitively, so check 2 must
too: with Bus in fly.env it read flysim's zeroed counters and would have
escalated to restarting flystage and flycast every pass. It now lowercases.
05-deploy.sh runs the value through feed_via_normalize (lib/common.sh) and
dies on anything but direct|bus, writing the lowercased word, so a typo is a
deploy refusal instead of a flysim boot loop.

lint's fly.target check read only the first physical Wants=/Requires= line;
target_pulls joins backslash continuations and drops comments, with a
fixture that names a unit only on a continuation line. The cpuset loop writes
a flyedge drop-in on the page CPUs, and lint holds it.
This commit is contained in:
acamilo 2026-09-23 11:50:11 +00:00
parent 1ba5c80053
commit 2da7f688a8
4 changed files with 87 additions and 8 deletions

View file

@ -413,6 +413,10 @@ log "05-deploy: non-secret env files"
# never drift apart (see cpuset_partition's own header comment). They are
# assigned in section 0b, which needs them earlier than this for the
# deploy-time cpu pinning; nothing between here and there changes them.
# Who serves the feed (docs/design/flybus.md): refused here rather than at flysim's boot.
FLY_FEED_VIA_EFFECTIVE="$(feed_via_normalize "${FLY_FEED_VIA:-}")" \
|| die "05-deploy: FLY_FEED_VIA must be 'direct' or 'bus', got '${FLY_FEED_VIA}'"
tmp_fly_env="$(mktemp)"
tmp_flypush_env="$(mktemp)"
trap 'rm -f "$tmp_fly_env" "$tmp_flypush_env"' EXIT
@ -507,7 +511,8 @@ trap 'rm -f "$tmp_fly_env" "$tmp_flypush_env"' EXIT
# unconditionally, like FLY_MACRO_MODE, so one grep says which a box runs.
# Watchdog check 2 reads this line to know whose /metrics carries the
# feed counters (flysim's :9101, or flyedge's loopback :9102).
echo "FLY_FEED_VIA=${FLY_FEED_VIA:-direct}"
# Validated and lowercased above (feed_via_normalize).
echo "FLY_FEED_VIA=${FLY_FEED_VIA_EFFECTIVE}"
# How long a macro leaves a target alone after a walk to it aborted
# (macros.md section 12.1, the Viridian stall). Only written when it is set,
# because the default lives in the crate and a box that has not tuned it
@ -642,9 +647,11 @@ if [ -n "${CPUSET:-}" ]; then
"leaves cpuset.cpus.effective empty and the unit unstartable."
else
read -r sim_cpus page_cpus encoder_cpus <<< "$(cpuset_partition "$CPUSET" "$RAYON_THREADS_EFFECTIVE" "$ENCODER_CORES_EFFECTIVE")"
log "05-deploy: cpuset partition — flysim=$sim_cpus, xvfb/flystage/flystage-web/pulse/mediamtx=$page_cpus, flycast=$encoder_cpus"
log "05-deploy: cpuset partition — flysim=$sim_cpus, xvfb/flystage/flystage-web/pulse/mediamtx/flyedge=$page_cpus, flycast=$encoder_cpus"
tmp_dropin="$(mktemp)"
for u in flysim xvfb flystage flystage-web flycast pulse mediamtx; do
# flyedge is off by default, but its drop-in is written with the rest so that the day
# it is enabled it serves the page from the page's CPUs, never from flysim's.
for u in flysim xvfb flystage flystage-web flycast pulse mediamtx flyedge; do
case "$u" in
flysim) cpus="$sim_cpus" ;;
flycast) cpus="$encoder_cpus" ;;

View file

@ -351,7 +351,8 @@ feed_metrics_url() {
fi
local via=""
if [ -f "$FLY_ENV_FILE" ]; then
via="$(awk -F= '/^FLY_FEED_VIA=/ { print $2; exit }' "$FLY_ENV_FILE" 2>/dev/null | tr -d ' \r' || true)"
# Lowercased: flysim reads the value case-insensitively, so `Bus` is bus mode.
via="$(awk -F= '/^FLY_FEED_VIA=/ { print $2; exit }' "$FLY_ENV_FILE" 2>/dev/null | tr -d ' \r"' | tr '[:upper:]' '[:lower:]' || true)"
fi
if [ "$via" = "bus" ]; then
echo "$FLY_EDGE_METRICS_URL"

View file

@ -154,6 +154,20 @@ require_release_tag() {
# (An earlier, eight-cpu version of this same live hotfix — CPUSET=
# 1,3,5,7,9,11,13,15, ENCODER_CORES=2 (the default) — gave flysim=1,3,5,7,
# page=9,11, flycast=13,15; infra/tests/lint.sh checks both shapes.)
# feed_via_normalize VALUE — print FLY_FEED_VIA lowercased (empty means "direct"), or
# return 1 for anything but direct|bus. flysim itself reads the value case-insensitively
# and refuses anything else at boot, which on a container is a restart loop; 05-deploy.sh
# refuses it at deploy instead and writes the lowercased word, so watchdog check 2 and
# flysim can never read the same line two ways (docs/design/flybus.md).
feed_via_normalize() {
local via
via="$(printf '%s' "${1:-direct}" | tr '[:upper:]' '[:lower:]')"
case "$via" in
direct|bus) printf '%s\n' "$via" ;;
*) return 1 ;;
esac
}
cpuset_partition() {
local cpuset="$1" rayon_threads="$2" encoder_cores="${3:-2}"
local sim_cpus remainder remainder_count page_count page_cpus encoder_cpus

View file

@ -460,19 +460,73 @@ else
&& pass "flyedge.service keeps its metrics on loopback" \
|| fail "flyedge.service FLY_EDGE_METRICS_ADDR must be a 127.0.0.1 address"
fi
if grep -E '^(Wants|Requires)=' "$INFRA_DIR/units/fly.target" | grep -q 'flyedge'; then
# Every unit a target's Wants=/Requires= names, with backslash continuations joined and
# comments dropped: fly.target spreads both lists over several physical lines, and the
# continuation line is exactly where a new unit would be added.
target_pulls() {
awk '
/^[[:space:]]*[#;]/ { next }
{
line = $0
cont = sub(/\\[[:space:]]*$/, "", line)
buf = buf line
if (cont) next
if (buf ~ /^[[:space:]]*(Wants|Requires)=/) { sub(/^[^=]*=/, "", buf); print buf }
buf = ""
}
' "$1" | tr -s ' \t' '\n' | grep -v '^$' || true
}
if target_pulls "$INFRA_DIR/units/fly.target" | grep -qx 'flyedge.service'; then
fail "fly.target pulls flyedge.service in; it must stay off until the operator enables it"
else
pass "fly.target does not pull flyedge.service in"
fi
# The parser itself: a unit named only on a continuation line must be found, a commented one
# must not, and the real fly.target must still yield flysim.service.
tp_fixture="$(mktemp "${TMPDIR:-/tmp}/fly-lint-target.XXXXXX")"
cat > "$tp_fixture" <<'TPTARGET'
[Unit]
Wants=network-online.target xvfb.service \
flysim.service flyedge.service
# Requires=commented.service
Requires=xvfb.service \
pulse.service
TPTARGET
tp_units="$(target_pulls "$tp_fixture")"
if printf '%s\n' "$tp_units" | grep -qx 'flyedge.service' \
&& printf '%s\n' "$tp_units" | grep -qx 'pulse.service' \
&& ! printf '%s\n' "$tp_units" | grep -qx 'commented.service' \
&& target_pulls "$INFRA_DIR/units/fly.target" | grep -qx 'flysim.service'; then
pass "target_pulls reads continuation lines and skips comments (fixture + fly.target)"
else
fail "target_pulls missed a continuation line or read a comment: $(echo "$tp_units" | tr '\n' ' ')"
fi
rm -f "$tp_fixture"
if grep -E '^(ALWAYS_ON_UNITS|APP_UNITS)=' "$INFRA_DIR/07-enable.sh" "$INFRA_DIR/verify.sh" | grep -q 'flyedge'; then
fail "07-enable.sh or verify.sh lists flyedge.service as always-on"
else
pass "07-enable.sh and verify.sh leave flyedge.service alone"
fi
grep -qF 'echo "FLY_FEED_VIA=${FLY_FEED_VIA:-direct}"' "$INFRA_DIR/05-deploy.sh" \
&& pass "05-deploy.sh writes FLY_FEED_VIA with direct as the default" \
|| fail "05-deploy.sh must write FLY_FEED_VIA=\${FLY_FEED_VIA:-direct} into fly.env"
if grep -qF 'FLY_FEED_VIA_EFFECTIVE="$(feed_via_normalize "${FLY_FEED_VIA:-}")"' "$INFRA_DIR/05-deploy.sh" \
&& grep -qF 'echo "FLY_FEED_VIA=${FLY_FEED_VIA_EFFECTIVE}"' "$INFRA_DIR/05-deploy.sh"; then
pass "05-deploy.sh validates FLY_FEED_VIA and writes the normalized value"
else
fail "05-deploy.sh must run FLY_FEED_VIA through feed_via_normalize and write FLY_FEED_VIA_EFFECTIVE"
fi
# shellcheck source=../lib/common.sh
fv_out="$(bash -c '. "$1/lib/common.sh"
for v in "" direct DIRECT bus Bus BUS; do printf "%s=%s " "${v:-empty}" "$(feed_via_normalize "$v")"; done
for v in buss "bus " direct,bus; do feed_via_normalize "$v" >/dev/null && printf "ACCEPTED:%s " "$v"; done; true' _ "$INFRA_DIR" 2>&1)"
if [ "$fv_out" = "empty=direct direct=direct DIRECT=direct bus=bus Bus=bus BUS=bus " ]; then
pass "feed_via_normalize: direct|bus in any case, empty is direct, anything else refused"
else
fail "feed_via_normalize: got '$fv_out'"
fi
if grep -qE '^[[:space:]]*for u in flysim .*\bflyedge\b.*; do$' "$INFRA_DIR/05-deploy.sh"; then
pass "05-deploy.sh writes a cpuset drop-in for flyedge.service"
else
fail "05-deploy.sh cpuset loop must include flyedge (the page's CPUs, never flysim's)"
fi
if grep -qE '^Environment=FLY_FEED_VIA' "$INFRA_DIR/units/flysim.service"; then
fail "flysim.service pins FLY_FEED_VIA; it belongs to fly.env so a box can be switched by deploy"
else
@ -502,6 +556,9 @@ else
feed_url_case "direct" "FLY_FEED_VIA=direct" "" "http://sim"
feed_url_case "no FLY_FEED_VIA line (a fly.env before it)" "FLY_GAME=pokemon-red" "" "http://sim"
feed_url_case "bus" "FLY_FEED_VIA=bus" "" "http://edge"
feed_url_case "Bus (flysim lowercases)" "FLY_FEED_VIA=Bus" "" "http://edge"
feed_url_case "BUS" "FLY_FEED_VIA=BUS" "" "http://edge"
feed_url_case "quoted bus" 'FLY_FEED_VIA="bus"' "" "http://edge"
feed_url_case "explicit override wins" "FLY_FEED_VIA=bus" "http://other" "http://other"
rm -rf "$fe_fixture"
fi