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;};
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;
};