Update Harbor client and gateway functionality
Build and Deploy Gateway / build-and-push (push) Successful in 35s
Build and Deploy Gateway / deploy (push) Successful in 14s

This commit is contained in:
2026-08-17 15:23:16 +03:00
parent 0b39211fbd
commit 7c255192b0
41 changed files with 1443 additions and 218 deletions
+89 -3
View File
@@ -6,6 +6,9 @@ import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { buildGatewayPresence } from '../../dist/server/gatewayPresence.js';
import { normalizeSubscriptionConfig } from '../../dist/server/subscription.js';
const root = path.resolve(import.meta.dirname, '../..');
function listen(server, ...args) {
@@ -72,7 +75,7 @@ if (process.argv[2] === 'run') {
function profileState(server) {
return {
schemaVersion: 5,
schemaVersion: 6,
revision: 4,
profiles: [{
id: 'profile-a',
@@ -116,6 +119,8 @@ async function startClientFixture(t, {
state,
cacheContents,
config,
hostNetwork,
gatewayPresencePort,
}) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-startup-recovery-'));
const { binDirectory, markerPath } = fakeSingbox(directory);
@@ -126,6 +131,8 @@ async function startClientFixture(t, {
if (config !== undefined) {
fs.writeFileSync(path.join(directory, 'sing-box-config.json'), JSON.stringify(config));
}
const hostNetworkPath = path.join(directory, 'host-network.json');
if (hostNetwork !== undefined) fs.writeFileSync(hostNetworkPath, JSON.stringify(hostNetwork));
const port = await freePort();
const child = spawn(process.execPath, ['dist/server/main.js'], {
cwd: root,
@@ -136,7 +143,10 @@ async function startClientFixture(t, {
DATA_DIR: directory,
PORT: String(port),
PATH: `${binDirectory}:${process.env.PATH || ''}`,
HARBOR_HOST_NETWORK_STATE: path.join(directory, 'missing-network.json'),
HARBOR_HOST_NETWORK_STATE: hostNetwork === undefined
? path.join(directory, 'missing-network.json')
: hostNetworkPath,
...(gatewayPresencePort ? { HARBOR_GATEWAY_CONTROL_PORT: String(gatewayPresencePort) } : {}),
HARBOR_TEST_RUN_MARKER: markerPath,
},
stdio: ['ignore', 'ignore', 'pipe'],
@@ -154,6 +164,82 @@ async function startClientFixture(t, {
};
}
function bootStateWithRules() {
const normalized = normalizeSubscriptionConfig({
outbounds: [{
type: 'vless',
tag: 'Boot VPN',
server: 'boot.example',
server_port: 443,
uuid: '00000000-0000-4000-8000-000000000000',
}],
});
const server = normalized.servers[0];
const state = profileState(server);
state.profiles[0].subscriptionUrl = 'https://provider.example/0123456789abcdef0123456789abcdef';
state.profiles[0].subscriptionConfig = normalized.config;
state.routeRules = [
{ type: 'domain', value: 'api.example.com', enabled: true, outbound: 'vpn' },
{ type: 'domain_suffix', value: 'example.com', enabled: true, outbound: 'direct' },
];
state.appliedRouteRules = [];
return state;
}
test('local-vpn boot promotes the exact desired ordered rules to applied truth', async (t) => {
const state = bootStateWithRules();
const fixture = await startClientFixture(t, { state });
const config = JSON.parse(fs.readFileSync(path.join(fixture.directory, 'sing-box-config.json'), 'utf8'));
assert.equal(fixture.state.route.mode, 'local-vpn');
assert.deepEqual(fixture.state.route.activeLocalRules, state.routeRules);
assert.equal(fixture.state.route.localRulesPendingRestart, false);
assert.deepEqual(config.route.rules.slice(2, 4), [
{ domain: ['api.example.com'], outbound: state.profiles[0].servers[0].id },
{ domain_suffix: ['example.com'], outbound: 'direct' },
]);
});
test('gateway-direct boot keeps the local proxy and diagnostics but omits every user rule', async (t) => {
const state = bootStateWithRules();
const gateway = http.createServer((req, res) => {
const nonce = new URL(req.url, 'http://127.0.0.1').searchParams.get('nonce');
const payload = buildGatewayPresence({
appMode: 'gateway',
subscriptionUrl: state.profiles[0].subscriptionUrl,
gatewayId: 'gateway-test',
nonce,
});
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify(payload));
});
const address = await listen(gateway, 0, '127.0.0.1');
t.after(() => close(gateway));
const fixture = await startClientFixture(t, {
state,
gatewayPresencePort: address.port,
hostNetwork: {
gateway: '127.0.0.1',
interface: 'lo0',
mac: 'aa:bb:cc:dd:ee:ff',
observedAt: new Date().toISOString(),
},
});
const config = JSON.parse(fs.readFileSync(path.join(fixture.directory, 'sing-box-config.json'), 'utf8'));
const stored = JSON.parse(fs.readFileSync(path.join(fixture.directory, 'state.json'), 'utf8'));
assert.equal(fixture.state.route.mode, 'gateway-direct');
assert.deepEqual(fixture.state.route.activeLocalRules, []);
assert.equal(fixture.state.route.localRulesPendingRestart, false);
assert.deepEqual(stored.appliedRouteRules, []);
assert.deepEqual(config.inbounds.map(({ tag }) => tag), ['mixed-in', 'diagnostics-vpn-in']);
assert.equal(config.route.rules.some((rule) => Object.keys(rule).some((key) => key.startsWith('domain'))), false);
assert.deepEqual(config.route.rules[1], {
inbound: ['diagnostics-vpn-in'],
outbound: state.profiles[0].servers[0].id,
});
});
test('corrupt legacy cache with no canonical subscription fails closed instead of starting stale config', async (t) => {
const staleServer = {
id: 'stale-server',
@@ -298,7 +384,7 @@ test('stopped Gateway boot explicitly stops an already running remote dataplane'
});
await listen(dataplane, socketPath);
fs.writeFileSync(path.join(directory, 'state.json'), JSON.stringify({
schemaVersion: 5,
schemaVersion: 6,
revision: 3,
profiles: [],
desiredProfileId: '',