1
0
cvsa/src/dump.ts

122 lines
3.0 KiB
TypeScript

import { postgresConfig } from "@core/db/pgConfigNew";
import logger from "@core/log";
import { $, S3Client } from "bun";
import dayjs from "dayjs";
import { env } from "./env";
const AK = env.OSS_ACCESS_KEY_ID;
const SK = env.OSS_ACCESS_KEY_SECRET;
const ENDPOINT = env.BACKUP_S3_ENDPOINT;
const REGION = env.BACKUP_S3_REGION;
const BUCKET = env.BACKUP_S3_BUCKET;
const DIR = env.BACKUP_DIR;
const CONTAINER = env.BACKUP_CONTAINER;
const CONFIG = {
localBackupDir: DIR,
retentionDaily: 3,
s3: {
bucket: BUCKET,
endpoint: ENDPOINT,
region: REGION,
},
};
const { username, password, host, port, database } = postgresConfig;
const dbUri = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
const s3 = new S3Client({
accessKeyId: AK,
bucket: CONFIG.s3.bucket,
endpoint: CONFIG.s3.endpoint,
region: CONFIG.s3.region,
secretAccessKey: SK,
virtualHostedStyle: true,
});
const getDayStr = (): string => {
return dayjs().format("YYYY-MM-DD");
};
const getMonthStr = (): string => {
return dayjs().format("YYYY-MM");
};
async function dump(filePath: string) {
await $`docker exec -u postgres ${CONTAINER} pg_dump -d ${dbUri} -Fc -n public > ${filePath}`;
}
async function runBackup() {
const dayStr = getDayStr();
const monthStr = getMonthStr();
const fileName = `cvsa_${dayStr}.dump`;
const filePath = `${CONFIG.localBackupDir}/${fileName}`;
const localDumpfile = Bun.file(filePath);
logger.log(`Starting backup...`);
if (!(await localDumpfile.exists())) {
logger.log(`Creating dump ${localDumpfile.name}...`);
await dump(filePath);
}
const monthlyBackupFile = s3.file(`dump/monthly/${monthStr}`);
if (!(await monthlyBackupFile.exists())) {
const f = Bun.file(filePath);
logger.log(`Uploading ${filePath} to ${monthlyBackupFile.name}`);
await monthlyBackupFile.write(f);
}
const dailyBackupFile = s3.file(`dump/daily/${dayStr}`);
if (!(await dailyBackupFile.exists())) {
const f = Bun.file(filePath);
logger.log(`Uploading ${filePath} to ${dailyBackupFile.name}`);
await dailyBackupFile.write(f);
}
}
async function rotateS3Backups() {
const dailyBackups = await s3.list({
maxKeys: 1000,
prefix: "dump/daily/",
});
if (!dailyBackups.contents) {
logger.log("No daily backups found");
return;
}
logger.log(`Found ${dailyBackups.contents.length} daily backups`);
for (const content of dailyBackups.contents) {
const key = content.key;
if (!key) {
continue;
}
const dateStr = key.split("/").at(-1);
const date = dayjs(dateStr, "YYYY-MM-DD");
if (date.isBefore(dayjs().subtract(CONFIG.retentionDaily, "day"))) {
logger.log(`Deleting daily backup ${key}`);
await s3.delete(key);
}
}
if (dailyBackups.isTruncated) {
logger.log("Still more daily backups");
await rotateS3Backups();
}
logger.log("Daily backups rotated");
}
async function main() {
try {
await runBackup();
await rotateS3Backups();
} catch (err) {
logger.error(err);
process.exit(1);
}
}
await main();
process.exit();