feat: backup databases

This commit is contained in:
Andras Bacsai
2023-07-18 14:36:54 +02:00
parent b2ffd9183b
commit b63dfb4bcd
5 changed files with 110 additions and 5 deletions

View File

@@ -1,6 +1,5 @@
import { exec } from 'node:child_process';
import util from 'util';
import fs from 'fs/promises';
import fsNormal from 'fs';
import yaml from 'js-yaml';
import forge from 'node-forge';
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
@@ -17,6 +16,7 @@ import { day } from './dayjs';
import { saveBuildLog } from './buildPacks/common';
import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa';
import { FastifyReply } from 'fastify';
export const version = '3.12.34';
export const isDev = process.env.NODE_ENV === 'development';
@@ -1942,3 +1942,51 @@ export function generateSecrets(
}
return envs;
}
export async function backupPostgresqlDatabase(database, reply) {
const backupFolder = '/tmp'
const fileName = `${database.id}-${new Date().getTime()}.gz`
const backupFileName = `${backupFolder}/${fileName}`
console.log({ database })
let command = null
switch (database?.type) {
case 'postgresql':
command = `docker exec ${database.id} sh -c "PGPASSWORD=${database.rootUserPassword} pg_dumpall -U postgres | gzip > ${backupFileName}"`
break;
case 'mongodb':
command = `docker exec ${database.id} sh -c "mongodump --archive=${backupFileName} --gzip --username=${database.rootUser} --password=${database.rootUserPassword}"`
break;
case 'mysql':
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
break;
case 'mariadb':
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
break;
case 'couchdb':
command = `docker exec ${database.id} sh -c "tar -czvf ${backupFileName} /bitnami/couchdb/data"`
break;
default:
return;
}
await executeCommand({
dockerId: database.destinationDockerId,
command,
});
const copyCommand = `docker cp ${database.id}:${backupFileName} ${backupFileName}`
await executeCommand({
dockerId: database.destinationDockerId,
command: copyCommand
});
if (isDev) {
await executeCommand({
dockerId: database.destinationDockerId,
command: `docker cp ${database.id}:${backupFileName} /app/backups/`
});
}
const stream = fsNormal.createReadStream(backupFileName);
reply.header('Content-Type', 'application/octet-stream');
reply.header('Content-Disposition', `attachment; filename=${fileName}`);
reply.header('Content-Length', fsNormal.statSync(backupFileName).size);
reply.header('Content-Transfer-Encoding', 'binary');
return reply.send(stream)
}

View File

@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
import fs from 'fs/promises';
import {
ComposeFile,
backupPostgresqlDatabase,
createDirectories,
decrypt,
defaultComposeConfiguration,
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
return errorHandler({ status, message });
}
}
export async function backupDatabase(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
return await backupPostgresqlDatabase(database, reply);
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
try {
const teamId = request.user.teamId;

View File

@@ -1,5 +1,5 @@
import { FastifyPluginAsync } from 'fastify';
import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
import { backupDatabase, cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
import type { OnlyId } from '../../../../types';
@@ -39,6 +39,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
};
export default root;