repair-admin-internal-routing.mjs 3.9 KB

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