fix(updates,migration): skip sing-box reload when config unchanged (#54) and guard test service restart
This commit is contained in:
@@ -3096,20 +3096,23 @@ function reload_singbox_after_list_update() {
|
|||||||
if (!singbox_runtime_success([ "init-config", "0", "1", "1" ]))
|
if (!singbox_runtime_success([ "init-config", "0", "1", "1" ]))
|
||||||
return false;
|
return false;
|
||||||
let sing_box_config_hash_after = file_md5(sing_box_config_path);
|
let sing_box_config_hash_after = file_md5(sing_box_config_path);
|
||||||
log_message("Rulesets updated on disk; reloading sing-box with new configuration", "info");
|
if (sing_box_config_hash_before != sing_box_config_hash_after) {
|
||||||
module_success([ DNS_FAILOVER_UC, "stop-runtime" ]);
|
log_message("Rulesets updated on disk; reloading sing-box with new configuration", "info");
|
||||||
module_success([ PRIORITY_UC, "stop-runtime" ]);
|
module_success([ DNS_FAILOVER_UC, "stop-runtime" ]);
|
||||||
let ok = service_state_success([
|
module_success([ PRIORITY_UC, "stop-runtime" ]);
|
||||||
"reload-sing-box-runtime",
|
let ok = service_state_success([
|
||||||
sing_box_pid,
|
"reload-sing-box-runtime",
|
||||||
sing_box_config_hash_before,
|
sing_box_pid,
|
||||||
sing_box_config_hash_after,
|
sing_box_config_hash_before,
|
||||||
"1"
|
sing_box_config_hash_after,
|
||||||
]);
|
"1"
|
||||||
module_success([ PRIORITY_UC, "start-runtime" ]);
|
]);
|
||||||
module_success([ DNS_FAILOVER_UC, "start-runtime" ]);
|
module_success([ PRIORITY_UC, "start-runtime" ]);
|
||||||
write_current_reload_state_clean();
|
module_success([ DNS_FAILOVER_UC, "start-runtime" ]);
|
||||||
return ok;
|
write_current_reload_state_clean();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function list_update() {
|
function list_update() {
|
||||||
@@ -3552,6 +3555,8 @@ else if (mode == "list-update-if-due")
|
|||||||
list_update_if_due();
|
list_update_if_due();
|
||||||
else if (mode == "stop-list-update")
|
else if (mode == "stop-list-update")
|
||||||
stop_list_update();
|
stop_list_update();
|
||||||
|
else if (mode == "reload-singbox-after-list-update")
|
||||||
|
exit(reload_singbox_after_list_update() ? 0 : 1);
|
||||||
else if (mode == "list-update-due-status")
|
else if (mode == "list-update-due-status")
|
||||||
uci_list_update_due_status(ARGV[1], ARGV[2]);
|
uci_list_update_due_status(ARGV[1], ARGV[2]);
|
||||||
else if (mode == "list-update-due-status-fixture")
|
else if (mode == "list-update-due-status-fixture")
|
||||||
|
|||||||
@@ -2183,9 +2183,10 @@ function import_settings_cli(source_path) {
|
|||||||
system("chmod 0600 " + shell_quote(target_config) + " 2>/dev/null");
|
system("chmod 0600 " + shell_quote(target_config) + " 2>/dev/null");
|
||||||
print(" ✓ Configuration successfully migrated and verified.\n\n");
|
print(" ✓ Configuration successfully migrated and verified.\n\n");
|
||||||
|
|
||||||
if (fs.stat("/etc/init.d/tachyon") != null) {
|
let is_system_config = (target_config == "/etc/config/" + CONFIG_NAME) && (getenv("TACHYON_CONFIG_FILE") == null || getenv("TACHYON_CONFIG_FILE") == ("/etc/config/" + CONFIG_NAME));
|
||||||
|
if (is_system_config && !bool_option(getenv("TACHYON_SKIP_RESTART"), false) && fs.stat("/etc/init.d/tachyon") != null) {
|
||||||
print("Restarting Tachyon service...\n");
|
print("Restarting Tachyon service...\n");
|
||||||
system("/usr/bin/tachyon restart >/dev/null 2>&1");
|
system("/usr/bin/tachyon restart </dev/null >/dev/null 2>&1");
|
||||||
print(" ✓ Tachyon restarted with imported configuration.\n\n");
|
print(" ✓ Tachyon restarted with imported configuration.\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,12 +58,16 @@ tachyon.youtube.proxy_config_type=url
|
|||||||
tachyon.youtube.proxy_string=vless://user@1.2.3.4:443?security=tls#node1
|
tachyon.youtube.proxy_string=vless://user@1.2.3.4:443?security=tls#node1
|
||||||
EOF_STATE
|
EOF_STATE
|
||||||
|
|
||||||
OUTPUT="$(TACHYON_LIB="$TACHYON_LIB" \
|
export TACHYON_SKIP_RESTART="1"
|
||||||
|
|
||||||
|
OUTPUT="$(env \
|
||||||
|
TACHYON_LIB="$TACHYON_LIB" \
|
||||||
TACHYON_CONFIG_FILE="$MOCK_ETC/config/tachyon" \
|
TACHYON_CONFIG_FILE="$MOCK_ETC/config/tachyon" \
|
||||||
TACHYON_CONFIG_DIR="$MOCK_ETC/config" \
|
TACHYON_CONFIG_DIR="$MOCK_ETC/config" \
|
||||||
TACHYON_UCI_STATE_FILE="$WORK_DIR/test_import.state" \
|
TACHYON_UCI_STATE_FILE="$WORK_DIR/test_import.state" \
|
||||||
TACHYON_UCI_LOG_FILE="$WORK_DIR/test_import.log" \
|
TACHYON_UCI_LOG_FILE="$WORK_DIR/test_import.log" \
|
||||||
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$WORK_DIR/internal-config-change" \
|
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$WORK_DIR/internal-config-change" \
|
||||||
|
TACHYON_SKIP_RESTART="1" \
|
||||||
ucode -L "$TACHYON_LIB" "$MIGRATION" import-settings "$MOCK_ETC/config/forkop")"
|
ucode -L "$TACHYON_LIB" "$MIGRATION" import-settings "$MOCK_ETC/config/forkop")"
|
||||||
|
|
||||||
echo "$OUTPUT" | grep -Fq 'SUCCESS: Settings successfully imported' ||
|
echo "$OUTPUT" | grep -Fq 'SUCCESS: Settings successfully imported' ||
|
||||||
@@ -73,24 +77,28 @@ echo "$OUTPUT" | grep -Fq 'Legacy Forkop / Podkop' ||
|
|||||||
fail "import_settings must recognize legacy format"
|
fail "import_settings must recognize legacy format"
|
||||||
|
|
||||||
# 3. Test auto-scan in mock directory
|
# 3. Test auto-scan in mock directory
|
||||||
SCAN_OUTPUT="$(TACHYON_LIB="$TACHYON_LIB" \
|
SCAN_OUTPUT="$(env \
|
||||||
|
TACHYON_LIB="$TACHYON_LIB" \
|
||||||
TACHYON_CONFIG_FILE="$MOCK_ETC/config/tachyon" \
|
TACHYON_CONFIG_FILE="$MOCK_ETC/config/tachyon" \
|
||||||
TACHYON_CONFIG_DIR="$MOCK_ETC/config" \
|
TACHYON_CONFIG_DIR="$MOCK_ETC/config" \
|
||||||
TACHYON_UCI_STATE_FILE="$WORK_DIR/test_import.state" \
|
TACHYON_UCI_STATE_FILE="$WORK_DIR/test_import.state" \
|
||||||
TACHYON_UCI_LOG_FILE="$WORK_DIR/test_import.log" \
|
TACHYON_UCI_LOG_FILE="$WORK_DIR/test_import.log" \
|
||||||
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$WORK_DIR/internal-config-change" \
|
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$WORK_DIR/internal-config-change" \
|
||||||
|
TACHYON_SKIP_RESTART="1" \
|
||||||
ucode -L "$TACHYON_LIB" "$MIGRATION" import-settings)"
|
ucode -L "$TACHYON_LIB" "$MIGRATION" import-settings)"
|
||||||
|
|
||||||
echo "$SCAN_OUTPUT" | grep -Fq 'SUCCESS: Settings successfully imported' ||
|
echo "$SCAN_OUTPUT" | grep -Fq 'SUCCESS: Settings successfully imported' ||
|
||||||
fail "import_settings auto-scan must import legacy file"
|
fail "import_settings auto-scan must import legacy file"
|
||||||
|
|
||||||
# 4. Test non-existent file path error handling
|
# 4. Test non-existent file path error handling
|
||||||
ERR_OUTPUT="$(TACHYON_LIB="$TACHYON_LIB" \
|
ERR_OUTPUT="$(env \
|
||||||
|
TACHYON_LIB="$TACHYON_LIB" \
|
||||||
TACHYON_CONFIG_FILE="$MOCK_ETC/config/tachyon" \
|
TACHYON_CONFIG_FILE="$MOCK_ETC/config/tachyon" \
|
||||||
TACHYON_CONFIG_DIR="$MOCK_ETC/config" \
|
TACHYON_CONFIG_DIR="$MOCK_ETC/config" \
|
||||||
TACHYON_UCI_STATE_FILE="$WORK_DIR/test_import.state" \
|
TACHYON_UCI_STATE_FILE="$WORK_DIR/test_import.state" \
|
||||||
TACHYON_UCI_LOG_FILE="$WORK_DIR/test_import.log" \
|
TACHYON_UCI_LOG_FILE="$WORK_DIR/test_import.log" \
|
||||||
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$WORK_DIR/internal-config-change" \
|
TACHYON_INTERNAL_CONFIG_TRIGGER_GUARD="$WORK_DIR/internal-config-change" \
|
||||||
|
TACHYON_SKIP_RESTART="1" \
|
||||||
ucode -L "$TACHYON_LIB" "$MIGRATION" import-settings "$WORK_DIR/nonexistent.conf" 2>&1 || true)"
|
ucode -L "$TACHYON_LIB" "$MIGRATION" import-settings "$WORK_DIR/nonexistent.conf" 2>&1 || true)"
|
||||||
echo "$ERR_OUTPUT" | grep -Fq 'does not exist' ||
|
echo "$ERR_OUTPUT" | grep -Fq 'does not exist' ||
|
||||||
fail "import_settings must handle missing file with error"
|
fail "import_settings must handle missing file with error"
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
UPDATES_UC="$ROOT_DIR/tachyon/files/usr/lib/components/updates.uc"
|
||||||
|
REAL_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||||
|
WORK_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
write_stub() {
|
||||||
|
local path="$1"
|
||||||
|
local body="$2"
|
||||||
|
mkdir -p "$(dirname "$path")"
|
||||||
|
printf '%s\n' "$body" >"$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
stub_header='#!/usr/bin/env ucode
|
||||||
|
let fs = require("fs");
|
||||||
|
|
||||||
|
function as_string(value) {
|
||||||
|
return value == null ? "" : "" + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function record(line) {
|
||||||
|
let path = getenv("FAKE_CALL_LOG") || "";
|
||||||
|
if (path != "") {
|
||||||
|
let f = fs.open(path, "a");
|
||||||
|
if (f) {
|
||||||
|
f.write(as_string(line) + "\n");
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
|
FAKE_LIB="$WORK_DIR/lib"
|
||||||
|
|
||||||
|
write_stub "$FAKE_LIB/service/state.uc" "$stub_header"'
|
||||||
|
let mode = as_string(ARGV[0]);
|
||||||
|
record("service/state:" + mode);
|
||||||
|
if (mode == "sing-box-service-runtime-pid") {
|
||||||
|
print("4567\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if (mode == "reload-sing-box-runtime" ||
|
||||||
|
mode == "write-current-reload-state-clean")
|
||||||
|
exit(0);
|
||||||
|
exit(64);
|
||||||
|
'
|
||||||
|
|
||||||
|
write_stub "$FAKE_LIB/singbox/runtime.uc" "$stub_header"'
|
||||||
|
let mode = as_string(ARGV[0]);
|
||||||
|
record("singbox/runtime:" + mode);
|
||||||
|
if (mode == "init-config") {
|
||||||
|
let mutate = getenv("MUTATE_CONFIG_ON_INIT") || "0";
|
||||||
|
let config_path = getenv("FAKE_SINGBOX_CONFIG_PATH") || "";
|
||||||
|
if (mutate == "1" && config_path != "") {
|
||||||
|
let f = fs.open(config_path, "w");
|
||||||
|
if (f) {
|
||||||
|
f.write("{\"version\": 2}\n");
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
exit(64);
|
||||||
|
'
|
||||||
|
|
||||||
|
write_stub "$FAKE_LIB/singbox/priority.uc" "$stub_header"'
|
||||||
|
let mode = as_string(ARGV[0]);
|
||||||
|
record("singbox/priority:" + mode);
|
||||||
|
if (mode == "stop-runtime" || mode == "start-runtime")
|
||||||
|
exit(0);
|
||||||
|
exit(64);
|
||||||
|
'
|
||||||
|
|
||||||
|
write_stub "$FAKE_LIB/singbox/dns_failover.uc" "$stub_header"'
|
||||||
|
let mode = as_string(ARGV[0]);
|
||||||
|
record("singbox/dns_failover:" + mode);
|
||||||
|
if (mode == "stop-runtime" || mode == "start-runtime")
|
||||||
|
exit(0);
|
||||||
|
exit(64);
|
||||||
|
'
|
||||||
|
|
||||||
|
CONFIG_PATH="$WORK_DIR/config.json"
|
||||||
|
echo '{"version": 1}' > "$CONFIG_PATH"
|
||||||
|
|
||||||
|
cat > "$WORK_DIR/uci.state" <<EOF
|
||||||
|
tachyon.settings=settings
|
||||||
|
tachyon.settings.config_path=$CONFIG_PATH
|
||||||
|
EOF
|
||||||
|
|
||||||
|
run_reload() {
|
||||||
|
local mutate="$1"
|
||||||
|
local log="$2"
|
||||||
|
|
||||||
|
: >"$log"
|
||||||
|
env \
|
||||||
|
TACHYON_LIB="$FAKE_LIB" \
|
||||||
|
TACHYON_RUNTIME_STATE_DIR="$WORK_DIR/run" \
|
||||||
|
TACHYON_RELOAD_STATE_FILE="$WORK_DIR/run/reload-state" \
|
||||||
|
TACHYON_RULE_CONDITION_CACHE_DIR="$WORK_DIR/run/rule-condition-cache" \
|
||||||
|
TACHYON_UCI_STATE_FILE="$WORK_DIR/uci.state" \
|
||||||
|
FAKE_CALL_LOG="$log" \
|
||||||
|
FAKE_SINGBOX_CONFIG_PATH="$CONFIG_PATH" \
|
||||||
|
MUTATE_CONFIG_ON_INIT="$mutate" \
|
||||||
|
ucode -L "$REAL_LIB" "$UPDATES_UC" reload-singbox-after-list-update
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test 1: Unchanged config must NOT trigger sing-box reload or stop failover/priority (Issue #54)
|
||||||
|
UNCHANGED_LOG="$WORK_DIR/unchanged.log"
|
||||||
|
echo '{"version": 1}' > "$CONFIG_PATH"
|
||||||
|
run_reload "0" "$UNCHANGED_LOG"
|
||||||
|
|
||||||
|
if grep -Eq 'reload-sing-box-runtime|priority:stop-runtime|dns_failover:stop-runtime' "$UNCHANGED_LOG"; then
|
||||||
|
fail "reload_singbox_after_list_update must NOT reload sing-box when config is unchanged"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 2: Changed config MUST trigger reload and stop/start priority & failover
|
||||||
|
CHANGED_LOG="$WORK_DIR/changed.log"
|
||||||
|
echo '{"version": 1}' > "$CONFIG_PATH"
|
||||||
|
run_reload "1" "$CHANGED_LOG"
|
||||||
|
|
||||||
|
grep -Fq 'service/state:reload-sing-box-runtime' "$CHANGED_LOG" ||
|
||||||
|
fail "reload_singbox_after_list_update must reload sing-box when config has changed"
|
||||||
|
|
||||||
|
grep -Fq 'singbox/priority:stop-runtime' "$CHANGED_LOG" ||
|
||||||
|
fail "reload_singbox_after_list_update must stop priority on reload"
|
||||||
|
|
||||||
|
grep -Fq 'singbox/dns_failover:stop-runtime' "$CHANGED_LOG" ||
|
||||||
|
fail "reload_singbox_after_list_update must stop dns_failover on reload"
|
||||||
|
|
||||||
|
grep -Fq 'service/state:write-current-reload-state-clean' "$CHANGED_LOG" ||
|
||||||
|
fail "reload_singbox_after_list_update must write clean reload state"
|
||||||
|
|
||||||
|
printf 'list_update_reload_guard tests passed\n'
|
||||||
Reference in New Issue
Block a user