fix(agent): fall back to install record for sing-box core version (item 12)
Locally built sing-box v1.13.21 carries no version stamp and reports 'unknown', leaving the panel core-version column empty. The agent probe now falls back to /etc/nexusgate/sing-box/version when the binary reports no version; agent-singbox.sh writes that record (version without the v prefix) on every successful install path, including the already-installed early exit. Agent version stays 0.6.8.
This commit is contained in:
+36
-7
@@ -211,11 +211,12 @@ type engineReport struct {
|
||||
|
||||
var singBoxVersionMu sync.Mutex
|
||||
var singBoxVersionCache struct {
|
||||
path string
|
||||
mtimeNs int64
|
||||
size int64
|
||||
version string
|
||||
probed bool
|
||||
path string
|
||||
mtimeNs int64
|
||||
size int64
|
||||
vfMtimeNs int64 // install-record file mtime (part of the cache key)
|
||||
version string
|
||||
probed bool
|
||||
}
|
||||
|
||||
// parseSingBoxVersion extracts the version token from the first line of
|
||||
@@ -233,22 +234,50 @@ func parseSingBoxVersion(out string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var singBoxVersionRe = regexp.MustCompile(`^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$`)
|
||||
|
||||
// installedSingBoxVersion reads the install record written by
|
||||
// agent-singbox.sh (/etc/nexusgate/sing-box/version, content like
|
||||
// "1.13.21"). It is the fallback for the locally built sing-box, whose
|
||||
// binary carries no version stamp and reports "unknown" (round-3 item
|
||||
// 12). A leading "v" is tolerated and stripped; anything that does not
|
||||
// look like a version is ignored.
|
||||
func installedSingBoxVersion() string {
|
||||
data, err := os.ReadFile(cfg.SingBoxVersionFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
v := strings.TrimSpace(string(data))
|
||||
v = strings.TrimPrefix(v, "v")
|
||||
if !singBoxVersionRe.MatchString(v) {
|
||||
return ""
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func singBoxVersion() string {
|
||||
info, err := os.Stat(cfg.SingBoxBin)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var vfMtimeNs int64 = -1
|
||||
if vfInfo, err := os.Stat(cfg.SingBoxVersionFile); err == nil {
|
||||
vfMtimeNs = vfInfo.ModTime().UnixNano()
|
||||
}
|
||||
singBoxVersionMu.Lock()
|
||||
defer singBoxVersionMu.Unlock()
|
||||
c := &singBoxVersionCache
|
||||
if c.probed && c.path == cfg.SingBoxBin && c.mtimeNs == info.ModTime().UnixNano() && c.size == info.Size() {
|
||||
if c.probed && c.path == cfg.SingBoxBin && c.mtimeNs == info.ModTime().UnixNano() && c.size == info.Size() && c.vfMtimeNs == vfMtimeNs {
|
||||
return c.version
|
||||
}
|
||||
version := ""
|
||||
if out, _, code := runCapture(5*time.Second, cfg.SingBoxBin, "version"); code == 0 {
|
||||
version = parseSingBoxVersion(out)
|
||||
}
|
||||
c.path, c.mtimeNs, c.size, c.version, c.probed = cfg.SingBoxBin, info.ModTime().UnixNano(), info.Size(), version, true
|
||||
if version == "" {
|
||||
version = installedSingBoxVersion()
|
||||
}
|
||||
c.path, c.mtimeNs, c.size, c.vfMtimeNs, c.version, c.probed = cfg.SingBoxBin, info.ModTime().UnixNano(), info.Size(), vfMtimeNs, version, true
|
||||
return version
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ type config struct {
|
||||
Root string // NG_CONFIG_DIR
|
||||
ResourceDir string // Root + "/resources"
|
||||
SingBoxConfig string
|
||||
SingBoxVersionFile string
|
||||
SingBoxLog string
|
||||
SingBoxCursor string
|
||||
KeyFile string
|
||||
@@ -74,6 +75,7 @@ func loadConfig() config {
|
||||
Root: root,
|
||||
ResourceDir: root + "/resources",
|
||||
SingBoxConfig: envOr("NG_SINGBOX_CONFIG", "/etc/nexusgate/sing-box/config.json"),
|
||||
SingBoxVersionFile: envOr("NG_SINGBOX_VERSION_FILE", "/etc/nexusgate/sing-box/version"),
|
||||
SingBoxLog: envOr("NG_SINGBOX_LOG", "/var/log/nexusgate/sing-box-access.log"),
|
||||
SingBoxCursor: envOr("NG_SINGBOX_CURSOR", "/etc/nexusgate/sing-box-cursor.json"),
|
||||
KeyFile: envOr("NG_KEY_FILE", "/etc/nexusgate/keys.json"),
|
||||
|
||||
@@ -71,6 +71,49 @@ func TestSingBoxVersionProbeCachesByMtime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Round-3 item 12: when the binary reports "unknown" (the locally built
|
||||
// sing-box has no version stamp), the probe falls back to the install
|
||||
// record written by agent-singbox.sh.
|
||||
func TestSingBoxVersionFallsBackToInstallRecord(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
binPath, _ := writeFakeSingBox(t, dir, "unknown")
|
||||
recordPath := filepath.Join(dir, "version")
|
||||
if err := os.WriteFile(recordPath, []byte("1.13.21\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldBin, oldRecord := cfg.SingBoxBin, cfg.SingBoxVersionFile
|
||||
cfg.SingBoxBin, cfg.SingBoxVersionFile = binPath, recordPath
|
||||
defer func() { cfg.SingBoxBin, cfg.SingBoxVersionFile = oldBin, oldRecord }()
|
||||
singBoxVersionCache.probed = false
|
||||
defer func() { singBoxVersionCache.probed = false }()
|
||||
|
||||
if got := singBoxVersion(); got != "1.13.21" {
|
||||
t.Fatalf("fallback probe = %q, want 1.13.21", got)
|
||||
}
|
||||
// A stamped binary wins over the record.
|
||||
writeFakeSingBox(t, dir, "1.13.22")
|
||||
if got := singBoxVersion(); got != "1.13.22" {
|
||||
t.Fatalf("stamped probe = %q, want 1.13.22 (record must not shadow it)", got)
|
||||
}
|
||||
// A garbage record is ignored.
|
||||
if err := os.WriteFile(recordPath, []byte("not-a-version\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeFakeSingBox(t, dir, "unknown")
|
||||
if got := singBoxVersion(); got != "" {
|
||||
t.Fatalf("garbage record: got %q, want empty", got)
|
||||
}
|
||||
if got := installedSingBoxVersion(); got != "" {
|
||||
t.Fatalf("installedSingBoxVersion(garbage) = %q, want empty", got)
|
||||
}
|
||||
if err := os.WriteFile(recordPath, []byte("v1.13.21\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := installedSingBoxVersion(); got != "1.13.21" {
|
||||
t.Fatalf("installedSingBoxVersion(v-prefixed) = %q, want 1.13.21", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingBoxVersionMissingBinary(t *testing.T) {
|
||||
oldBin := cfg.SingBoxBin
|
||||
cfg.SingBoxBin = filepath.Join(t.TempDir(), "does-not-exist")
|
||||
|
||||
Reference in New Issue
Block a user