Files
dokploy/packages/server/src/utils/volume-backups/restore.ts
2025-07-15 14:22:37 +09:00

127 lines
4.1 KiB
TypeScript

import path from "node:path";
import {
findApplicationById,
findComposeById,
findDestinationById,
getS3Credentials,
paths,
} from "../..";
export const restoreVolume = async (
id: string,
destinationId: string,
volumeName: string,
backupFileName: string,
serverId: string,
serviceType: "application" | "compose",
) => {
const destination = await findDestinationById(destinationId);
const { VOLUME_BACKUPS_PATH } = paths(!!serverId);
const volumeBackupPath = path.join(VOLUME_BACKUPS_PATH, volumeName);
const rcloneFlags = getS3Credentials(destination);
const bucketPath = `:s3:${destination.bucket}`;
const backupPath = `${bucketPath}/${backupFileName}`;
// Command to download backup file from S3
const downloadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${backupPath}" "${volumeBackupPath}/${backupFileName}"`;
// Base restore command that creates the volume and restores data
const baseRestoreCommand = `
set -e
echo "Volume name: ${volumeName}"
echo "Backup file name: ${backupFileName}"
echo "Volume backup path: ${volumeBackupPath}"
echo "Downloading backup from S3..."
mkdir -p ${volumeBackupPath}
${downloadCommand}
echo "Download completed ✅"
echo "Creating new volume and restoring data..."
docker run --rm \
-v ${volumeName}:/volume_data \
-v ${volumeBackupPath}:/backup \
ubuntu \
bash -c "cd /volume_data && tar xvf /backup/${backupFileName} ."
echo "Volume restore completed ✅"
`;
// Function to check if volume exists and get containers using it
const checkVolumeCommand = `
# Check if volume exists
VOLUME_EXISTS=$(docker volume ls -q --filter name="^${volumeName}$" | wc -l)
echo "Volume exists: $VOLUME_EXISTS"
if [ "$VOLUME_EXISTS" = "0" ]; then
echo "Volume doesn't exist, proceeding with direct restore"
${baseRestoreCommand}
else
echo "Volume exists, checking for containers using it (including stopped ones)..."
# Get ALL containers (running and stopped) using this volume - much simpler with native filter!
CONTAINERS_USING_VOLUME=$(docker ps -a --filter "volume=${volumeName}" --format "{{.ID}}|{{.Names}}|{{.State}}|{{.Labels}}")
if [ -z "$CONTAINERS_USING_VOLUME" ]; then
echo "Volume exists but no containers are using it"
echo "Removing existing volume and proceeding with restore"
docker volume rm ${volumeName} --force
${baseRestoreCommand}
else
echo ""
echo "⚠️ WARNING: Cannot restore volume as it is currently in use!"
echo ""
echo "📋 The following containers are using volume '${volumeName}':"
echo ""
echo "$CONTAINERS_USING_VOLUME" | while IFS='|' read container_id container_name container_state labels; do
echo " 🐳 Container: $container_name ($container_id)"
echo " Status: $container_state"
# Determine container type
if echo "$labels" | grep -q "com.docker.swarm.service.name="; then
SERVICE_NAME=$(echo "$labels" | grep -o "com.docker.swarm.service.name=[^,]*" | cut -d'=' -f2)
echo " Type: Docker Swarm Service ($SERVICE_NAME)"
elif echo "$labels" | grep -q "com.docker.compose.project="; then
PROJECT_NAME=$(echo "$labels" | grep -o "com.docker.compose.project=[^,]*" | cut -d'=' -f2)
echo " Type: Docker Compose ($PROJECT_NAME)"
else
echo " Type: Regular Container"
fi
echo ""
done
echo ""
echo "🔧 To restore this volume, please:"
echo " 1. Stop all containers/services using this volume"
echo " 2. Remove the existing volume: docker volume rm ${volumeName}"
echo " 3. Run the restore operation again"
echo ""
echo "❌ Volume restore aborted - volume is in use"
exit 1
fi
fi
`;
if (serviceType === "application") {
const application = await findApplicationById(id);
return `
echo "=== VOLUME RESTORE FOR APPLICATION ==="
echo "Application: ${application.appName}"
${checkVolumeCommand}
`;
}
if (serviceType === "compose") {
const compose = await findComposeById(id);
return `
echo "=== VOLUME RESTORE FOR COMPOSE ==="
echo "Compose: ${compose.appName}"
echo "Compose Type: ${compose.composeType}"
${checkVolumeCommand}
`;
}
// Fallback for unknown service types
return checkVolumeCommand;
};