feat(agent+panel): sing-box core version probe and display (round-2 #4)

agent-go: engineReport gains singBoxVersion. The probe runs
`sing-box version` once, parses the version token from its first line
('unknown' from unstamped builds reports empty), and caches it keyed by
the binary's mtime+size — re-probed only when an engine install/update
replaces the file. Steady-state heartbeats pay one stat, no subprocess.
Agent version stays 0.6.8.

panel-go: EngineState.singBoxVersion persisted from heartbeats (old
agents without the field keep showing '-'), servers table core-version
column now renders it. Golden gains heartbeatWithEngine +
serversAfterEngineHeartbeat sections locking the full chain, plus a
listen-address (<hostip>) normalization for the sandbox bind address.
singboxversion_test.go covers parsing, cache hits, and re-probe on
binary replacement.
This commit is contained in:
aaaamoooo
2026-10-20 21:02:59 +00:00
parent 6e82d0d06a
commit bb8a0cb0c4
7 changed files with 248 additions and 23 deletions
+96 -19
View File
@@ -1,6 +1,6 @@
{
"meta": {
"capturedAt": "2026-10-20T20:56:25.514Z",
"capturedAt": "2026-10-20T21:02:25.354Z",
"note": "Go panel golden baseline; see README.md in this directory."
},
"sections": {
@@ -483,6 +483,75 @@
"status": 200
}
},
"heartbeatWithEngine": {
"body": {
"ok": true,
"serverTime": "<ts>"
},
"status": 200
},
"serversAfterEngineHeartbeat": {
"body": {
"servers": [
{
"agentVersion": "0.6.8",
"createdAt": "<ts>",
"enabled": true,
"engine": {
"at": "<ts>",
"certificates": [],
"detail": "",
"singBoxInstalled": true,
"singBoxStatus": "ready",
"singBoxVersion": "1.13.21",
"status": "ready"
},
"id": "<srv>#1",
"labels": [],
"lastSeenAt": "<ts>",
"name": "Entry",
"pendingCleanup": 0,
"portRangeEnd": 50000,
"portRangeStart": 20000,
"publicAddress": "entry.example.com",
"publicAddressV6": "v6.example.com",
"region": "",
"role": "relay",
"status": "online",
"system": {},
"tlsDomain": "tls.example.com",
"updatedAt": "<ts>",
"usage": {
"error": null,
"lastReportAt": "<ts>",
"sampleCount": 2
}
},
{
"agentVersion": "0.6.8",
"createdAt": "<ts>",
"enabled": true,
"engine": null,
"id": "<srv>#2",
"labels": [],
"lastSeenAt": "<ts>",
"name": "Exit",
"pendingCleanup": 0,
"portRangeEnd": 50000,
"portRangeStart": 20000,
"publicAddress": "exit.example.com",
"publicAddressV6": "",
"region": "",
"role": "exit",
"status": "online",
"system": {},
"tlsDomain": "",
"updatedAt": "<ts>"
}
]
},
"status": 200
},
"credentials": {
"alice": {
"body": {
@@ -787,7 +856,7 @@
"id": "<res>#6",
"inbounds": [
{
"listen": "198.18.187.63",
"listen": "<hostip>",
"listen_port": "<port>#4",
"tag": "ng-<nod>#3-in",
"tls": {
@@ -864,7 +933,7 @@
"id": "<res>#6",
"inbounds": [
{
"listen": "198.18.187.63",
"listen": "<hostip>",
"listen_port": "<port>#4",
"tag": "ng-<nod>#3-in",
"tls": {
@@ -1403,7 +1472,7 @@
"id": "<res>#2",
"inbounds": [
{
"listen": "198.18.187.62",
"listen": "<hostip>",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
@@ -1491,7 +1560,7 @@
"id": "<res>#2",
"inbounds": [
{
"listen": "198.18.187.62",
"listen": "<hostip>",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
@@ -1578,7 +1647,7 @@
"id": "<res>#1",
"inbounds": [
{
"listen": "198.18.187.61",
"listen": "<hostip>",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
@@ -1629,7 +1698,7 @@
"id": "<res>#1",
"inbounds": [
{
"listen": "198.18.187.61",
"listen": "<hostip>",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
@@ -2324,7 +2393,7 @@
"id": "<res>#2",
"inbounds": [
{
"listen": "198.18.60.35",
"listen": "<hostip>",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
@@ -2406,7 +2475,7 @@
"id": "<res>#1",
"inbounds": [
{
"listen": "198.18.60.37",
"listen": "<hostip>",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
@@ -2457,7 +2526,7 @@
"id": "<res>#4",
"inbounds": [
{
"listen": "198.18.60.35",
"listen": "<hostip>",
"listen_port": "<port>#3",
"method": "2022-blake3-aes-128-gcm",
"password": "<secret>#12",
@@ -2521,7 +2590,7 @@
"id": "<res>#6",
"inbounds": [
{
"listen": "198.18.187.63",
"listen": "<hostip>",
"listen_port": "<port>#4",
"tag": "ng-<nod>#3-in",
"tls": {
@@ -2598,7 +2667,7 @@
"id": "<res>#2",
"inbounds": [
{
"listen": "198.18.187.62",
"listen": "<hostip>",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
@@ -2680,7 +2749,7 @@
"id": "<res>#1",
"inbounds": [
{
"listen": "198.18.187.61",
"listen": "<hostip>",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
@@ -2717,15 +2786,15 @@
}
],
"keyStore": {
"reality-<nod>#2": {
"privateKey": "<rpriv>#2",
"publicKey": "<rpub>#1",
"updatedAt": "<ts>"
},
"reality-<nod>#3": {
"privateKey": "<rpriv>#1",
"publicKey": "<rpub>#2",
"updatedAt": "<ts>"
},
"reality-<nod>#2": {
"privateKey": "<rpriv>#2",
"publicKey": "<rpub>#1",
"updatedAt": "<ts>"
}
},
"nodeClients": [
@@ -2922,7 +2991,15 @@
"servers": [
{
"createdAt": "<ts>",
"engine": null,
"engine": {
"at": "<ts>",
"certificates": [],
"detail": "",
"singBoxInstalled": true,
"singBoxStatus": "ready",
"singBoxVersion": "1.13.21",
"status": "ready"
},
"id": "<srv>#1",
"labels": [],
"lastSeenAt": "<ts>",