135 lines · 3.9 KB · TypeScript
RawDownload
import { quote } from "shell-quote";import {	getComposeContainerCommand,	getServiceContainerCommand,} from "../backups/utils"; // User-controlled values are passed to the container via `docker exec -e` and// read as "$VAR" inside a single-quoted inner script, so they never enter the// inner command text. See the matching note in backups/utils.ts.export const getPostgresRestoreCommand = (	database: string,	databaseUser: string,) => {	return `docker exec -e DB_NAME=${quote([database])} -e DB_USER=${quote([databaseUser])} -i $CONTAINER_ID sh -c 'pg_restore -U "$DB_USER" -d "$DB_NAME" -O --clean --if-exists'`;}; export const getMariadbRestoreCommand = (	database: string,	databaseUser: string,	databasePassword: string,) => {	return `docker exec -e DB_NAME=${quote([database])} -e DB_USER=${quote([databaseUser])} -e DB_PASS=${quote([databasePassword])} -i $CONTAINER_ID sh -c 'mariadb -u "$DB_USER" -p"$DB_PASS" "$DB_NAME"'`;}; export const getMysqlRestoreCommand = (	database: string,	databasePassword: string,) => {	return `docker exec -e DB_NAME=${quote([database])} -e DB_PASS=${quote([databasePassword])} -i $CONTAINER_ID sh -c 'mysql -u root -p"$DB_PASS" "$DB_NAME"'`;}; export const getMongoRestoreCommand = (	database: string,	databaseUser: string,	databasePassword: string,) => {	return `docker exec -e DB_NAME=${quote([database])} -e DB_USER=${quote([databaseUser])} -e DB_PASS=${quote([databasePassword])} -i $CONTAINER_ID sh -c 'mongorestore --username "$DB_USER" --password "$DB_PASS" --authenticationDatabase admin --db "$DB_NAME" --archive --drop'`;}; export const getComposeSearchCommand = (	appName: string,	type: "stack" | "docker-compose" | "database",	serviceName?: string,) => {	if (type === "database") {		return getServiceContainerCommand(appName || "");	}	return getComposeContainerCommand(appName || "", serviceName || "", type);}; interface DatabaseCredentials {	database: string;	databaseUser?: string;	databasePassword?: string;} const generateRestoreCommand = (	type: "postgres" | "mariadb" | "mysql" | "mongo",	credentials: DatabaseCredentials,) => {	const { database, databaseUser, databasePassword } = credentials;	switch (type) {		case "postgres":			return getPostgresRestoreCommand(database, databaseUser || "");		case "mariadb":			return getMariadbRestoreCommand(				database,				databaseUser || "",				databasePassword || "",			);		case "mysql":			return getMysqlRestoreCommand(database, databasePassword || "");		case "mongo":			return getMongoRestoreCommand(				database,				databaseUser || "",				databasePassword || "",			);	}}; const getMongoSpecificCommand = (	rcloneCommand: string,	restoreCommand: string,	backupFile: string,): string => {	const tempDir = "/tmp/dokploy-restore";	const fileName = backupFile.split("/").pop() || "backup.sql.gz";	const decompressedName = fileName.replace(".gz", "");	return `rm -rf ${tempDir} && \mkdir -p ${tempDir} && \${rcloneCommand} ${tempDir} && \cd ${tempDir} && \gunzip -f "${fileName}" && \${restoreCommand} < "${decompressedName}" && \rm -rf ${tempDir}	`;}; interface RestoreOptions {	appName: string;	type: "postgres" | "mariadb" | "mysql" | "mongo";	restoreType: "stack" | "docker-compose" | "database";	credentials: DatabaseCredentials;	serviceName?: string;	rcloneCommand: string;	backupFile?: string;} export const getRestoreCommand = ({	appName,	type,	restoreType,	credentials,	serviceName,	rcloneCommand,	backupFile,}: RestoreOptions) => {	const containerSearch = getComposeSearchCommand(		appName,		restoreType,		serviceName,	);	const restoreCommand = generateRestoreCommand(type, credentials);	let cmd = `CONTAINER_ID=$(${containerSearch})`; 	if (type !== "mongo") {		cmd += ` && ${rcloneCommand} | ${restoreCommand}`;	} else {		cmd += ` && ${getMongoSpecificCommand(rcloneCommand, restoreCommand, backupFile || "")}`;	} 	return cmd;};