mirror of
https://gh-proxy.org/https://github.com/klinsmaya/open-connector.git
synced 2026-11-09 18:06:53 +08:00
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import type { RuntimeDataBackup } from "../src/server/runtime-data-backup.ts";
|
|
|
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { decodeRuntimeDataBackup, encodeRuntimeDataBackup } from "../src/server/runtime-data-backup.ts";
|
|
import { createSecretCodec } from "../src/server/secret-codec.ts";
|
|
import { SqliteRuntimeDatabase } from "../src/server/sqlite-runtime-store.ts";
|
|
|
|
const command = process.argv[2];
|
|
const options = parseOptions(process.argv.slice(3));
|
|
const dataDir = resolve(options.dataDir ?? process.env.OOMOL_CONNECT_DATA_DIR ?? join(process.cwd(), "data"));
|
|
const databasePath = join(dataDir, "connect.sqlite");
|
|
const secretCodec = createSecretCodec(process.env.OOMOL_CONNECT_ENCRYPTION_KEY);
|
|
|
|
if (!command || !["export", "import", "reset", "rotate-key"].includes(command)) {
|
|
printUsageAndExit();
|
|
}
|
|
|
|
await mkdir(dataDir, { recursive: true });
|
|
|
|
if (command === "rotate-key") {
|
|
const nextEncryptionKey = process.env.OOMOL_CONNECT_NEW_ENCRYPTION_KEY;
|
|
if (!nextEncryptionKey && options.plain !== "true") {
|
|
throw new Error("rotate-key requires OOMOL_CONNECT_NEW_ENCRYPTION_KEY unless --plain is set.");
|
|
}
|
|
const source = new SqliteRuntimeDatabase(databasePath, { secretCodec });
|
|
const snapshot = await source.exportSnapshot().finally(() => source.close());
|
|
const target = new SqliteRuntimeDatabase(databasePath, {
|
|
secretCodec: createSecretCodec(nextEncryptionKey),
|
|
});
|
|
try {
|
|
target.restoreSnapshot(snapshot);
|
|
console.log(`Rotated runtime data encryption in ${databasePath}.`);
|
|
} finally {
|
|
target.close();
|
|
}
|
|
} else {
|
|
const database = new SqliteRuntimeDatabase(databasePath, { secretCodec });
|
|
try {
|
|
if (command === "export") {
|
|
const output = requireOption(options.output, "--output is required for export.");
|
|
if (!process.env.OOMOL_CONNECT_BACKUP_KEY && options.plain !== "true") {
|
|
throw new Error("export requires OOMOL_CONNECT_BACKUP_KEY unless --plain is set.");
|
|
}
|
|
const snapshot = await database.exportSnapshot();
|
|
const backup = encodeRuntimeDataBackup({
|
|
snapshot,
|
|
backupKey: process.env.OOMOL_CONNECT_BACKUP_KEY,
|
|
});
|
|
await mkdir(dirname(resolve(output)), { recursive: true });
|
|
await writeFile(resolve(output), `${JSON.stringify(backup, null, 2)}\n`);
|
|
console.log(`Exported runtime data to ${resolve(output)}.`);
|
|
} else if (command === "import") {
|
|
const input = requireOption(options.input, "--input is required for import.");
|
|
const backup = JSON.parse(await readFile(resolve(input), "utf8")) as RuntimeDataBackup;
|
|
const snapshot = decodeRuntimeDataBackup({
|
|
backup,
|
|
backupKey: process.env.OOMOL_CONNECT_BACKUP_KEY,
|
|
});
|
|
database.restoreSnapshot(snapshot);
|
|
console.log(`Imported runtime data from ${resolve(input)}.`);
|
|
} else {
|
|
if (options.yes !== "true") {
|
|
throw new Error("reset requires --yes.");
|
|
}
|
|
database.resetRuntimeData();
|
|
console.log(`Reset runtime data in ${databasePath}.`);
|
|
}
|
|
} finally {
|
|
database.close();
|
|
}
|
|
}
|
|
|
|
type RuntimeDataCommandOptions = {
|
|
dataDir?: string;
|
|
input?: string;
|
|
output?: string;
|
|
plain?: string;
|
|
yes?: string;
|
|
};
|
|
|
|
function parseOptions(args: string[]): RuntimeDataCommandOptions {
|
|
const options: RuntimeDataCommandOptions = {};
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index];
|
|
if (arg === "--yes") {
|
|
options.yes = "true";
|
|
continue;
|
|
}
|
|
if (arg === "--plain") {
|
|
options.plain = "true";
|
|
continue;
|
|
}
|
|
|
|
const value = args[index + 1];
|
|
if (!value) {
|
|
throw new Error(`${arg} requires a value.`);
|
|
}
|
|
|
|
if (arg === "--data-dir") {
|
|
options.dataDir = value;
|
|
} else if (arg === "--input") {
|
|
options.input = value;
|
|
} else if (arg === "--output") {
|
|
options.output = value;
|
|
} else {
|
|
throw new Error(`Unknown option: ${arg}.`);
|
|
}
|
|
index += 1;
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
function requireOption(value: string | undefined, message: string): string {
|
|
if (!value) {
|
|
throw new Error(message);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function printUsageAndExit(): never {
|
|
console.error(`Usage:
|
|
node scripts/runtime-data.ts export --output backup.json [--data-dir ./data]
|
|
node scripts/runtime-data.ts export --output backup.json --plain [--data-dir ./data]
|
|
node scripts/runtime-data.ts import --input backup.json [--data-dir ./data]
|
|
node scripts/runtime-data.ts reset --yes [--data-dir ./data]
|
|
node scripts/runtime-data.ts rotate-key [--data-dir ./data]
|
|
node scripts/runtime-data.ts rotate-key --plain [--data-dir ./data]
|
|
|
|
Set OOMOL_CONNECT_ENCRYPTION_KEY to read/write encrypted local credential records.
|
|
Set OOMOL_CONNECT_NEW_ENCRYPTION_KEY when rotating to a new encryption key.
|
|
Set OOMOL_CONNECT_BACKUP_KEY to encrypt or decrypt backup files.`);
|
|
process.exit(1);
|
|
}
|