fix(package): не удалять состояние выполняющегося задания в postinst
Самообновление Tachyon — это компонентное задание, которое устанавливает пакет, чей postinst и вызывает remove_component_update_cache(). Очистка стирала каталог component-actions целиком, включая файл состояния того самого задания, которое ещё выполнялось. LuCI опрашивал задание, не находил его, не получал ни message, ни stderr и показывал фолбэк "Failed to execute" поверх успешно прошедшего обновления. Теперь очистка пропускает задания, помеченные running. Устаревшие результаты и кэш проверок обновлений убираются как раньше. Зависшее задание не залипнет: детект по PID и started_at помечает такие stale. Пути каталогов состояния стали переопределяемыми через окружение, чтобы тест мог прогнать luci-postinst, не трогая /var/run.
This commit is contained in:
@@ -22,6 +22,11 @@ const SING_BOX_BIN = env("TACHYON_SING_BOX_BIN", "/usr/bin/sing-box");
|
||||
const SING_BOX_CRONET = env("TACHYON_SING_BOX_CRONET", "/usr/lib/libcronet.so");
|
||||
const SING_BOX_MANAGED_MARKER = env("SB_MANAGED_SERVICE_MARKER", "Tachyon managed sing-box service for binary variants");
|
||||
const PACKAGE_UPGRADE_STATE = env("TACHYON_PACKAGE_UPGRADE_STATE", "/tmp/tachyon-package-was-running");
|
||||
const RUNTIME_STATE_DIR = env("TACHYON_RUNTIME_STATE_DIR", "/var/run/tachyon");
|
||||
const COMPONENT_ACTION_DIR = env("TACHYON_UI_COMPONENT_ACTION_DIR", RUNTIME_STATE_DIR + "/component-actions");
|
||||
const COMPONENT_UPDATE_CHECK_DIR = env("TACHYON_COMPONENT_UPDATE_CHECK_CACHE_DIR", RUNTIME_STATE_DIR + "/component-update-checks");
|
||||
const COMPONENT_UPDATE_CHECK_TIMESTAMP = env("TACHYON_COMPONENT_UPDATE_CHECK_STATE_FILE", RUNTIME_STATE_DIR + "/component-update-check.timestamp");
|
||||
const COMPONENT_ACTION_LOCK = env("TACHYON_COMPONENT_ACTION_LOCK", RUNTIME_STATE_DIR + "/component-action.lock");
|
||||
const PACKAGE_TEST_MODE = env("TACHYON_PACKAGE_TEST_MODE", "") != "";
|
||||
// apk aborts and rolls the whole transaction back if a hook outlives its
|
||||
// patience, and the installer kills it even sooner. Anything that touches
|
||||
@@ -270,13 +275,27 @@ function remove_luci_index_cache() {
|
||||
}
|
||||
}
|
||||
|
||||
// A Tachyon self-update is itself a component job: the job installs the package
|
||||
// whose postinst this is. Wiping the whole directory therefore deleted the state
|
||||
// file of the job that was still running, so the UI polled a job that no longer
|
||||
// existed, got no message and no stderr, and rendered its "Failed to execute"
|
||||
// fallback over an update that had in fact succeeded. Only finished results are
|
||||
// stale; a job still marked running must survive to write its own result.
|
||||
function component_job_is_running(path) {
|
||||
let data = fs.readfile(path);
|
||||
if (data == null)
|
||||
return false;
|
||||
return match(as_string(data), /"running"[ \t]*:[ \t]*(true|1)/) != null;
|
||||
}
|
||||
|
||||
function remove_component_update_cache() {
|
||||
for (let path in fs.glob("/var/run/tachyon/component-update-checks/*.json"))
|
||||
unlink_if_exists(path);
|
||||
unlink_if_exists("/var/run/tachyon/component-update-check.timestamp");
|
||||
command_success_from_args([ "rm", "-rf", "/var/run/tachyon/component-action.lock" ]);
|
||||
for (let path in fs.glob("/var/run/tachyon/component-actions/*"))
|
||||
for (let path in fs.glob(COMPONENT_UPDATE_CHECK_DIR + "/*.json"))
|
||||
unlink_if_exists(path);
|
||||
unlink_if_exists(COMPONENT_UPDATE_CHECK_TIMESTAMP);
|
||||
command_success_from_args([ "rm", "-rf", COMPONENT_ACTION_LOCK ]);
|
||||
for (let path in fs.glob(COMPONENT_ACTION_DIR + "/*"))
|
||||
if (!component_job_is_running(path))
|
||||
unlink_if_exists(path);
|
||||
}
|
||||
|
||||
function luci_postinst() {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
# A Tachyon self-update is itself a component job: the job installs the package
|
||||
# whose postinst runs remove_component_update_cache(). That cleanup used to wipe
|
||||
# the whole component-actions directory, deleting the state file of the very job
|
||||
# that was still running. LuCI then polled a job that no longer existed, got
|
||||
# neither a message nor stderr, and rendered its "Failed to execute" fallback
|
||||
# over an update that had actually succeeded.
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
PACKAGE_UC="$TACHYON_LIB/service/package.uc"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
STATE_DIR="$WORK_DIR/run/tachyon"
|
||||
ACTION_DIR="$STATE_DIR/component-actions"
|
||||
CHECK_DIR="$STATE_DIR/component-update-checks"
|
||||
mkdir -p "$ACTION_DIR" "$CHECK_DIR"
|
||||
|
||||
RUNNING_JOB="$ACTION_DIR/1786306166-287510904.json"
|
||||
FINISHED_JOB="$ACTION_DIR/1786306100-111111111.json"
|
||||
STALE_CHECK="$CHECK_DIR/tachyon.json"
|
||||
TIMESTAMP="$STATE_DIR/component-update-check.timestamp"
|
||||
|
||||
printf '%s\n' '{ "success": true, "running": true, "kind": "component", "component": "tachyon", "action": "install", "message": "Component action is running", "started_at": 1786306166 }' >"$RUNNING_JOB"
|
||||
printf '%s\n' '{ "success": true, "running": false, "kind": "component", "component": "sing_box", "action": "check_update", "message": "Latest version is installed" }' >"$FINISHED_JOB"
|
||||
printf '%s\n' '{ "component": "tachyon", "status": "latest" }' >"$STALE_CHECK"
|
||||
printf '%s\n' '1786306166' >"$TIMESTAMP"
|
||||
|
||||
TACHYON_PACKAGE_TEST_MODE=1 \
|
||||
TACHYON_RUNTIME_STATE_DIR="$STATE_DIR" \
|
||||
TACHYON_UI_COMPONENT_ACTION_DIR="$ACTION_DIR" \
|
||||
TACHYON_COMPONENT_UPDATE_CHECK_CACHE_DIR="$CHECK_DIR" \
|
||||
TACHYON_COMPONENT_UPDATE_CHECK_STATE_FILE="$TIMESTAMP" \
|
||||
TACHYON_COMPONENT_ACTION_LOCK="$STATE_DIR/component-action.lock" \
|
||||
TACHYON_CONFIG_NAME=tachyon-package-job-test \
|
||||
ucode -L "$TACHYON_LIB" "$PACKAGE_UC" luci-postinst >/dev/null 2>&1 ||
|
||||
fail "luci-postinst must succeed in test mode"
|
||||
|
||||
# The whole point: the in-flight job survives so its worker can write the result.
|
||||
[ -f "$RUNNING_JOB" ] ||
|
||||
fail "postinst deleted the state file of a running component job; the UI then reports 'Failed to execute' over a successful update"
|
||||
|
||||
# Finished results are genuinely stale and must still be cleared.
|
||||
[ ! -f "$FINISHED_JOB" ] ||
|
||||
fail "postinst must still remove finished component job results"
|
||||
[ ! -f "$STALE_CHECK" ] ||
|
||||
fail "postinst must still clear the component update check cache"
|
||||
[ ! -f "$TIMESTAMP" ] ||
|
||||
fail "postinst must still clear the component update check timestamp"
|
||||
|
||||
# Structural: no unconditional wipe of the job directory may come back.
|
||||
sweep="$(sed -n '/^function remove_component_update_cache/,/^}/p' "$PACKAGE_UC" | sed 's://.*::')"
|
||||
[ -n "$sweep" ] || fail "service/package.uc must define remove_component_update_cache"
|
||||
grep -Fq 'component_job_is_running' <<<"$sweep" ||
|
||||
fail "remove_component_update_cache must skip jobs that are still running"
|
||||
|
||||
printf 'package postinst running-job checks passed\n'
|
||||
Reference in New Issue
Block a user