update
This commit is contained in:
1 parent
66158e222e
commit
67c757f481
314 files changed
+37945
-22962
No files matched your search
@@ -0,0 +1,121 @@
|
||||
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
||||
import { copyFile, cp, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync, backup as backupDatabase } from "node:sqlite";
|
||||
|
||||
async function copyIfExists(source: string, destination: string) {
|
||||
if (!existsSync(source)) {
|
||||
return;
|
||||
}
|
||||
await copyFile(source, destination);
|
||||
}
|
||||
|
||||
export function buildBackupMethods(
|
||||
deps: {
|
||||
getDb: () => DatabaseSync;
|
||||
setDb: (db: DatabaseSync) => void;
|
||||
openConnection: () => DatabaseSync;
|
||||
init: () => void;
|
||||
close: () => void;
|
||||
profileDir: string;
|
||||
dbPath: string;
|
||||
logsDir: string;
|
||||
artifactsDir: string;
|
||||
backupsDir: string;
|
||||
},
|
||||
) {
|
||||
return {
|
||||
exportBackup(targetDir: string) {
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
deps.getDb().exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
||||
const copyJobs = [
|
||||
backupDatabase(deps.getDb(), join(targetDir, "profile.sqlite")),
|
||||
cp(deps.logsDir, join(targetDir, "logs"), { recursive: true, force: true }),
|
||||
cp(deps.artifactsDir, join(targetDir, "artifacts"), { recursive: true, force: true }),
|
||||
copyIfExists(join(deps.profileDir, "vault.key"), join(targetDir, "vault.key")),
|
||||
copyIfExists(join(deps.profileDir, "agent-api-signing.key"), join(targetDir, "agent-api-signing.key")),
|
||||
copyIfExists(`${deps.dbPath}-wal`, join(targetDir, "profile.sqlite-wal")),
|
||||
copyIfExists(`${deps.dbPath}-shm`, join(targetDir, "profile.sqlite-shm")),
|
||||
];
|
||||
return Promise.all(copyJobs);
|
||||
},
|
||||
|
||||
async restoreFromBackup(sourceDir: string) {
|
||||
const sourceDbPath = join(sourceDir, "profile.sqlite");
|
||||
if (!existsSync(sourceDbPath)) {
|
||||
throw new Error("Backup directory is missing profile.sqlite.");
|
||||
}
|
||||
|
||||
// Validate
|
||||
const validationDb = new DatabaseSync(sourceDbPath);
|
||||
try {
|
||||
const requiredTables = ["settings", "companies", "connectors"] as const;
|
||||
for (const table of requiredTables) {
|
||||
const row = validationDb
|
||||
.prepare("select name from sqlite_master where type = 'table' and name = ?")
|
||||
.get(table) as { name: string } | undefined;
|
||||
if (!row) {
|
||||
throw new Error(`Backup is missing the required table "${table}".`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
validationDb.close();
|
||||
}
|
||||
|
||||
const recoveryDir = join(deps.backupsDir, `pre-restore-${Date.now()}`);
|
||||
mkdirSync(recoveryDir, { recursive: true });
|
||||
deps.getDb().exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
||||
await Promise.all([
|
||||
backupDatabase(deps.getDb(), join(recoveryDir, "profile.sqlite")),
|
||||
cp(deps.logsDir, join(recoveryDir, "logs"), { recursive: true, force: true }),
|
||||
cp(deps.artifactsDir, join(recoveryDir, "artifacts"), { recursive: true, force: true }),
|
||||
copyIfExists(join(deps.profileDir, "vault.key"), join(recoveryDir, "vault.key")),
|
||||
copyIfExists(join(deps.profileDir, "agent-api-signing.key"), join(recoveryDir, "agent-api-signing.key")),
|
||||
copyIfExists(`${deps.dbPath}-wal`, join(recoveryDir, "profile.sqlite-wal")),
|
||||
copyIfExists(`${deps.dbPath}-shm`, join(recoveryDir, "profile.sqlite-shm")),
|
||||
]);
|
||||
|
||||
deps.close();
|
||||
try {
|
||||
await applyBackup(sourceDir);
|
||||
} catch (error) {
|
||||
await applyBackup(recoveryDir);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Restore failed. Previous profile was recovered from ${recoveryDir}. ${message}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function applyBackup(sourceDir: string) {
|
||||
await rm(deps.dbPath, { force: true });
|
||||
await rm(`${deps.dbPath}-wal`, { force: true });
|
||||
await rm(`${deps.dbPath}-shm`, { force: true });
|
||||
|
||||
await copyFile(join(sourceDir, "profile.sqlite"), deps.dbPath);
|
||||
await copyIfExists(join(sourceDir, "profile.sqlite-wal"), `${deps.dbPath}-wal`);
|
||||
await copyIfExists(join(sourceDir, "profile.sqlite-shm"), `${deps.dbPath}-shm`);
|
||||
|
||||
await rm(deps.logsDir, { recursive: true, force: true });
|
||||
await rm(deps.artifactsDir, { recursive: true, force: true });
|
||||
mkdirSync(deps.logsDir, { recursive: true });
|
||||
mkdirSync(deps.artifactsDir, { recursive: true });
|
||||
|
||||
if (existsSync(join(sourceDir, "logs"))) {
|
||||
await cp(join(sourceDir, "logs"), deps.logsDir, { recursive: true, force: true });
|
||||
}
|
||||
if (existsSync(join(sourceDir, "artifacts"))) {
|
||||
await cp(join(sourceDir, "artifacts"), deps.artifactsDir, { recursive: true, force: true });
|
||||
}
|
||||
await copyIfExists(join(sourceDir, "vault.key"), join(deps.profileDir, "vault.key"));
|
||||
await copyIfExists(join(sourceDir, "agent-api-signing.key"), join(deps.profileDir, "agent-api-signing.key"));
|
||||
if (existsSync(join(deps.profileDir, "vault.key"))) {
|
||||
chmodSync(join(deps.profileDir, "vault.key"), 0o600);
|
||||
}
|
||||
if (existsSync(join(deps.profileDir, "agent-api-signing.key"))) {
|
||||
chmodSync(join(deps.profileDir, "agent-api-signing.key"), 0o600);
|
||||
}
|
||||
|
||||
deps.setDb(deps.openConnection());
|
||||
deps.init();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user