diff --git a/infra/05-deploy.sh b/infra/05-deploy.sh index 5d9b922..bb38302 100644 --- a/infra/05-deploy.sh +++ b/infra/05-deploy.sh @@ -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" ;; diff --git a/infra/bin/fly-watchdog b/infra/bin/fly-watchdog index 8499058..c21a565 100755 --- a/infra/bin/fly-watchdog +++ b/infra/bin/fly-watchdog @@ -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" diff --git a/infra/lib/common.sh b/infra/lib/common.sh index 675fbb7..770d8e7 100755 --- a/infra/lib/common.sh +++ b/infra/lib/common.sh @@ -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 diff --git a/infra/tests/lint.sh b/infra/tests/lint.sh index 965d5f0..f41e9bd 100755 --- a/infra/tests/lint.sh +++ b/infra/tests/lint.sh @@ -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