fix(settings): восстанавливать базу транзакционно и сохранять результат операции
This commit is contained in:
1 parent
ff8b04bddf
commit
af5dbb0519
12 files changed
+86
-40
No files matched your search
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process"
|
||||
import { mkdir, rm, writeFile } from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import { executeSystemDatabaseRestore, validateSystemDatabaseRestoreArchive, type SystemDatabaseRestoreOutcome } from "./system-database-restore-execution.js"
|
||||
import { env } from "../config.js"
|
||||
import { withAdvisoryLock } from "../db/index.js"
|
||||
import { refreshScheduler, stopScheduler } from "./scheduler.js"
|
||||
@@ -11,8 +12,6 @@ import {
|
||||
stopTrafficFlowListener,
|
||||
} from "./traffic-flow-ingest.js"
|
||||
|
||||
const PG_DUMP_MAGIC = Buffer.from("PGDMP")
|
||||
const MAX_RESTORE_BYTES = 512 * 1024 * 1024
|
||||
const BACKUP_LOCK_KEY = 8_723_102
|
||||
|
||||
function fmtTimestamp(date = new Date()): string {
|
||||
@@ -20,15 +19,6 @@ function fmtTimestamp(date = new Date()): string {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`
|
||||
}
|
||||
|
||||
function assertPgDumpFile(buffer: Buffer): void {
|
||||
if (buffer.length < PG_DUMP_MAGIC.length) {
|
||||
throw new Error("Файл слишком маленький для дампа PostgreSQL")
|
||||
}
|
||||
if (!buffer.subarray(0, PG_DUMP_MAGIC.length).equals(PG_DUMP_MAGIC)) {
|
||||
throw new Error("Файл не похож на резервную копию PostgreSQL (ожидается формат custom, PGDMP)")
|
||||
}
|
||||
}
|
||||
|
||||
function spawnCollected(command: string, args: string[], opts?: { stdin?: Buffer }): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
@@ -56,7 +46,7 @@ function spawnCollected(command: string, args: string[], opts?: { stdin?: Buffer
|
||||
|
||||
async function withDatabaseOperation<T>(fn: () => Promise<T>): Promise<T> {
|
||||
return await withAdvisoryLock(BACKUP_LOCK_KEY, async () => {
|
||||
stopTrafficFlowListener()
|
||||
await stopTrafficFlowListener()
|
||||
stopScheduler()
|
||||
try {
|
||||
return await fn()
|
||||
@@ -77,21 +67,16 @@ export async function exportSystemDatabaseBackup(): Promise<{ filename: string;
|
||||
})
|
||||
}
|
||||
|
||||
export async function restoreSystemDatabaseBackup(buffer: Buffer): Promise<void> {
|
||||
if (buffer.length > MAX_RESTORE_BYTES) {
|
||||
throw new Error("Файл бэкапа слишком большой")
|
||||
}
|
||||
assertPgDumpFile(buffer)
|
||||
|
||||
await withDatabaseOperation(async () => {
|
||||
const tempDir = path.join(os.tmpdir(), "mmapp-db-restore")
|
||||
await mkdir(tempDir, { recursive: true })
|
||||
const tempPath = path.join(tempDir, `restore-${randomUUID()}.dump`)
|
||||
try {
|
||||
await writeFile(tempPath, buffer)
|
||||
await spawnCollected("pg_restore", ["--clean", "--if-exists", "-d", env.DATABASE_URL, tempPath])
|
||||
} finally {
|
||||
await rm(tempPath, { force: true })
|
||||
}
|
||||
})
|
||||
export async function restoreSystemDatabaseBackup(buffer: Buffer): Promise<SystemDatabaseRestoreOutcome> {
|
||||
validateSystemDatabaseRestoreArchive(buffer)
|
||||
return await withDatabaseOperation(() => executeSystemDatabaseRestore(buffer, env.DATABASE_URL, {
|
||||
allocate: async () => {
|
||||
const tempDir = path.join(os.tmpdir(), "mmapp-db-restore")
|
||||
await mkdir(tempDir, { recursive: true })
|
||||
return path.join(tempDir, `restore-${randomUUID()}.dump`)
|
||||
},
|
||||
write: (file, data) => writeFile(file, data),
|
||||
run: async args => { await spawnCollected("pg_restore", args) },
|
||||
remove: file => rm(file, { force: true }),
|
||||
}))
|
||||
}
|
||||
Reference in new issue
Block a user