Add local routing rules to Harbor
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 17s
Build and Deploy Gateway / deploy (push) Successful in 6s

This commit is contained in:
2026-07-11 21:52:03 +03:00
parent 306a9b8ced
commit a0c66edb02
17 changed files with 823 additions and 26 deletions

View File

@@ -169,6 +169,10 @@ setInterval(() => {}, 60_000);
});
assertStateSnapshot(initial);
assert.equal(initial.selection.appliedServerId, 'test-vpn');
assert.deepEqual(initial.route.localRules, {
builtIn: [{ type: 'domain_suffix', value: 'ru' }],
custom: [],
});
assert.equal(JSON.stringify(initial).includes(subscriptionUrl), false);
const stateKeys = Object.keys(initial).sort();
let revision = initial.revision;
@@ -234,6 +238,66 @@ setInterval(() => {}, 60_000);
assert.equal((await mutation('/api/singbox/stop')).state.connection.desired, 'stopped');
assert.equal((await mutation('/api/singbox/restart')).state.connection.desired, 'running');
const rulesRevision = revision;
const routed = await mutation('/api/route-rules', 'PUT', {
expectedRevision: rulesRevision,
rules: [
{ type: 'domain', value: 'https://Example.com/private?q=1' },
{ type: 'domain_suffix', value: '*.Example.org' },
],
});
assert.deepEqual(routed.state.route.localRules.custom, [
{ type: 'domain', value: 'example.com' },
{ type: 'domain_suffix', value: 'example.org' },
]);
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(dir, 'sing-box-config.json'))).route.rules.slice(0, 3), [
{ domain_suffix: ['ru'], outbound: 'direct' },
{ domain: ['example.com'], outbound: 'direct' },
{ domain_suffix: ['example.org'], outbound: 'direct' },
]);
const invalidRules = await rawRequest(port, '/api/route-rules', 'PUT', {
expectedRevision: revision,
rules: [{ type: 'domain_regex', value: '.*' }],
});
assert.equal(invalidRules.response.status, 400);
assert.equal(invalidRules.payload.error.code, 'REQUEST_INVALID');
assert.equal((await request(port, '/api/state')).revision, revision);
const staleRules = await rawRequest(port, '/api/route-rules', 'PUT', {
expectedRevision: rulesRevision,
rules: [],
});
assert.equal(staleRules.response.status, 409);
assert.equal(staleRules.payload.error.code, 'STATE_CONFLICT');
assert.deepEqual((await request(port, '/api/state')).route.localRules.custom, routed.state.route.localRules.custom);
const workingConfig = fs.readFileSync(path.join(dir, 'sing-box-config.json'), 'utf8');
fs.writeFileSync(singboxPath, `#!/usr/bin/env node
const fs = require('node:fs');
if (process.argv[2] === 'check') {
const config = fs.readFileSync(process.argv[4], 'utf8');
process.exit(config.includes('broken.example') ? 1 : 0);
}
if (process.argv[2] === 'version') process.exit(0);
process.on('SIGTERM', () => process.exit(0));
setInterval(() => {}, 60_000);
`);
fs.chmodSync(singboxPath, 0o755);
const failedRules = await rawRequest(port, '/api/route-rules', 'PUT', {
expectedRevision: revision,
rules: [{ type: 'domain', value: 'broken.example' }],
});
assert.equal(failedRules.response.status, 422);
assert.equal(failedRules.payload.error.code, 'CONFIG_INVALID');
const rolledBack = await request(port, '/api/state');
assert.deepEqual(rolledBack.route.localRules.custom, routed.state.route.localRules.custom);
assert.equal(rolledBack.connection.process, 'running');
assert.equal(fs.readFileSync(path.join(dir, 'sing-box-config.json'), 'utf8'), workingConfig);
revision = rolledBack.revision;
fs.writeFileSync(singboxPath, workingSingbox);
fs.chmodSync(singboxPath, 0o755);
fs.writeFileSync(singboxPath, `#!/usr/bin/env node
if (process.argv[2] === 'check') {
require('node:fs').unlinkSync(process.argv[1]);
@@ -253,6 +317,7 @@ if (process.argv[2] === 'check') {
const forgotten = await mutation('/api/subscription', 'DELETE');
assert.equal(forgotten.state.subscription.status, 'missing');
assert.equal(forgotten.state.servers.length, 0);
assert.deepEqual(forgotten.state.route.localRules.custom, routed.state.route.localRules.custom);
assert.deepEqual((await stateResponse('/api/servers/ping-all')).results, []);
const missingConfig = await rawRequest(port, '/api/singbox/restart', 'POST');