Files
NeoTachyon/tests/dns_apply.sh
T
Dushnilin 4e419d5aea fix: OpenWrt 25.12 compatibility fixes, startup speedup, and Telegram polling fix
- wget: remove -t flag incompatible with BusyBox 25.12+, add curl-first fallback
  in download_to_file() with wget as fallback

- startup/reload speedup (5 improvements):
  - cut sing-box verification timeout from 45 iterations to 15
  - remove redundant second stability wait for sing-box process
  - batch requirement check + runtime validation into single ucode fork
  - replace dnsmasq full restart with procd reload (config-conditional bounce)
  - move telegram and watchdog runtime start to background

- telegram: fix "API failure" spam - empty result[] from getUpdates is now
  treated as a successful idle poll, not an error that triggers backoff

- watchdog SmartDetect: extract domain from proxy and VPN URLs for DNS
  health check, use real domains (google.com/example.com) instead of
  provider-specific endpoints that trigger false alerts

- tests: new invariants for wget -t prohibition, telegram empty-result
  handling, DNS apply reload-vs-restart, and combined validation mode ownership
2026-08-07 11:46:52 +03:00

288 lines
7.3 KiB
Bash

#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
APPLY="$ROOT_DIR/tachyon/files/usr/lib/dns/apply.uc"
UCODE_LIB="$ROOT_DIR/tachyon/files/usr/lib"
WORK_DIR="$(mktemp -d)"
STATE="$WORK_DIR/uci.state"
LOG="$WORK_DIR/uci.log"
DNSMASQ_LOG="$WORK_DIR/dnsmasq.log"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
printf 'UCI state:\n' >&2
cat "$STATE" >&2 2>/dev/null || true
printf 'UCI log:\n' >&2
cat "$LOG" >&2 2>/dev/null || true
exit 1
}
mkdir -p "$WORK_DIR/bin"
cat >"$WORK_DIR/bin/uci" <<'UCI'
#!/usr/bin/env bash
set -eo pipefail
while [ "${1:-}" = "-q" ]; do
shift
done
cmd="${1:-}"
shift || true
state="${UCI_STATE:?}"
log="${UCI_LOG:?}"
get_value() {
awk -F= -v key="$1" '$1 == key { print substr($0, length($1) + 2); found = 1 } END { exit found ? 0 : 1 }' "$state"
}
key_exists() {
awk -F= -v key="$1" -v prefix="$1." '$1 == key || index($1, prefix) == 1 { found = 1 } END { exit found ? 0 : 1 }' "$state"
}
delete_key() {
local key="$1"
local tmp
tmp="$(mktemp)"
awk -F= -v key="$key" -v prefix="$key." '$1 != key && index($1, prefix) != 1' "$state" > "$tmp"
mv "$tmp" "$state"
}
set_key() {
local key="$1"
local value="$2"
delete_key "$key"
printf '%s=%s\n' "$key" "$value" >> "$state"
}
case "$cmd" in
get)
get_value "$1"
;;
show)
key_exists "$1"
;;
delete)
delete_key "$1"
;;
set)
item="$1"
set_key "${item%%=*}" "${item#*=}"
;;
add_list)
item="$1"
key="${item%%=*}"
value="${item#*=}"
current="$(get_value "$key" 2>/dev/null || true)"
if [ -n "$current" ]; then
set_key "$key" "$current $value"
else
set_key "$key" "$value"
fi
;;
del_list)
item="$1"
key="${item%%=*}"
value="${item#*=}"
current="$(get_value "$key" 2>/dev/null || true)"
[ -n "$current" ] || exit 1
new=""
removed=0
for entry in $current; do
if [ "$entry" = "$value" ]; then
removed=1
continue
fi
new="${new:+$new }$entry"
done
[ "$removed" -eq 1 ] || exit 1
if [ -n "$new" ]; then
set_key "$key" "$new"
else
delete_key "$key"
fi
;;
commit)
printf 'commit %s\n' "$1" >> "$log"
;;
*)
printf 'unsupported uci command: %s\n' "$cmd" >&2
exit 2
;;
esac
UCI
chmod 0755 "$WORK_DIR/bin/uci"
cat >"$WORK_DIR/dnsmasq-init" <<'DNSMASQ'
#!/usr/bin/env bash
set -eo pipefail
printf '%s\n' "$*" >> "${DNSMASQ_LOG:?}"
DNSMASQ
chmod 0755 "$WORK_DIR/dnsmasq-init"
export PATH="$WORK_DIR/bin:$PATH"
export UCI_STATE="$STATE"
export UCI_LOG="$LOG"
export DNSMASQ_LOG
export DNSMASQ_INIT="$WORK_DIR/dnsmasq-init"
export TACHYON_CONFIG_NAME="tachyon"
export SB_DNS_INBOUND_ADDRESS="127.0.0.42"
if grep -E 'uci -q|command -v uci' "$APPLY" >/dev/null; then
fail "dns/apply.uc must use ucode UCI access instead of shelling out to uci"
fi
run_restore() {
: > "$LOG"
ucode -L "$UCODE_LIB" "$APPLY" failsafe-restore
}
uci_get() {
"$WORK_DIR/bin/uci" -q get "$1"
}
assert_value() {
local path="$1"
local expected="$2"
local actual
actual="$(uci_get "$path" 2>/dev/null || true)"
[ "$actual" = "$expected" ] || fail "$path: expected '$expected', got '$actual'"
}
assert_absent() {
local path="$1"
if uci_get "$path" >/dev/null 2>&1; then
fail "$path: expected option to be absent"
fi
}
assert_log_contains() {
local expected="$1"
grep -Fxq "$expected" "$LOG" || fail "expected log entry '$expected'"
}
assert_log_empty() {
[ ! -s "$LOG" ] || fail "expected empty log"
}
# dnsmasq must be told to re-read its config. A reload is preferred (procd
# regenerates dnsmasq.conf and only bounces the instance when it changed), with a
# full restart as the fallback, so either action satisfies the requirement.
assert_dnsmasq_reloaded() {
grep -Fxq 'reload' "$DNSMASQ_LOG" || grep -Fxq 'restart' "$DNSMASQ_LOG" ||
fail "expected dnsmasq reload or restart"
}
cat >"$STATE" <<'EOF_STATE'
dhcp.@dnsmasq[0].server=1.1.1.1 8.8.8.8
dhcp.@dnsmasq[0].noresolv=0
dhcp.@dnsmasq[0].cachesize=150
tachyon.settings.shutdown_correctly=1
EOF_STATE
: > "$DNSMASQ_LOG"
: > "$LOG"
ucode -L "$UCODE_LIB" "$APPLY" configure force
assert_value 'dhcp.@dnsmasq[0].server' '127.0.0.42'
assert_value 'dhcp.@dnsmasq[0].tachyon_server' '1.1.1.1 8.8.8.8'
assert_value 'dhcp.@dnsmasq[0].noresolv' '1'
assert_value 'dhcp.@dnsmasq[0].tachyon_noresolv' '0'
assert_value 'dhcp.@dnsmasq[0].cachesize' '0'
assert_value 'dhcp.@dnsmasq[0].tachyon_cachesize' '150'
assert_log_contains 'commit dhcp'
assert_dnsmasq_reloaded
: > "$DNSMASQ_LOG"
: > "$LOG"
ucode -L "$UCODE_LIB" "$APPLY" restore force
assert_value 'dhcp.@dnsmasq[0].server' '1.1.1.1 8.8.8.8'
assert_value 'dhcp.@dnsmasq[0].noresolv' '0'
assert_value 'dhcp.@dnsmasq[0].cachesize' '150'
assert_absent 'dhcp.@dnsmasq[0].tachyon_server'
assert_absent 'dhcp.@dnsmasq[0].tachyon_noresolv'
assert_absent 'dhcp.@dnsmasq[0].tachyon_cachesize'
assert_log_contains 'commit dhcp'
assert_dnsmasq_reloaded
cat >"$STATE" <<'EOF_STATE'
dhcp.@dnsmasq[0].server=127.0.0.42
dhcp.@dnsmasq[0].notinterface=br-lan guest
dhcp.@dnsmasq[0].tachyon_server=1.1.1.1 8.8.8.8
dhcp.@dnsmasq[0].tachyon_notinterface=wan docker
dhcp.@dnsmasq[0].tachyon_noresolv=1
dhcp.@dnsmasq[0].tachyon_cachesize=0
dhcp.tachyon.interface=br-lan guest
tachyon.settings.dont_touch_dhcp=1
EOF_STATE
run_restore
assert_value 'dhcp.@dnsmasq[0].server' '1.1.1.1 8.8.8.8'
assert_value 'dhcp.@dnsmasq[0].notinterface' 'wan docker'
assert_value 'dhcp.@dnsmasq[0].noresolv' '1'
assert_value 'dhcp.@dnsmasq[0].cachesize' '0'
assert_absent 'dhcp.@dnsmasq[0].tachyon_server'
assert_absent 'dhcp.@dnsmasq[0].tachyon_notinterface'
assert_absent 'dhcp.@dnsmasq[0].tachyon_noresolv'
assert_absent 'dhcp.@dnsmasq[0].tachyon_cachesize'
assert_absent 'dhcp.tachyon.interface'
assert_log_contains 'commit dhcp'
cat >"$STATE" <<'EOF_STATE'
dhcp.@dnsmasq[0].server=9.9.9.9
tachyon.settings.dont_touch_dhcp=1
EOF_STATE
run_restore
assert_value 'dhcp.@dnsmasq[0].server' '9.9.9.9'
assert_log_empty
cat >"$STATE" <<'EOF_STATE'
dhcp.@dnsmasq[0].server=127.0.0.42
dhcp.@dnsmasq[0].tachyon_noresolv=1
dhcp.@dnsmasq[0].tachyon_cachesize=0
tachyon.settings.dont_touch_dhcp=0
EOF_STATE
run_restore
assert_absent 'dhcp.@dnsmasq[0].server'
assert_value 'dhcp.@dnsmasq[0].noresolv' '1'
assert_value 'dhcp.@dnsmasq[0].cachesize' '0'
assert_absent 'dhcp.@dnsmasq[0].tachyon_noresolv'
assert_absent 'dhcp.@dnsmasq[0].tachyon_cachesize'
assert_log_contains 'commit dhcp'
# An init script without a working reload (older or non-procd dnsmasq) must still
# get its config applied through the full-restart fallback.
cat >"$WORK_DIR/dnsmasq-init" <<'DNSMASQ'
#!/usr/bin/env bash
set -eo pipefail
printf '%s\n' "$*" >> "${DNSMASQ_LOG:?}"
[ "${1:-}" != "reload" ] || exit 1
DNSMASQ
chmod 0755 "$WORK_DIR/dnsmasq-init"
cat >"$STATE" <<'EOF_STATE'
dhcp.@dnsmasq[0].server=1.1.1.1 8.8.8.8
dhcp.@dnsmasq[0].noresolv=0
dhcp.@dnsmasq[0].cachesize=150
tachyon.settings.shutdown_correctly=1
EOF_STATE
: > "$DNSMASQ_LOG"
: > "$LOG"
ucode -L "$UCODE_LIB" "$APPLY" configure force
assert_value 'dhcp.@dnsmasq[0].server' '127.0.0.42'
grep -Fxq 'reload' "$DNSMASQ_LOG" || fail "fallback: expected a reload attempt first"
grep -Fxq 'restart' "$DNSMASQ_LOG" || fail "fallback: expected restart after failed reload"
printf 'DNS apply checks passed\n'