Files
NeoTachyon/tests/runtime_state_owner.sh

126 lines
7.8 KiB
Bash

#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TACHYON_BIN="$ROOT_DIR/tachyon/files/usr/bin/tachyon"
TACHYON_INIT="$ROOT_DIR/tachyon/files/etc/init.d/tachyon"
CLI_UC="$TACHYON_BIN"
RUNTIME_STATE="$ROOT_DIR/tachyon/files/usr/lib/runtime_state.sh"
UPDATES_RUNTIME="$ROOT_DIR/tachyon/files/usr/lib/updates_runtime.sh"
LIFECYCLE_UC="$ROOT_DIR/tachyon/files/usr/lib/service/lifecycle.uc"
UPDATES_UC="$ROOT_DIR/tachyon/files/usr/lib/components/updates.uc"
STATE_UC="$ROOT_DIR/tachyon/files/usr/lib/service/state.uc"
NFT_UC="$ROOT_DIR/tachyon/files/usr/lib/nft/apply.uc"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
[ ! -e "$RUNTIME_STATE" ] ||
fail "runtime_state.sh shell owner must be removed"
[ ! -e "$UPDATES_RUNTIME" ] ||
fail "updates_runtime.sh shell owner must be removed"
grep -Fq 'runtime_state.sh' "$TACHYON_BIN" &&
fail "tachyon must not source runtime_state.sh"
service_predicates='process_age_seconds|sing_box_service_pid|pid_is_sing_box|sing_box_service_is_running|sing_box_service_is_stable|tachyon_runtime_network_is_configured|tachyon_is_running|tachyon_is_stably_running|wait_for_tachyon_stable_start'
if grep -R -n -E "$service_predicates" "$TACHYON_BIN" "$ROOT_DIR/tachyon/files/usr/lib" --include='*.sh' >/dev/null 2>&1; then
fail "service running/stability predicates must not remain in shell"
fi
pending_state='mark_pending_reload|consume_pending_reload|run_pending_reload_if_requested|sync_time_if_needed'
if grep -R -n -E "$pending_state" "$TACHYON_BIN" "$TACHYON_INIT" "$ROOT_DIR/tachyon/files/usr/lib" --include='*.sh' >/dev/null 2>&1; then
fail "pending reload/time-sync ownership must not remain in shell"
fi
runtime_state_shell_symbols='runtime_state_ucode|acquire_runtime_dir_lock|release_runtime_dir_lock|capture_reload_state|write_reload_state_file|write_reload_state|clear_reload_state|populate_nft_runtime_sets|rebuild_nft_runtime|reload_sing_box_runtime|apply_pending_urltest_selector_switches|close_inherited_service_lock_fd|TACHYON_URLTEST_SELECTOR_SWITCHES'
if grep -R -n -E "$runtime_state_shell_symbols" "$TACHYON_BIN" "$TACHYON_INIT" "$ROOT_DIR/tachyon/files/usr/lib" --include='*.sh' >/dev/null 2>&1; then
fail "runtime_state.sh symbols must not remain in shell"
fi
if grep -Fq 'acquire_reload_lock' "$TACHYON_INIT" || grep -Fq 'release_reload_lock' "$TACHYON_INIT"; then
fail "init.d must not own reload lock decisions"
fi
grep -Fq '#!/usr/bin/ucode' "$TACHYON_BIN" ||
fail "tachyon entrypoint must be a direct ucode executable"
grep -Fq 'service/lifecycle.uc' "$CLI_UC" ||
fail "service/cli.uc must dispatch lifecycle orchestration through service/lifecycle.uc"
grep -Fq 'refresh-cron-from-uci' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc start/reload must call the ucode cron refresh operation"
grep -Fq 'remove-cron-jobs' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc stop must call the ucode cron removal operation"
grep -Fq '"list-update"' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc start/reload must run list updates through components/updates.uc"
grep -Fq 'subscription_update: [ "components/updates.uc", "subscription-update", 2 ]' "$CLI_UC" ||
fail "service/cli.uc must run subscription updates through components/updates.uc"
if grep -R -n -F 'updates_runtime.sh' "$TACHYON_BIN" "$ROOT_DIR/tachyon/files/usr/lib" --include='*.sh' >/dev/null 2>&1; then
fail "runtime shell must not reference updates_runtime.sh"
fi
grep -Fq 'mode == "refresh-cron-from-uci"' "$UPDATES_UC" ||
fail "components/updates.uc must own cron refresh"
grep -Fq 'mode == "remove-cron-jobs"' "$UPDATES_UC" ||
fail "components/updates.uc must own cron removal"
grep -Fq 'mode == "list-update"' "$UPDATES_UC" ||
fail "components/updates.uc must own list update"
grep -Fq 'mode == "subscription-update"' "$UPDATES_UC" ||
fail "components/updates.uc must own subscription update"
grep -Fq 'mode == "tachyon-running"' "$STATE_UC" ||
fail "service/state.uc must own Tachyon running predicate"
grep -Fq 'mode == "sing-box-service-stable"' "$STATE_UC" ||
fail "service/state.uc must expose stable sing-box predicate"
grep -Fq 'mode == "wait-tachyon-stable-start"' "$STATE_UC" ||
fail "service/state.uc must own stable-start waiting"
grep -Fq 'mode == "mark-pending-reload"' "$STATE_UC" ||
fail "service/state.uc must own pending reload writes"
grep -Fq 'mode == "sync-time-if-needed"' "$STATE_UC" ||
fail "service/state.uc must own time-sync decisions"
grep -Fq 'mode == "acquire-runtime-dir-lock"' "$STATE_UC" ||
fail "service/state.uc must own runtime lock acquisition"
grep -Fq 'mode == "write-captured-reload-state"' "$STATE_UC" ||
fail "service/state.uc must own captured reload state writes"
grep -Fq 'mode == "reload-sing-box-runtime"' "$STATE_UC" ||
fail "service/state.uc must own sing-box runtime reload"
grep -Fq '"wait-tachyon-stable-start"' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc must verify stable runtime after sing-box start/reload"
grep -Fq 'SING_BOX_START_STABLE_MIN_AGE' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc must use a dedicated sing-box start stability window"
sing_box_start_line="$(awk '/command_success_from_args\(\[ "\/etc\/init.d\/sing-box", "start" \]\)/ { print NR; exit }' "$LIFECYCLE_UC")"
early_stable_line="$(awk -v start="$sing_box_start_line" 'NR > start && /"wait-tachyon-stable-start"/ { print NR; exit }' "$LIFECYCLE_UC")"
start_stable_min_age_line="$(awk -v start="$sing_box_start_line" 'NR > start && /SING_BOX_START_STABLE_MIN_AGE/ { print NR; exit }' "$LIFECYCLE_UC")"
deferred_bootstrap_line="$(awk '/"run-deferred-bootstrap"/ { print NR; exit }' "$LIFECYCLE_UC")"
[ -n "$sing_box_start_line" ] ||
fail "service/lifecycle.uc must start sing-box through init.d"
[ -n "$early_stable_line" ] ||
fail "service/lifecycle.uc must verify stable runtime immediately after sing-box start"
[ "$early_stable_line" -lt "$deferred_bootstrap_line" ] ||
fail "service/lifecycle.uc must verify sing-box stability before deferred bootstrap work"
[ -n "$start_stable_min_age_line" ] && [ "$start_stable_min_age_line" -lt "$deferred_bootstrap_line" ] ||
fail "service/lifecycle.uc must use the dedicated sing-box start stability window before deferred bootstrap work"
sing_box_reload_line="$(awk '/"reload-sing-box-runtime"/ { print NR; exit }' "$LIFECYCLE_UC")"
reload_stable_min_age_line="$(awk -v start="$sing_box_reload_line" 'NR > start && /SING_BOX_START_STABLE_MIN_AGE/ { print NR; exit }' "$LIFECYCLE_UC")"
[ -n "$sing_box_reload_line" ] ||
fail "service/lifecycle.uc must reload sing-box through service/state.uc"
[ -n "$reload_stable_min_age_line" ] ||
fail "service/lifecycle.uc must use the dedicated sing-box start stability window after sing-box reload"
grep -Fq 'Reload verification failed after sing-box was reloaded; stopping Tachyon runtime' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc must fail reload when sing-box does not stay stable"
grep -Fq 'Reload runtime restart verification failed after Tachyon was started; rolling back DNS changes' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc must fail full runtime restart when sing-box does not stay stable"
if grep -n -E 'require\("uci"\)\.cursor|uci -q|uci", "-q"|uci_get_cli|uci_exists_cli|mwan3_has_enabled_interface_from_uci_show' "$STATE_UC" >/dev/null 2>&1; then
fail "service/state.uc must not own direct UCI CLI/cursor access"
fi
grep -Fq 'nft-rebuild-runtime-from-uci' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc reload must call nft rebuild through ucode"
if grep -Fq 'nft-create-full-runtime-from-uci' "$LIFECYCLE_UC"; then
fail "tachyon start/reload must rebuild nft runtime instead of appending to stale state"
fi
grep -Fq 'nft-populate-runtime-sets-from-uci' "$LIFECYCLE_UC" ||
fail "service/lifecycle.uc reload must call nft populate through ucode"
grep -Fq 'mode == "nft-rebuild-runtime-from-uci"' "$NFT_UC" ||
fail "nft/apply.uc must own nft runtime rebuild"
printf 'runtime state ownership checks passed\n'