feat(updates): 1.3.10 - Автоматическое обновление компонентов (сразу при выходе и по расписанию)
- Backend: реализован запуск автоматического обновления компонентов в фоновом режиме (trigger_component_auto_update, component_auto_update_apply) - Cron: поддержка окна обслуживания (schedule mode) с регистрацией задачи в crontab по заданному времени (HH:MM) - UCI: новые опции component_auto_update_enabled, component_auto_update_mode, component_auto_update_time, component_auto_update_targets - Web UI: элементы управления во вкладке настроек «Обновления» (чекбокс включения, переключатель режима, время и мультиселект разрешенных компонентов) - Тесты: добавлен интеграционный тест component_auto_update.sh, обновлены cron-тесты updates_due.sh, все 118 тестов успешно пройдены в Docker - Локализация: добавлены русские переводы для всех новых элементов интерфейса
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
UPDATES_UC="$TACHYON_LIB/components/updates.uc"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_eq() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local label="$3"
|
||||
|
||||
[ "$actual" = "$expected" ] || fail "$label: expected '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
fake_lib="$WORK_DIR/lib"
|
||||
cache_dir="$WORK_DIR/cache"
|
||||
job_dir="$WORK_DIR/jobs"
|
||||
timestamp_file="$WORK_DIR/component-update-check.timestamp"
|
||||
mkdir -p \
|
||||
"$fake_lib/core" \
|
||||
"$fake_lib/config" \
|
||||
"$fake_lib/components" \
|
||||
"$fake_lib/service" \
|
||||
"$fake_lib/providers/zapret" \
|
||||
"$fake_lib/providers/zapret2" \
|
||||
"$fake_lib/providers/byedpi" \
|
||||
"$job_dir" \
|
||||
"$cache_dir"
|
||||
|
||||
cat >"$fake_lib/core/uci.uc" <<'UCODE'
|
||||
function get_all(_config, section) {
|
||||
if (section == "settings") {
|
||||
return {
|
||||
component_update_check_enabled: getenv("TEST_COMPONENT_UPDATE_CHECK_ENABLED") || "1",
|
||||
component_update_check_interval: getenv("TEST_COMPONENT_UPDATE_CHECK_INTERVAL") || "1d",
|
||||
component_auto_update_enabled: getenv("TEST_COMPONENT_AUTO_UPDATE_ENABLED") || "0",
|
||||
component_auto_update_mode: getenv("TEST_COMPONENT_AUTO_UPDATE_MODE") || "immediate",
|
||||
component_auto_update_time: getenv("TEST_COMPONENT_AUTO_UPDATE_TIME") || "04:00",
|
||||
component_auto_update_targets: getenv("TEST_COMPONENT_AUTO_UPDATE_TARGETS") ? split(getenv("TEST_COMPONENT_AUTO_UPDATE_TARGETS"), " ") : [ "all" ]
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function section_objects(_config, _type) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
get_all,
|
||||
section_objects
|
||||
};
|
||||
UCODE
|
||||
|
||||
cat >"$fake_lib/config/connections.uc" <<'UCODE'
|
||||
return {};
|
||||
UCODE
|
||||
|
||||
cat >"$fake_lib/components/action.uc" <<'UCODE'
|
||||
#!/usr/bin/env ucode
|
||||
let component = ARGV[1] || "tachyon";
|
||||
let action = ARGV[2] || "check_update";
|
||||
if (action == "check_update") {
|
||||
let result = {
|
||||
success: true,
|
||||
component,
|
||||
action,
|
||||
message: "Update is available",
|
||||
current_version: "1.0.0",
|
||||
latest_version: "1.1.0",
|
||||
release_url: "https://example.com/release",
|
||||
changed: 0,
|
||||
status: "outdated"
|
||||
};
|
||||
print(sprintf("%J\n", result));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (action == "update") {
|
||||
let result = {
|
||||
success: true,
|
||||
component,
|
||||
action,
|
||||
message: "Updated successfully",
|
||||
current_version: "1.1.0",
|
||||
latest_version: "1.1.0",
|
||||
changed: 1,
|
||||
status: "latest"
|
||||
};
|
||||
print(sprintf("%J\n", result));
|
||||
exit(0);
|
||||
}
|
||||
UCODE
|
||||
|
||||
cat >"$fake_lib/providers/zapret/runtime.uc" <<'UCODE'
|
||||
#!/usr/bin/env ucode
|
||||
exit(1);
|
||||
UCODE
|
||||
cp "$fake_lib/providers/zapret/runtime.uc" "$fake_lib/providers/zapret2/runtime.uc"
|
||||
cp "$fake_lib/providers/zapret/runtime.uc" "$fake_lib/providers/byedpi/runtime.uc"
|
||||
|
||||
cat >"$fake_lib/service/state.uc" <<'UCODE'
|
||||
#!/usr/bin/env ucode
|
||||
exit(0);
|
||||
UCODE
|
||||
|
||||
updates_ucode() {
|
||||
TEST_COMPONENT_UPDATE_CHECK_ENABLED="${TEST_COMPONENT_UPDATE_CHECK_ENABLED:-1}" \
|
||||
TEST_COMPONENT_UPDATE_CHECK_INTERVAL="${TEST_COMPONENT_UPDATE_CHECK_INTERVAL:-1d}" \
|
||||
TEST_COMPONENT_AUTO_UPDATE_ENABLED="${TEST_COMPONENT_AUTO_UPDATE_ENABLED:-0}" \
|
||||
TEST_COMPONENT_AUTO_UPDATE_MODE="${TEST_COMPONENT_AUTO_UPDATE_MODE:-immediate}" \
|
||||
TEST_COMPONENT_AUTO_UPDATE_TIME="${TEST_COMPONENT_AUTO_UPDATE_TIME:-04:00}" \
|
||||
TEST_COMPONENT_AUTO_UPDATE_TARGETS="${TEST_COMPONENT_AUTO_UPDATE_TARGETS:-all}" \
|
||||
TACHYON_LIB="$fake_lib" \
|
||||
TACHYON_BIN="/usr/bin/tachyon" \
|
||||
TACHYON_SERVICE_INIT="/etc/init.d/tachyon" \
|
||||
TACHYON_COMPONENT_UPDATE_CHECK_CACHE_DIR="$cache_dir" \
|
||||
TACHYON_COMPONENT_UPDATE_CHECK_STATE_FILE="$timestamp_file" \
|
||||
UPDATES_JOB_DIR="$job_dir" \
|
||||
ucode -L "$fake_lib" -L "$TACHYON_LIB" "$UPDATES_UC" "$@"
|
||||
}
|
||||
|
||||
# 1. Test immediate auto-update trigger on check if outdated
|
||||
TEST_COMPONENT_AUTO_UPDATE_ENABLED=1 \
|
||||
TEST_COMPONENT_AUTO_UPDATE_MODE=immediate \
|
||||
updates_ucode component-updates-if-due
|
||||
|
||||
# Verify update check cache was populated
|
||||
[ -f "$cache_dir/tachyon.json" ] || fail "component update check cache missing"
|
||||
|
||||
# Verify that auto-update spawned a background job in job_dir
|
||||
sleep 1
|
||||
job_files=( "$job_dir"/*.json )
|
||||
[ -f "${job_files[0]}" ] || fail "auto-update should create background job state"
|
||||
|
||||
# 2. Test scheduled apply
|
||||
rm -rf "$job_dir"/*
|
||||
TEST_COMPONENT_AUTO_UPDATE_ENABLED=1 \
|
||||
TEST_COMPONENT_AUTO_UPDATE_MODE=schedule \
|
||||
TEST_COMPONENT_AUTO_UPDATE_TIME="04:00" \
|
||||
updates_ucode component-auto-update-apply
|
||||
|
||||
sleep 1
|
||||
job_files=( "$job_dir"/*.json )
|
||||
[ -f "${job_files[0]}" ] || fail "scheduled auto-update should create background job state"
|
||||
|
||||
printf 'component auto update test passed\n'
|
||||
+18
-5
@@ -50,14 +50,16 @@ fi
|
||||
|
||||
echo "=== Running ${#test_files[@]} Tests (-P $PARALLEL) ==="
|
||||
|
||||
results_dir="$(mktemp -d)"
|
||||
results_dir="$(mktemp -d /tmp/tachyon-run-all-XXXXXX 2>/dev/null || mktemp -d)"
|
||||
cleanup() { rm -rf "$results_dir"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
run_one() {
|
||||
f="$1"
|
||||
log="$2"
|
||||
local f="$1"
|
||||
local results_dir="$2"
|
||||
local name
|
||||
name="$(basename "$f")"
|
||||
local log="$results_dir/${name%.sh}.log"
|
||||
echo "--- START $name ---"
|
||||
if bash "$f" >"$log" 2>&1; then
|
||||
echo "--- PASS $name ---"
|
||||
@@ -65,9 +67,20 @@ run_one() {
|
||||
echo "--- FAIL $name ---"
|
||||
fi
|
||||
}
|
||||
export -f run_one
|
||||
|
||||
printf '%s\0' "${test_files[@]}" | xargs -0 -n1 -I'{}' bash -c 'run_one "$1" "$2/$(basename "${1%.sh}").log"' _ '{}' "$results_dir" | tee "$results_dir/progress.log"
|
||||
progress_log="$results_dir/progress.log"
|
||||
touch "$progress_log"
|
||||
|
||||
running=0
|
||||
for f in "${test_files[@]}"; do
|
||||
( run_one "$f" "$results_dir" | tee -a "$progress_log" ) &
|
||||
running=$((running + 1))
|
||||
if [ "$running" -ge "$PARALLEL" ]; then
|
||||
wait -n 2>/dev/null || wait
|
||||
running=$((running - 1))
|
||||
fi
|
||||
done
|
||||
wait
|
||||
|
||||
pass_count=0
|
||||
fail_count=0
|
||||
|
||||
@@ -199,6 +199,24 @@ assert_eq "list-disabled" \
|
||||
"$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan-list-disabled.json" /usr/bin/tachyon '# list' '# subscription' '# component')" \
|
||||
"disabled list cron plan"
|
||||
|
||||
cat >"$WORK_DIR/cron-plan-auto-update.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
"list_update_enabled": "0",
|
||||
"component_update_check_enabled": "1",
|
||||
"component_update_check_interval": "1d",
|
||||
"component_auto_update_enabled": "1",
|
||||
"component_auto_update_mode": "schedule",
|
||||
"component_auto_update_time": "03:30"
|
||||
},
|
||||
"section": []
|
||||
}
|
||||
JSON
|
||||
|
||||
assert_eq $'component\t0 0 * * * /usr/bin/tachyon component_updates_if_due # component\ncomponent-auto-update\t30 3 * * * /usr/bin/tachyon component_auto_update_apply # component' \
|
||||
"$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan-auto-update.json" /usr/bin/tachyon '# list' '# subscription' '# component')" \
|
||||
"component auto-update cron plan"
|
||||
|
||||
cat >"$WORK_DIR/cron-plan-invalid-list.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
|
||||
@@ -40,8 +40,8 @@ grep -Fq 'function strip_unmarked_tachyon_cron_lines(' "$UPD" ||
|
||||
grep -Fq 'strip_unmarked_tachyon_cron_lines(' "$UPD" || true
|
||||
[ "$(grep -c 'strip_unmarked_tachyon_cron_lines(' "$UPD")" -ge 3 ] ||
|
||||
fail "unmarked cron line stripping must be applied to remove and refresh paths"
|
||||
grep -Fq '(list_update|subscription_update|component_updates_if_due)' "$UPD" ||
|
||||
fail "legacy cron pattern must cover all three tachyon jobs"
|
||||
grep -Fq '(list_update|subscription_update|component_updates_if_due|component_auto_update_apply)' "$UPD" ||
|
||||
fail "legacy cron pattern must cover all tachyon jobs"
|
||||
|
||||
# 4. Tunnel-style sections count as outbound configuration: a lone AWG/WARP
|
||||
# section must not trigger "No proxy outbound sections found".
|
||||
|
||||
Reference in New Issue
Block a user