| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 |
- #!/usr/bin/env node
- // Operator-bound, application-scoped repair. No restart and no shared service changes.
- import { internalRoutingConfig } from './lib/admin-internal-routing.mjs';
- import { withGateway } from '../perf/speed-round3-2026-11-10/server-util.mjs';
- const mode = process.argv[2] || '--check';
- if (!['--check', '--apply', '--rollback'].includes(mode)) throw new Error('Use --check, --apply EXPECTED_SHA256, or --rollback EXPECTED_SHA256 BACKUP_PATH');
- const expected = process.argv[3] || '';
- if (mode !== '--check' && !/^[a-f0-9]{64}$/.test(expected)) throw new Error('Apply requires the checked configuration SHA-256');
- if (mode === '--rollback' && !process.argv[4]) throw new Error('Rollback requires its exact backup path');
- async function run(input) {
- const fs = await import('node:fs/promises'), crypto = await import('node:crypto');
- const path = '/opt/future-server/config/config.xiaoshu.json';
- const original = await fs.readFile(path, 'utf8'), before = JSON.parse(original);
- let next = internalRoutingConfig(before);
- const sha = value => crypto.createHash('sha256').update(value).digest('hex');
- if (input.rollback) {
- if (!/^\/opt\/future-server\/config\/\.xiaoshu-routing-backups\/[0-9]+\.json$/.test(input.rollback)) throw new Error('Invalid rollback path');
- const saved = JSON.parse(await fs.readFile(input.rollback, 'utf8'));
- if (JSON.stringify(internalRoutingConfig(saved)) !== JSON.stringify(internalRoutingConfig(before))) throw new Error('Backup contains unrelated configuration changes');
- next = saved;
- }
- if (Parse.applicationId !== before.parse.appId || global.Config?.parse?.appId !== before.parse.appId) throw new Error('Unexpected runtime application');
- if (![before.parse.serverURL, next.parse.serverURL].includes(Parse.serverURL)) throw new Error('Unexpected SDK routing');
- // Verify the target app through its own loopback listener before any change.
- const response = await fetch(internalRoutingConfig(before).parse.serverURL + '/classes/Company?limit=0&count=1', {
- headers: { 'X-Parse-Application-Id': Parse.applicationId, 'X-Parse-Master-Key': Parse.masterKey },
- signal: AbortSignal.timeout(5000),
- });
- const data = await response.json();
- if (!response.ok || !Number.isInteger(data.count)) throw new Error('Loopback application check failed');
- let backup = null;
- if (input.apply) {
- if (sha(original) !== input.expected) throw new Error('Configuration changed since check');
- if (next.parse.serverURL !== before.parse.serverURL) {
- const dir = '/opt/future-server/config/.xiaoshu-routing-backups';
- await fs.mkdir(dir, { recursive: true, mode: 0o700 });
- backup = dir + '/' + Date.now() + '.json';
- await fs.writeFile(backup, original, { mode: 0o600, flag: 'wx' });
- const temp = path + '.routing-' + crypto.randomUUID();
- await fs.writeFile(temp, JSON.stringify(next, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
- try {
- if (sha(await fs.readFile(path, 'utf8')) !== input.expected) throw new Error('Configuration changed before replacement');
- await fs.rename(temp, path);
- } finally { await fs.unlink(temp).catch(() => {}); }
- }
- // All functions in this Xiaoshu process share this SDK; active requests keep running.
- Parse.serverURL = next.parse.serverURL;
- global.Config.parse.serverURL = next.parse.serverURL;
- }
- const stored = JSON.parse(await fs.readFile(path, 'utf8'));
- return { applied: Boolean(input.apply), beforeSha256: sha(original), afterSha256: sha(await fs.readFile(path, 'utf8')),
- serverURL: stored.parse.serverURL, publicServerURL: stored.parse.publicServerURL,
- runtimeServerURL: Parse.serverURL, appId: Parse.applicationId, backup, pid: process.pid };
- }
- await withGateway(internalRoutingConfig.toString() + '\n' + run.toString(), async call => {
- console.log(JSON.stringify(await call({ apply: mode !== '--check', expected, rollback: mode === '--rollback' ? process.argv[4] : undefined })));
- });
|