merge: wave 2 executor (01-02) into main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a5b8fcf9d3
commit
aa1a78d7d9
1081 files changed
+445341
No files matched your search
@@ -0,0 +1,122 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { formatDatabaseBackupResult, runDatabaseBackup } from "./backup-lib.js";
|
||||
|
||||
type PartialConfig = {
|
||||
database?: {
|
||||
mode?: "embedded-postgres" | "postgres";
|
||||
connectionString?: string;
|
||||
embeddedPostgresPort?: number;
|
||||
backup?: {
|
||||
dir?: string;
|
||||
retentionDays?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function expandHomePrefix(value: string): string {
|
||||
if (value === "~") return os.homedir();
|
||||
if (value.startsWith("~/")) return path.resolve(os.homedir(), value.slice(2));
|
||||
return value;
|
||||
}
|
||||
|
||||
function resolvePaperclipHomeDir(): string {
|
||||
const envHome = process.env.PAPERCLIP_HOME?.trim();
|
||||
if (envHome) return path.resolve(expandHomePrefix(envHome));
|
||||
return path.resolve(os.homedir(), ".paperclip");
|
||||
}
|
||||
|
||||
function resolvePaperclipInstanceId(): string {
|
||||
const raw = process.env.PAPERCLIP_INSTANCE_ID?.trim() || "default";
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(raw)) {
|
||||
throw new Error(`Invalid PAPERCLIP_INSTANCE_ID '${raw}'.`);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
function resolveDefaultConfigPath(): string {
|
||||
return path.resolve(resolvePaperclipHomeDir(), "instances", resolvePaperclipInstanceId(), "config.json");
|
||||
}
|
||||
|
||||
function readConfig(configPath: string): PartialConfig | null {
|
||||
if (!existsSync(configPath)) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
||||
return typeof parsed === "object" && parsed ? (parsed as PartialConfig) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function asPositiveInt(value: unknown): number | null {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
||||
const rounded = Math.trunc(value);
|
||||
return rounded > 0 ? rounded : null;
|
||||
}
|
||||
|
||||
function resolveEmbeddedPort(config: PartialConfig | null): number {
|
||||
return asPositiveInt(config?.database?.embeddedPostgresPort) ?? 54329;
|
||||
}
|
||||
|
||||
function resolveConnectionString(config: PartialConfig | null): string {
|
||||
const envUrl = process.env.DATABASE_URL?.trim();
|
||||
if (envUrl) return envUrl;
|
||||
|
||||
if (config?.database?.mode === "postgres" && typeof config.database.connectionString === "string") {
|
||||
const trimmed = config.database.connectionString.trim();
|
||||
if (trimmed) return trimmed;
|
||||
}
|
||||
|
||||
const port = resolveEmbeddedPort(config);
|
||||
return `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`;
|
||||
}
|
||||
|
||||
function resolveDefaultBackupDir(): string {
|
||||
return path.resolve(resolvePaperclipHomeDir(), "instances", resolvePaperclipInstanceId(), "data", "backups");
|
||||
}
|
||||
|
||||
function resolveBackupDir(config: PartialConfig | null): string {
|
||||
const raw = config?.database?.backup?.dir;
|
||||
if (typeof raw === "string" && raw.trim().length > 0) {
|
||||
return path.resolve(expandHomePrefix(raw.trim()));
|
||||
}
|
||||
return resolveDefaultBackupDir();
|
||||
}
|
||||
|
||||
function resolveRetentionDays(config: PartialConfig | null): number {
|
||||
return asPositiveInt(config?.database?.backup?.retentionDays) ?? 30;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveDefaultConfigPath();
|
||||
const config = readConfig(configPath);
|
||||
const connectionString = resolveConnectionString(config);
|
||||
const backupDir = resolveBackupDir(config);
|
||||
const retentionDays = resolveRetentionDays(config);
|
||||
|
||||
console.log(`Config path: ${configPath}`);
|
||||
console.log(`Backing up database to: ${backupDir}`);
|
||||
console.log(`Retention window: ${retentionDays} day(s)`);
|
||||
|
||||
try {
|
||||
const result = await runDatabaseBackup({
|
||||
connectionString,
|
||||
backupDir,
|
||||
retentionDays,
|
||||
filenamePrefix: "paperclip",
|
||||
});
|
||||
|
||||
console.log(`Backup saved: ${formatDatabaseBackupResult(result)}`);
|
||||
} catch (err) {
|
||||
console.error("Backup failed.");
|
||||
if (err instanceof Error) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.error(String(err));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
Reference in new issue
Block a user