282 lines
12 KiB
Bash
Executable File
282 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
|
INITD_UC="$TACHYON_LIB/service/initd.uc"
|
|
STATE_UC="$TACHYON_LIB/service/state.uc"
|
|
INITD="$ROOT_DIR/tachyon/files/etc/init.d/tachyon"
|
|
WORK_DIR="$(mktemp -d)"
|
|
|
|
cleanup() {
|
|
rm -rf "$WORK_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
initd_ucode() {
|
|
ucode -L "$TACHYON_LIB" "$INITD_UC" "$@"
|
|
}
|
|
|
|
config_file="$WORK_DIR/tachyon"
|
|
guard_file="$WORK_DIR/internal-config-change"
|
|
sync_file="$WORK_DIR/service-triggers.sync"
|
|
|
|
printf '%s\n' "config-version=1" >"$config_file"
|
|
config_hash="$(md5sum "$config_file" | awk '{print $1}')"
|
|
|
|
printf '%s\n%s\n' 1000 "$config_hash" >"$guard_file"
|
|
initd_ucode initd-guard-matches-current-config "$guard_file" "$config_file" 1010 >/dev/null ||
|
|
fail "valid internal config guard should match current config"
|
|
if initd_ucode initd-guard-matches-current-config "$guard_file" "$config_file" 1031 >/dev/null 2>&1; then
|
|
fail "stale internal config guard should not match"
|
|
fi
|
|
|
|
printf '%s\n%s\n' 1000 "$config_hash" >"$guard_file"
|
|
initd_ucode initd-should-skip-internal-config-reload on_config_change "$guard_file" "$config_file" on_config_change 1010 >/dev/null ||
|
|
fail "matching internal config guard should skip config-change reload"
|
|
[ ! -e "$guard_file" ] ||
|
|
fail "matching internal config guard should be consumed"
|
|
|
|
printf '%s\n%s\n' 1000 "$config_hash" >"$guard_file"
|
|
if initd_ucode initd-should-skip-internal-config-reload manual "$guard_file" "$config_file" on_config_change 1010 >/dev/null 2>&1; then
|
|
fail "manual reload should not be skipped by internal config guard"
|
|
fi
|
|
[ -e "$guard_file" ] ||
|
|
fail "non-config-change reason should not consume internal config guard"
|
|
|
|
printf '%s\n%s\n' 1000 "bad-hash" >"$guard_file"
|
|
if initd_ucode initd-should-skip-internal-config-reload on_config_change "$guard_file" "$config_file" on_config_change 1010 >/dev/null 2>&1; then
|
|
fail "mismatched internal config guard should not skip reload"
|
|
fi
|
|
[ ! -e "$guard_file" ] ||
|
|
fail "mismatched internal config guard should be consumed"
|
|
|
|
printf '%s\n' 1 >"$sync_file"
|
|
initd_ucode initd-service-trigger-sync-requested "$sync_file" >/dev/null ||
|
|
fail "service trigger sync marker should request sync"
|
|
[ ! -e "$sync_file" ] ||
|
|
fail "service trigger sync marker should be consumed"
|
|
|
|
printf '%s\n' 0 >"$sync_file"
|
|
if initd_ucode initd-service-trigger-sync-requested "$sync_file" >/dev/null 2>&1; then
|
|
fail "service trigger sync marker with value 0 should not request sync"
|
|
fi
|
|
[ ! -e "$sync_file" ] ||
|
|
fail "service trigger sync marker value 0 should be consumed"
|
|
|
|
initd_ucode initd-should-ignore-config-change-reload on_config_change on_config_change 0 0 >/dev/null ||
|
|
fail "disabled stopped service should ignore config-change reload"
|
|
if initd_ucode initd-should-ignore-config-change-reload manual on_config_change 0 0 >/dev/null 2>&1; then
|
|
fail "manual reload should not be ignored"
|
|
fi
|
|
if initd_ucode initd-should-ignore-config-change-reload on_config_change on_config_change 1 0 >/dev/null 2>&1; then
|
|
fail "running service should not ignore config-change reload"
|
|
fi
|
|
initd_ucode initd-should-ignore-config-change-reload on_config_change on_config_change 0 1 >/dev/null ||
|
|
fail "stopped service should ignore config-change reload even when autostart is enabled"
|
|
initd_ucode initd-should-queue-config-change-reload on_config_change on_config_change 0 start >/dev/null ||
|
|
fail "config-change reload during service start should be queued"
|
|
if initd_ucode initd-should-queue-config-change-reload on_config_change on_config_change 0 "" >/dev/null 2>&1; then
|
|
fail "stopped service without active service action should not queue config-change reload"
|
|
fi
|
|
if initd_ucode initd-should-queue-config-change-reload on_config_change on_config_change 1 reload >/dev/null 2>&1; then
|
|
fail "running service should not use stopped-service queue path"
|
|
fi
|
|
|
|
pending_file="$WORK_DIR/reload.pending"
|
|
if TACHYON_PENDING_RELOAD_FILE="$pending_file" \
|
|
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$guard_file" \
|
|
TACHYON_CONFIG_FILE="$config_file" \
|
|
initd_ucode reload-begin-fixture on_config_change 123 0 1 start >/dev/null 2>&1; then
|
|
fail "queued config-change reload should not run immediately while service is starting"
|
|
fi
|
|
[ -f "$pending_file" ] ||
|
|
fail "config-change reload during service start should create pending reload"
|
|
grep -Fq 'reason=on_config_change' "$pending_file" ||
|
|
fail "pending reload should preserve config-change reason"
|
|
|
|
rm -f "$pending_file"
|
|
if TACHYON_PENDING_RELOAD_FILE="$pending_file" \
|
|
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$guard_file" \
|
|
TACHYON_CONFIG_FILE="$config_file" \
|
|
initd_ucode reload-begin-fixture pending 123 1 1 start >/dev/null 2>&1; then
|
|
fail "pending reload should not run while another service action is active"
|
|
fi
|
|
[ -f "$pending_file" ] ||
|
|
fail "pending reload should stay pending while another service action is active"
|
|
grep -Fq 'reason=pending' "$pending_file" ||
|
|
fail "pending reload should preserve pending reason while delayed by active action"
|
|
|
|
start_retry_file="$WORK_DIR/start.retry"
|
|
if initd_ucode start-retry-pending "$start_retry_file" >/dev/null 2>&1; then
|
|
fail "missing start retry marker should not be pending"
|
|
fi
|
|
initd_ucode mark-start-retry "$start_retry_file" start_failed >/dev/null ||
|
|
fail "failed start should create WAN retry marker"
|
|
initd_ucode start-retry-pending "$start_retry_file" >/dev/null ||
|
|
fail "created start retry marker should be pending"
|
|
grep -Fq 'reason=start_failed' "$start_retry_file" ||
|
|
fail "start retry marker should preserve failure reason"
|
|
initd_ucode clear-start-retry "$start_retry_file"
|
|
if initd_ucode start-retry-pending "$start_retry_file" >/dev/null 2>&1; then
|
|
fail "cleared start retry marker should not be pending"
|
|
fi
|
|
|
|
retry_pid_file="$WORK_DIR/start-retry.pid"
|
|
retry_call_file="$WORK_DIR/start-retry.called"
|
|
retry_service="$WORK_DIR/tachyon-init"
|
|
cat >"$retry_service" <<EOF
|
|
#!/bin/sh
|
|
printf '%s\n' "\$1" >>"$retry_call_file"
|
|
EOF
|
|
chmod +x "$retry_service"
|
|
TACHYON_SERVICE_INIT="$retry_service" \
|
|
TACHYON_START_RETRY_PID_FILE="$retry_pid_file" \
|
|
TACHYON_START_RETRY_DELAY_SECONDS=1 \
|
|
initd_ucode schedule-start-retry >/dev/null ||
|
|
fail "failed start should schedule an automatic retry"
|
|
[ -s "$retry_pid_file" ] ||
|
|
fail "scheduled automatic retry should record its worker pid"
|
|
for _ in 1 2 3 4 5; do
|
|
[ -s "$retry_call_file" ] && break
|
|
sleep 1
|
|
done
|
|
grep -Fxq 'retry_start_on_wan_up' "$retry_call_file" ||
|
|
fail "scheduled automatic retry should call the marker-gated retry action"
|
|
[ ! -e "$retry_pid_file" ] ||
|
|
fail "automatic retry worker should clear its pid before retrying"
|
|
|
|
[ "$(initd_ucode retry-start-on-wan-up-action 1 1 1)" = "skip_running" ] ||
|
|
fail "WAN retry should skip when runtime is already running"
|
|
[ "$(initd_ucode retry-start-on-wan-up-action 0 0 1)" = "skip_disabled" ] ||
|
|
fail "WAN retry should skip when autostart is disabled"
|
|
[ "$(initd_ucode retry-start-on-wan-up-action 0 1 0)" = "skip_no_retry" ] ||
|
|
fail "WAN retry should skip stopped service without failed-start marker"
|
|
[ "$(initd_ucode retry-start-on-wan-up-action 0 1 1)" = "restart" ] ||
|
|
fail "WAN retry should restart only after a recorded failed start"
|
|
|
|
initd_ucode initd-should-restore-dnsmasq-on-start-fixture "" 0 >/dev/null ||
|
|
fail "unclean shutdown should request dnsmasq restore on start"
|
|
if initd_ucode initd-should-restore-dnsmasq-on-start-fixture triggered 0 >/dev/null 2>&1; then
|
|
fail "triggered start should not restore dnsmasq"
|
|
fi
|
|
if initd_ucode initd-should-restore-dnsmasq-on-start-fixture "" 1 >/dev/null 2>&1; then
|
|
fail "clean shutdown should not restore dnsmasq"
|
|
fi
|
|
|
|
start_plan="$(initd_ucode start-plan-fixture "" 0 1 "wan vpn0" 123 1)"
|
|
case "$start_plan" in
|
|
*"INITD_BIN_OK='1'"* ) ;;
|
|
*) fail "start-plan must expose executable binary state" ;;
|
|
esac
|
|
case "$start_plan" in
|
|
*"INITD_BADWAN_NETDEV='wan vpn0'"* ) ;;
|
|
*) fail "start-plan must derive Bad WAN netdev from UCI settings" ;;
|
|
esac
|
|
|
|
start_plan_triggered="$(initd_ucode start-plan-fixture triggered 0 1 "wan vpn0" 123 1)"
|
|
case "$start_plan_triggered" in
|
|
*"INITD_BADWAN_NETDEV='wan vpn0'"* ) ;;
|
|
*) fail "triggered start should keep Bad WAN netdev plan" ;;
|
|
esac
|
|
|
|
cat >"$WORK_DIR/settings.json" <<'JSON'
|
|
{
|
|
"settings": {
|
|
"enable_badwan_interface_monitoring": "1",
|
|
"badwan_monitored_interfaces": [ "wan", "vpn0", "wwan" ],
|
|
"badwan_reload_delay": "3500"
|
|
}
|
|
}
|
|
JSON
|
|
trigger_plan="$(initd_ucode trigger-plan-fixture "$WORK_DIR/settings.json")"
|
|
case "$trigger_plan" in
|
|
*"delay 3500"* ) ;;
|
|
*) fail "trigger-plan must emit Bad WAN reload delay" ;;
|
|
esac
|
|
case "$trigger_plan" in
|
|
*"interface interface.*.up vpn0"* ) ;;
|
|
*) fail "trigger-plan must include non-wan Bad WAN interface triggers" ;;
|
|
esac
|
|
case "$trigger_plan" in
|
|
*"interface interface.*.up wan "*"retry_start_on_wan_up"* ) ;;
|
|
*) fail "trigger-plan must keep WAN cold-start retry trigger" ;;
|
|
esac
|
|
|
|
for legacy in \
|
|
service_trigger_sync_requested \
|
|
current_config_hash \
|
|
internal_config_guard_matches_current_config \
|
|
should_skip_internal_config_reload \
|
|
should_restore_dnsmasq_on_start; do
|
|
if grep -Fq "$legacy" "$INITD"; then
|
|
fail "init.d must not keep shell decision function $legacy"
|
|
fi
|
|
done
|
|
|
|
grep -Fq 'service/initd.uc' "$INITD" ||
|
|
fail "init.d must delegate init.d decisions to service/initd.uc"
|
|
grep -Fq 'start-service' "$INITD" ||
|
|
fail "init.d must start through a complete ucode start entrypoint"
|
|
grep -Fq 'mode == "start-service"' "$INITD_UC" ||
|
|
fail "service/initd.uc must expose the complete start entrypoint"
|
|
grep -Fq 'stop-service' "$INITD" ||
|
|
fail "init.d must stop through a complete ucode stop entrypoint"
|
|
grep -Fq 'mode == "stop-service"' "$INITD_UC" ||
|
|
fail "service/initd.uc must expose the complete stop entrypoint"
|
|
grep -Fq 'reload-service' "$INITD" ||
|
|
fail "init.d must reload through a complete ucode reload entrypoint"
|
|
grep -Fq 'mode == "reload-service"' "$INITD_UC" ||
|
|
fail "service/initd.uc must expose the complete reload entrypoint"
|
|
grep -Fq 'status-service' "$INITD" ||
|
|
fail "init.d status must delegate to service/initd.uc"
|
|
grep -Fq 'mode == "status-service"' "$INITD_UC" ||
|
|
fail "service/initd.uc must expose the complete status entrypoint"
|
|
grep -Fq 'retry-start-on-wan-up' "$INITD" ||
|
|
fail "init.d WAN retry must delegate to service/initd.uc"
|
|
grep -Fq 'mode == "retry-start-on-wan-up"' "$INITD_UC" ||
|
|
fail "service/initd.uc must expose the complete WAN retry entrypoint"
|
|
grep -Fq 'mode == "retry-start-on-wan-up-action"' "$INITD_UC" ||
|
|
fail "service/initd.uc must expose the WAN retry decision fixture"
|
|
grep -Fq 'schedule_start_retry(START_RETRY_PID_FILE, START_RETRY_DELAY_SECONDS)' "$INITD_UC" ||
|
|
fail "failed service start must schedule a retry even if WAN is already up"
|
|
grep -Fq 'start_retry_pending(START_RETRY_FILE)' "$INITD_UC" ||
|
|
fail "WAN retry must be gated by a failed-start marker"
|
|
service_enabled_line="$(awk '/function service_is_enabled\(\)/ { print NR; exit }' "$INITD_UC")"
|
|
retry_start_line="$(awk '/function retry_start_on_wan_up\(/ { print NR; exit }' "$INITD_UC")"
|
|
[ -n "$service_enabled_line" ] && [ -n "$retry_start_line" ] && [ "$service_enabled_line" -lt "$retry_start_line" ] ||
|
|
fail "service_is_enabled must be declared before retry_start_on_wan_up for ucode runtime calls"
|
|
grep -Fq 'trigger-plan' "$INITD" ||
|
|
fail "init.d must get procd trigger plan from service/initd.uc"
|
|
if grep -Fq 'eval ' "$INITD"; then
|
|
fail "init.d must not eval ucode-generated shell plans"
|
|
fi
|
|
if grep -Fq 'runtime_is_running' "$INITD"; then
|
|
fail "init.d must not keep shell runtime status decisions"
|
|
fi
|
|
if grep -E -n 'TACHYON_(CONFIG_FILE|RELOAD_LOCK_DIR|RUNTIME_STATE_DIR|PENDING_RELOAD_FILE|SERVICE_TRIGGER_SYNC_FILE|INTERNAL_CONFIG_TRIGGER_GUARD|CONFIG_CHANGE_REASON|BIN|SERVICE_INIT)=' "$INITD" >/dev/null 2>&1; then
|
|
fail "init.d must not pass internal orchestration paths through shell env"
|
|
fi
|
|
if grep -n -E 'require\("uci"\)\.cursor|uci -q|uci", "-q"' "$INITD_UC" >/dev/null 2>&1; then
|
|
fail "service/initd.uc must use core.uci instead of direct UCI cursor/CLI access"
|
|
fi
|
|
if grep -E -n '(^|[[:space:]])config_(load|get|get_bool|set)[[:space:]]' "$INITD" >/dev/null 2>&1; then
|
|
fail "init.d must not read UCI directly"
|
|
fi
|
|
if grep -Fq 'TACHYON_STATE_UC' "$INITD" || grep -Fq 'TACHYON_UI_UC' "$INITD" || grep -Fq 'TACHYON_STATUS_UC' "$INITD"; then
|
|
fail "init.d must not orchestrate state/UI/status modules directly"
|
|
fi
|
|
if grep -Fq 'procd_set_param command "$TACHYON_BIN" start' "$INITD"; then
|
|
fail "init.d must not hand the one-shot start operation to procd as a daemon"
|
|
fi
|
|
if grep -Fq 'initd-should-' "$STATE_UC"; then
|
|
fail "service/state.uc must not keep init.d decision owner modes"
|
|
fi
|
|
|
|
printf 'init.d state checks passed\n'
|