Separate one-shot utility
This commit is contained in:
@@ -11,17 +11,26 @@ try {
|
||||
const admin = new Pool(await config('admin'));
|
||||
try {
|
||||
const ownerConfig = await config('owner'), runtimeConfig = await config('runtime');
|
||||
for (const [role, password] of [['custody_owner', ownerConfig.password], ['custody_runtime', runtimeConfig.password]] as const) {
|
||||
const backupConfig = await postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: '/secrets/backup/url',
|
||||
WIKIDECK_CUSTODY_PG_CA_FILE: '/secrets/backup/ca.crt' });
|
||||
for (const [role, password] of [['custody_owner', ownerConfig.password], ['custody_runtime', runtimeConfig.password],
|
||||
['custody_backup', backupConfig.password]] as const) {
|
||||
// Static role allowlist; password is a generated hex token. Values are never printed, even on error.
|
||||
if (typeof password !== 'string' || !/^[a-f0-9]{64}$/.test(password)) throw new Error('Invalid generated credential');
|
||||
const exists = await admin.query('SELECT 1 FROM pg_roles WHERE rolname=$1', [role]);
|
||||
if (!exists.rowCount) await admin.query(`CREATE ROLE ${role} LOGIN PASSWORD '${password}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`);
|
||||
}
|
||||
await admin.query('REVOKE CREATE ON SCHEMA public FROM PUBLIC; REVOKE ALL ON DATABASE wikideck_custody FROM PUBLIC');
|
||||
await admin.query('GRANT CONNECT,CREATE ON DATABASE wikideck_custody TO custody_owner; GRANT CONNECT ON DATABASE wikideck_custody TO custody_runtime');
|
||||
await admin.query(`GRANT CONNECT,CREATE ON DATABASE wikideck_custody TO custody_owner;
|
||||
GRANT CONNECT ON DATABASE wikideck_custody TO custody_runtime,custody_backup`);
|
||||
} finally { await admin.end(); }
|
||||
const owner = new Pool(await config('owner'));
|
||||
try { await migrateCustody(owner); await grantCustodyRuntime(owner, 'custody_runtime'); }
|
||||
try {
|
||||
await migrateCustody(owner); await grantCustodyRuntime(owner, 'custody_runtime');
|
||||
await owner.query(`GRANT USAGE ON SCHEMA wk_custody TO custody_backup;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA wk_custody TO custody_backup;
|
||||
GRANT SELECT ON ALL SEQUENCES IN SCHEMA wk_custody TO custody_backup`);
|
||||
}
|
||||
finally { await owner.end(); }
|
||||
console.log('Local PostgreSQL schema and restricted runtime grants ready. Sandbox payments only.');
|
||||
} catch {
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
import { createCoordinatedCustodyBackup, verifyCoordinatedCustodyBackup } from '../server/coordinated-backup.ts';
|
||||
|
||||
const [action, ...args] = process.argv.slice(2);
|
||||
if (action !== 'create' && action !== 'verify') throw new Error('Expected create or verify');
|
||||
const allowed = new Set(['--source', '--destination', '--consent']);
|
||||
for (let index = 0; index < args.length; index++) {
|
||||
if (!allowed.has(args[index]) || (args[index] !== '--consent' && (!args[index + 1] || args[index + 1].startsWith('--')))) {
|
||||
throw new Error('Invalid coordinated backup arguments');
|
||||
}
|
||||
if (args[index] !== '--consent') index++;
|
||||
}
|
||||
const value = (name: string) => { const index = args.indexOf(name); return index < 0 ? undefined : args[index + 1]; };
|
||||
const source = value('--source');
|
||||
if (!source) throw new Error('Explicit --source is required');
|
||||
let result: unknown;
|
||||
try {
|
||||
if (action === 'create') {
|
||||
const destination = value('--destination');
|
||||
if (!destination || !args.includes('--consent')) throw new Error('Create requires --destination and explicit --consent');
|
||||
result = await createCoordinatedCustodyBackup(source, destination);
|
||||
} else {
|
||||
if (value('--destination') || args.includes('--consent')) throw new Error('Verify accepts only --source');
|
||||
result = await verifyCoordinatedCustodyBackup(source);
|
||||
}
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} catch {
|
||||
console.error('Coordinated backup failed; check stopped-node state, separate backup credentials, PostgreSQL tools, destination permissions and partial output privately. No source database was reset.');
|
||||
process.exitCode = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user