From 13c9b4f3cd14f1fc863cef11748ef70861b02d96 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Sun, 14 Jan 2024 14:34:02 +0100 Subject: [PATCH] feat: PostgreSQL database backups (#462) --- docker/boltz/Dockerfile | 8 +++++ lib/Boltz.ts | 1 + lib/backup/BackupScheduler.ts | 45 ++++++++++++++++++++---- test/unit/backup/BackupScheduler.spec.ts | 6 +++- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/docker/boltz/Dockerfile b/docker/boltz/Dockerfile index 2d3f527d..d6a7abf0 100644 --- a/docker/boltz/Dockerfile +++ b/docker/boltz/Dockerfile @@ -27,6 +27,14 @@ RUN npm run compile FROM node:${NODE_VERSION} AS final +RUN apt-get update && apt-get -y upgrade && \ + apt-get -y install gnupg2 wget lsb-release && \ + sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \ + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ + apt-get update && \ + apt-get -y install postgresql-client-14 postgresql-client-common && \ + apt-get clean all && rm -rf /var/lib/apt/lists/* + COPY --from=builder /boltz-backend/bin /boltz-backend/bin COPY --from=builder /boltz-backend/dist /boltz-backend/dist COPY --from=builder /boltz-backend/node_modules /boltz-backend/node_modules diff --git a/lib/Boltz.ts b/lib/Boltz.ts index dc8a84a7..ed101256 100644 --- a/lib/Boltz.ts +++ b/lib/Boltz.ts @@ -124,6 +124,7 @@ class Boltz { this.backup = new BackupScheduler( this.logger, this.config.dbpath, + this.config.postgres, this.config.backup, this.service.eventHandler, ); diff --git a/lib/backup/BackupScheduler.ts b/lib/backup/BackupScheduler.ts index 3ea924e4..1aa7b029 100644 --- a/lib/backup/BackupScheduler.ts +++ b/lib/backup/BackupScheduler.ts @@ -1,11 +1,14 @@ +import { unlinkSync } from 'fs'; +import { exec } from 'child_process'; import { scheduleJob } from 'node-schedule'; import Errors from './Errors'; import Logger from '../Logger'; import { formatError } from '../Utils'; import Webdav from './providers/Webdav'; -import { BackupConfig } from '../Config'; import GoogleCloud from './providers/GoogleCloud'; import EventHandler from '../service/EventHandler'; +import Database, { DatabaseType } from '../db/Database'; +import { BackupConfig, PostgresConfig } from '../Config'; interface BackupProvider { uploadString(path: string, data: string): Promise; @@ -16,10 +19,11 @@ class BackupScheduler { private readonly providers: BackupProvider[] = []; constructor( - private logger: Logger, - private dbpath: string, - private config: BackupConfig, - private eventHandler: EventHandler, + private readonly logger: Logger, + private readonly dbpath: string, + private readonly postgresConfig: PostgresConfig | undefined, + private readonly config: BackupConfig, + private readonly eventHandler: EventHandler, ) { try { if (config.gcloud && GoogleCloud.configValid(config.gcloud)) { @@ -90,9 +94,36 @@ class BackupScheduler { } const dateString = BackupScheduler.getDate(date); - this.logger.silly(`Backing up databases at: ${dateString}`); + this.logger.silly(`Backing up ${Database.type} database at: ${dateString}`); - await this.uploadFile(`backend/database-${dateString}.db`, this.dbpath); + const backupPath = `backend/database-${dateString}.${Database.type === DatabaseType.SQLite ? 'db' : 'sql.gz'}`; + + if (Database.type === DatabaseType.SQLite) { + await this.uploadFile(backupPath, this.dbpath); + } else { + const tempFilePath = `sql-backup-${Date.now().toString()}.temp`; + + return new Promise((resolve, reject) => { + const backupChild = exec( + `PGPASSWORD="${this.postgresConfig!.password}" pg_dump -U ${this.postgresConfig!.username} -h ${this.postgresConfig!.host} -p ${this.postgresConfig!.port} -d ${this.postgresConfig!.database} | gzip > ${tempFilePath}`, + ); + backupChild.on('exit', (code) => { + if (code !== 0) { + reject( + `creating ${DatabaseType.PostgreSQL} backup failed with code: ${code}`, + ); + return; + } + + this.uploadFile(backupPath, tempFilePath) + .then(() => { + unlinkSync(tempFilePath); + resolve(); + }) + .catch(reject); + }); + }); + } }; private uploadFile = async (path: string, file: string) => { diff --git a/test/unit/backup/BackupScheduler.spec.ts b/test/unit/backup/BackupScheduler.spec.ts index b5a0582c..85087897 100644 --- a/test/unit/backup/BackupScheduler.spec.ts +++ b/test/unit/backup/BackupScheduler.spec.ts @@ -1,8 +1,9 @@ import Logger from '../../../lib/Logger'; import { BackupConfig } from '../../../lib/Config'; +import Webdav from '../../../lib/backup/providers/Webdav'; import EventHandler from '../../../lib/service/EventHandler'; +import Database, { DatabaseType } from '../../../lib/db/Database'; import BackupScheduler from '../../../lib/backup/BackupScheduler'; -import Webdav from '../../../lib/backup/providers/Webdav'; type callback = (currency: string, channelBackup: string) => void; @@ -71,11 +72,13 @@ describe('BackupScheduler', () => { const backupScheduler = new BackupScheduler( mockedLogger(), dbPath, + undefined, backupConfig, eventHandler, ); beforeAll(() => { + Database.type = DatabaseType.SQLite; backupScheduler['providers'].push(mockedWebdav()); }); @@ -147,6 +150,7 @@ describe('BackupScheduler', () => { new BackupScheduler( mockedLogger(), dbPath, + undefined, { interval: '0 0 * * *',