Skip to content

Commit e8fbe4b

Browse files
authored
update (#275)
* update * Fix backup directory permissions
1 parent a117037 commit e8fbe4b

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

packages/db/scripts/backup.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { cp, readFile, stat } from "node:fs/promises";
1+
import { chmod, cp, readFile, rm, stat } from "node:fs/promises";
22
import path from "node:path";
33
import { printCliError, readOptionValue, resolveDatabasePath } from "./cli";
44

5+
const COPIED_RUNTIME_FILES = ["postmaster.pid", "postmaster.opts"];
6+
57
export interface BackupDatabaseOptions {
68
dbPath?: string;
79
now?: Date;
@@ -61,9 +63,27 @@ export async function backupDatabase(
6163
throw new Error(`Backup path already exists: ${backupPath}`);
6264
}
6365
await cp(sourcePath, backupPath, { recursive: true });
66+
await copyDirectoryPermissions(sourcePath, backupPath);
67+
await removeCopiedRuntimeFiles(backupPath);
6468
return { backupPath, sourcePath };
6569
}
6670

71+
async function copyDirectoryPermissions(
72+
sourcePath: string,
73+
backupPath: string,
74+
): Promise<void> {
75+
const sourceStats = await stat(sourcePath);
76+
await chmod(backupPath, sourceStats.mode & 0o777);
77+
}
78+
79+
async function removeCopiedRuntimeFiles(backupPath: string): Promise<void> {
80+
await Promise.all(
81+
COPIED_RUNTIME_FILES.map((fileName) =>
82+
rm(path.join(backupPath, fileName), { force: true }),
83+
),
84+
);
85+
}
86+
6787
async function ensureClusterIsStopped(sourcePath: string): Promise<void> {
6888
const pidPath = path.join(sourcePath, "postmaster.pid");
6989
let pid: number | undefined;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it } from "bun:test";
2+
import { stat, writeFile } from "node:fs/promises";
3+
import path from "node:path";
4+
import { backupDatabase } from "../scripts/backup";
5+
import { migrateDatabase } from "../scripts/migrate";
6+
import {
7+
createDatabasePort,
8+
readMigrationCount,
9+
withDatabasePath,
10+
} from "./helpers/database-script-helpers";
11+
12+
const EMBEDDED_POSTGRES_TEST_TIMEOUT_MS = 20_000;
13+
14+
describe("database backup script", () => {
15+
it(
16+
"omits stale PostgreSQL runtime files from copied backups",
17+
() =>
18+
withDatabasePath(async (dbPath) => {
19+
await migrateDatabase({ dbPath, port: await createDatabasePort() });
20+
const staleRuntimeFiles = ["postmaster.pid", "postmaster.opts"];
21+
22+
for (const fileName of staleRuntimeFiles) {
23+
await writeFile(path.join(dbPath, fileName), "stale runtime state");
24+
}
25+
26+
const result = await backupDatabase({
27+
dbPath,
28+
now: new Date("2026-05-20T01:02:03.004Z"),
29+
});
30+
31+
for (const fileName of staleRuntimeFiles) {
32+
await expectPath(path.join(dbPath, fileName), true);
33+
await expectPath(path.join(result.backupPath, fileName), false);
34+
}
35+
await expect(
36+
readMigrationCount(result.backupPath),
37+
).resolves.toBeGreaterThan(0);
38+
}),
39+
EMBEDDED_POSTGRES_TEST_TIMEOUT_MS,
40+
);
41+
});
42+
43+
async function expectPath(filePath: string, exists: boolean): Promise<void> {
44+
await expect(pathExists(filePath)).resolves.toBe(exists);
45+
}
46+
47+
async function pathExists(filePath: string): Promise<boolean> {
48+
try {
49+
await stat(filePath);
50+
return true;
51+
} catch {
52+
return false;
53+
}
54+
}

0 commit comments

Comments
 (0)