Add runtime version reporting and display
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 16s
Build and Deploy Gateway / deploy (push) Successful in 12s

This commit is contained in:
2026-07-11 21:24:18 +03:00
parent c9223aa3a9
commit 7a6f9a26ac
12 changed files with 351 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ import http from 'node:http';
import path from 'node:path';
import { settings } from './config.js';
import { createSingboxRuntime } from './singboxRuntime.js';
import { buildVersionInfo } from './version.js';
const socketPath = settings.dataplaneSocket;
const runtime = createSingboxRuntime({
@@ -10,6 +11,7 @@ const runtime = createSingboxRuntime({
gateway: true,
tproxyChain: settings.tproxyChain,
});
const versionInfo = buildVersionInfo('gateway');
let ready = false;
function sendJson(res, statusCode, payload) {
@@ -22,6 +24,8 @@ const server = http.createServer(async (req, res) => {
if (req.method === 'GET' && req.url === '/status') {
return sendJson(res, ready ? 200 : 503, {
...await runtime.refresh(),
gatewayBackendVersion: versionInfo.components.gatewayBackend,
singBoxVersion: versionInfo.runtime.singBox,
ready,
});
}

View File

@@ -31,6 +31,7 @@ import {
} from '../shared/contracts/state.js';
import { HarborError, normalizeHarborError } from '../shared/errors.js';
import { createJsonStore, createStateStore } from './services/stateStore.js';
import { buildVersionInfo } from './version.js';
const MAX_BODY_BYTES = 1_000_000;
const SUBSCRIPTION_REFRESH_INTERVAL_MS = 15 * 60 * 1000;
@@ -63,6 +64,7 @@ if (stateStore.recovery) {
}
const remoteDataplane = settings.appMode === 'gateway' && Boolean(process.env.DATAPLANE_SOCKET);
const versionInfo = buildVersionInfo(settings.appMode);
const singboxRuntime = remoteDataplane
? createDataplaneClient(settings.dataplaneSocket)
: createSingboxRuntime({
@@ -462,6 +464,16 @@ async function handleApi(req, res) {
return sendJson(res, 200, await publicState());
}
if (req.method === 'GET' && req.url === '/api/version') {
if (!remoteDataplane) return sendJson(res, 200, versionInfo);
const runtime = await singboxRuntime.refresh();
return sendJson(res, 200, {
...versionInfo,
components: { gatewayBackend: runtime.gatewayBackendVersion || null },
runtime: { singBox: runtime.singBoxVersion || null },
});
}
if (req.method === 'GET' && req.url === '/api/shared-proxy') {
return sendJson(res, 200, buildSharedProxyInfo({
appMode: settings.appMode,

20
src/server/version.js Normal file
View File

@@ -0,0 +1,20 @@
import { spawnSync } from 'node:child_process';
import { HARBOR_VERSIONS } from '../shared/versions.js';
export function detectSingBoxVersion(run = spawnSync) {
const result = run('sing-box', ['version'], { encoding: 'utf8', timeout: 1000 });
const match = /sing-box version\s+v?([^\s]+)/i.exec(`${result.stdout || ''}\n${result.stderr || ''}`);
return match?.[1] || null;
}
export function buildVersionInfo(appMode, run = spawnSync) {
const client = appMode === 'client';
return {
apiVersion: 1,
location: client ? 'mac' : 'gateway',
components: client
? { macClient: HARBOR_VERSIONS.macClient }
: { gatewayBackend: HARBOR_VERSIONS.gatewayBackend },
runtime: { singBox: detectSingBoxVersion(run) },
};
}