Migrate Harbor state and traffic history to SQLite
This commit is contained in:
@@ -5,6 +5,7 @@ import http from 'node:http';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
|
||||
import { buildGatewayPresence } from '../../dist/server/gatewayPresence.js';
|
||||
import { normalizeSubscriptionConfig } from '../../dist/server/subscription.js';
|
||||
@@ -151,6 +152,7 @@ async function startClientFixture(t, {
|
||||
hostNetwork,
|
||||
gatewayPresencePort,
|
||||
trafficSource,
|
||||
expectMigrationError,
|
||||
}) {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-startup-recovery-'));
|
||||
const { binDirectory, markerPath } = fakeSingbox(directory);
|
||||
@@ -188,6 +190,15 @@ async function startClientFixture(t, {
|
||||
await stopChild(child);
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
});
|
||||
if (expectMigrationError) {
|
||||
await assert.rejects(waitForState(port, child, () => stderr), expectMigrationError);
|
||||
assert.notEqual(child.exitCode, 0);
|
||||
assert.equal(fs.existsSync(markerPath), false);
|
||||
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(directory, 'state.json'), 'utf8')), state);
|
||||
assert.equal(fs.readFileSync(path.join(directory, 'subscription-cache.json'), 'utf8'), cacheContents);
|
||||
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(directory, 'sing-box-config.json'), 'utf8')), config);
|
||||
return { directory, markerPath };
|
||||
}
|
||||
return {
|
||||
directory,
|
||||
markerPath,
|
||||
@@ -257,7 +268,9 @@ test('gateway-direct boot keeps the local proxy and diagnostics but omits every
|
||||
},
|
||||
});
|
||||
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'));
|
||||
const db = new DatabaseSync(path.join(fixture.directory, 'harbor.sqlite'), { readOnly: true });
|
||||
const stored = JSON.parse(db.prepare("SELECT value FROM documents WHERE key = 'state'").get().value);
|
||||
db.close();
|
||||
|
||||
assert.equal(fixture.state.route.mode, 'gateway-direct');
|
||||
assert.deepEqual(fixture.state.route.activeLocalRules, []);
|
||||
@@ -279,23 +292,15 @@ test('corrupt legacy cache with no canonical subscription fails closed instead o
|
||||
port: 443,
|
||||
protocol: 'vless',
|
||||
};
|
||||
const fixture = await startClientFixture(t, {
|
||||
await startClientFixture(t, {
|
||||
state: { schemaVersion: 4, revision: 2, connectionDesired: 'running' },
|
||||
cacheContents: '{broken',
|
||||
config: generatedConfig(staleServer),
|
||||
expectMigrationError: /Cannot migrate subscription-cache.json/,
|
||||
});
|
||||
|
||||
assert.equal(fixture.state.subscription.status, 'missing');
|
||||
assert.deepEqual(fixture.state.profiles, []);
|
||||
assert.equal(fixture.state.connection.desired, 'stopped');
|
||||
assert.equal(fixture.state.connection.process, 'stopped');
|
||||
assert.equal(fs.existsSync(fixture.markerPath), false);
|
||||
assert.ok(fs.readdirSync(fixture.directory).some((name) => (
|
||||
name.startsWith('subscription-cache.json.corrupt-')
|
||||
)));
|
||||
});
|
||||
|
||||
test('legacy cache owned by another URL is backed up without mixing providers and boots stopped', async (t) => {
|
||||
test('legacy cache owned by another URL aborts migration without mixing providers or changing originals', async (t) => {
|
||||
const stateServer = {
|
||||
id: 'server-a',
|
||||
label: 'State server',
|
||||
@@ -314,7 +319,8 @@ test('legacy cache owned by another URL is backed up without mixing providers an
|
||||
server: 'cache.example',
|
||||
server_port: 8443,
|
||||
};
|
||||
const fixture = await startClientFixture(t, {
|
||||
await startClientFixture(t, {
|
||||
expectMigrationError: /owner mismatch/,
|
||||
state: {
|
||||
schemaVersion: 4,
|
||||
revision: 2,
|
||||
@@ -331,20 +337,6 @@ test('legacy cache owned by another URL is backed up without mixing providers an
|
||||
}),
|
||||
config: generatedConfig(stateServer),
|
||||
});
|
||||
|
||||
assert.equal(fixture.state.profiles.length, 1);
|
||||
assert.equal(fixture.state.profiles[0].subscription.host, 'state.example/…');
|
||||
assert.deepEqual(fixture.state.profiles[0].servers.map(({ id }) => id), [stateServer.id]);
|
||||
assert.equal(JSON.stringify(fixture.state).includes('cache.example'), false);
|
||||
assert.equal(fixture.state.connection.desired, 'stopped');
|
||||
assert.equal(fixture.state.connection.process, 'stopped');
|
||||
assert.equal(fixture.state.selection.appliedServerId, '');
|
||||
assert.equal(fs.existsSync(path.join(fixture.directory, 'subscription-cache.json')), false);
|
||||
assert.ok(fs.readdirSync(fixture.directory).some((name) => (
|
||||
name.startsWith('subscription-cache.json.backup-v1-')
|
||||
)));
|
||||
assert.equal(fs.existsSync(path.join(fixture.directory, 'sing-box-config.json')), false);
|
||||
assert.equal(fs.existsSync(fixture.markerPath), false);
|
||||
});
|
||||
|
||||
test('boot rejects an existing config whose route mode disagrees with the current route', async (t) => {
|
||||
|
||||
Reference in New Issue
Block a user