fix(ui): report the live sing-box version for plain extended installs
The dashboard showed a sing-box version that never changed. build_system_info()
preferred /etc/tachyon/sing-box-version for every "extended" variant, and that
state file is written by the component action alone — a sing-box installed via
install.sh, opkg or by hand leaves it pinned at whatever was recorded last. The
UI then reported the stale version indefinitely, and the update badge compared
the release against it rather than against the binary actually on disk.
The state file only exists for variants whose binary cannot be executed to print
a version: extended-compressed is a self-extracting stub, and lx needs a runtime
that may be absent. Plain extended answers `sing-box version` fine — singbox
/runtime.uc:259 and subscription/cache.uc:1482 both gate on the compressed
variants only. Covering plain extended was a regression from 46ef97fe.
Both call sites now share sing_box_resolved_version(), which also falls through
to the live probe when the state file is missing or empty instead of reporting
"unknown".
Verified on a router: with the state file still holding 1.13.16-extended-2.6.2,
system-info now reports the installed 1.13.18-extended-2.6.3.
This commit is contained in:
@@ -1079,12 +1079,38 @@ function sing_box_component_action_running() {
|
||||
return module_success(SERVICE_UI_UC, [ "component-action-running-for", "sing_box" ]);
|
||||
}
|
||||
|
||||
// Only the variants whose binary cannot be executed for a version string fall back
|
||||
// to the state file: `extended-compressed` is a self-extracting stub and `lx` needs
|
||||
// a runtime that may be absent. Plain `extended` runs `sing-box version` fine, and
|
||||
// listing it here made the UI show whatever was last written to
|
||||
// /etc/tachyon/sing-box-version — a file only the component action updates. A
|
||||
// sing-box installed any other way (install.sh, opkg, by hand) then displayed a
|
||||
// stale version forever, and the update badge compared against it.
|
||||
function sing_box_live_probe_disabled() {
|
||||
return sing_box_marker_is("extended") ||
|
||||
sing_box_marker_is("extended-compressed") ||
|
||||
return sing_box_marker_is("extended-compressed") ||
|
||||
sing_box_marker_is("lx") ||
|
||||
sing_box_component_action_running();
|
||||
}
|
||||
|
||||
// Resolves the pair (version, version-output) both callers below need. The state
|
||||
// file is only consulted for variants whose binary cannot be run, and an empty or
|
||||
// missing state falls through to the live probe rather than reporting "unknown":
|
||||
// the state is written by the component action alone, so any sing-box installed by
|
||||
// install.sh, opkg or by hand has none.
|
||||
function sing_box_resolved_version() {
|
||||
if (sing_box_live_probe_disabled()) {
|
||||
let state = replace(module_output(SINGBOX_RUNTIME_UC, [ "read-version-state" ]), /[\r\n]+$/g, "");
|
||||
if (state != "")
|
||||
return { version: state, output: "" };
|
||||
}
|
||||
|
||||
let output = module_output(SINGBOX_RUNTIME_UC, [ "version-output" ]);
|
||||
return {
|
||||
version: replace(module_output_stdin(SINGBOX_RUNTIME_UC, [ "version-from-output" ], output), /[\r\n]+$/g, ""),
|
||||
output: output
|
||||
};
|
||||
}
|
||||
|
||||
function sing_box_tiny_package_installed() {
|
||||
return module_success(PACKAGES_UC, [ "installed", "sing-box-tiny" ]);
|
||||
}
|
||||
@@ -1151,14 +1177,9 @@ function build_system_info() {
|
||||
let sing_box_version_output = "";
|
||||
|
||||
if (command_exists("sing-box")) {
|
||||
if (sing_box_live_probe_disabled()) {
|
||||
sing_box_version = replace(module_output(SINGBOX_RUNTIME_UC, [ "read-version-state" ]), /[\r\n]+$/g, "");
|
||||
sing_box_version_output = "";
|
||||
}
|
||||
else {
|
||||
sing_box_version_output = module_output(SINGBOX_RUNTIME_UC, [ "version-output" ]);
|
||||
sing_box_version = replace(module_output_stdin(SINGBOX_RUNTIME_UC, [ "version-from-output" ], sing_box_version_output), /[\r\n]+$/g, "");
|
||||
}
|
||||
let resolved = sing_box_resolved_version();
|
||||
sing_box_version = resolved.version;
|
||||
sing_box_version_output = resolved.output;
|
||||
if (sing_box_version == "")
|
||||
sing_box_version = "unknown";
|
||||
}
|
||||
@@ -1231,15 +1252,8 @@ function get_server_capabilities() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let sing_box_version_output = "";
|
||||
let sing_box_version = "";
|
||||
if (sing_box_live_probe_disabled())
|
||||
sing_box_version = replace(module_output(SINGBOX_RUNTIME_UC, [ "read-version-state" ]), /[\r\n]+$/g, "");
|
||||
else {
|
||||
sing_box_version_output = module_output(SINGBOX_RUNTIME_UC, [ "version-output" ]);
|
||||
sing_box_version = replace(module_output_stdin(SINGBOX_RUNTIME_UC, [ "version-from-output" ], sing_box_version_output), /[\r\n]+$/g, "");
|
||||
}
|
||||
let flags = sing_box_capability_flags(sing_box_version, sing_box_version_output);
|
||||
let resolved = sing_box_resolved_version();
|
||||
let flags = sing_box_capability_flags(resolved.version, resolved.output);
|
||||
write_json({
|
||||
sing_box_extended: flags.extended,
|
||||
sing_box_tiny: flags.tiny,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
# The UI reads the sing-box version out of system-info.json, which used to prefer
|
||||
# /etc/tachyon/sing-box-version for every "extended" install. That state file is
|
||||
# written by the component action alone, so a sing-box installed through install.sh,
|
||||
# opkg or by hand left it pinned at whatever the last component action wrote — the
|
||||
# dashboard reported an old version indefinitely and the update badge compared
|
||||
# against it. The state file is only a fallback for variants whose binary cannot be
|
||||
# executed to print a version: extended-compressed (a self-extracting stub) and lx.
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
RUNTIME_UC="$TACHYON_LIB/diagnostics/runtime.uc"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
BIN_VERSION='1.13.18-extended-2.6.3'
|
||||
STATE_VERSION='1.13.16-extended-2.6.2'
|
||||
|
||||
cat >"$WORK_DIR/sing-box" <<SH
|
||||
#!/bin/sh
|
||||
printf 'sing-box version $BIN_VERSION\n\n'
|
||||
printf 'Tags: with_quic,with_tailscale\n'
|
||||
SH
|
||||
chmod 0755 "$WORK_DIR/sing-box"
|
||||
|
||||
printf '%s\n' "$STATE_VERSION" >"$WORK_DIR/sing-box-version"
|
||||
|
||||
system_info() {
|
||||
local variant="$1"
|
||||
printf '%s\n' "$variant" >"$WORK_DIR/sing-box-variant"
|
||||
PATH="$WORK_DIR:$PATH" \
|
||||
TACHYON_LIB="$TACHYON_LIB" \
|
||||
SB_VARIANT_STATE_FILE="$WORK_DIR/sing-box-variant" \
|
||||
SB_VERSION_STATE_FILE="$WORK_DIR/sing-box-version" \
|
||||
TACHYON_DIAGNOSTICS_SING_BOX_BIN_PATH="$WORK_DIR/sing-box" \
|
||||
TACHYON_SYSTEM_INFO_CACHE_FILE="$WORK_DIR/system-info.json" \
|
||||
TACHYON_SYSTEM_INFO_CACHE_TTL=0 \
|
||||
TACHYON_CONFIG_NAME=tachyon-sing-box-version-test \
|
||||
ucode -L "$TACHYON_LIB" "$RUNTIME_UC" get-system-info
|
||||
}
|
||||
|
||||
reported_version() {
|
||||
sed -n 's/.*"sing_box_version": *"\([^"]*\)".*/\1/p' <<<"$1"
|
||||
}
|
||||
|
||||
# Plain `extended`: the binary answers, so the stale state file must not win.
|
||||
rm -f "$WORK_DIR/system-info.json"
|
||||
extended="$(system_info extended)"
|
||||
got="$(reported_version "$extended")"
|
||||
[ "$got" = "$BIN_VERSION" ] ||
|
||||
fail "extended sing-box must report the live binary version, got '$got' (state file holds $STATE_VERSION)"
|
||||
|
||||
# extended-compressed cannot be executed for a version, so the state file stands.
|
||||
rm -f "$WORK_DIR/system-info.json"
|
||||
compressed="$(system_info extended-compressed)"
|
||||
got="$(reported_version "$compressed")"
|
||||
[ "$got" = "$STATE_VERSION" ] ||
|
||||
fail "extended-compressed sing-box must report the recorded state version, got '$got'"
|
||||
|
||||
# ...but an absent state must fall through to the live probe rather than "unknown":
|
||||
# nothing writes the state except the component action.
|
||||
rm -f "$WORK_DIR/system-info.json"
|
||||
mv "$WORK_DIR/sing-box-version" "$WORK_DIR/sing-box-version.bak"
|
||||
no_state="$(system_info extended-compressed)"
|
||||
got="$(reported_version "$no_state")"
|
||||
[ "$got" = "$BIN_VERSION" ] ||
|
||||
fail "missing version state must fall back to the live probe, got '$got'"
|
||||
mv "$WORK_DIR/sing-box-version.bak" "$WORK_DIR/sing-box-version"
|
||||
|
||||
# Structural: the gate must never list plain `extended` again.
|
||||
gate="$(sed -n '/^function sing_box_live_probe_disabled/,/^}/p' "$RUNTIME_UC")"
|
||||
[ -n "$gate" ] || fail "diagnostics/runtime.uc must define sing_box_live_probe_disabled"
|
||||
if grep -Fq 'sing_box_marker_is("extended")' <<<"$gate"; then
|
||||
fail "sing_box_live_probe_disabled must not cover plain extended; its binary reports its own version"
|
||||
fi
|
||||
grep -Fq 'sing_box_marker_is("extended-compressed")' <<<"$gate" ||
|
||||
fail "sing_box_live_probe_disabled must still cover extended-compressed"
|
||||
grep -Fq 'sing_box_marker_is("lx")' <<<"$gate" ||
|
||||
fail "sing_box_live_probe_disabled must still cover lx"
|
||||
|
||||
printf 'sing-box version source checks passed\n'
|
||||
Reference in New Issue
Block a user