Compare commits

...

3 Commits

Author SHA1 Message Date
Mauricio Siu ba9c21955a fix: use a temp file instead of process substitution for POSIX sh compat
The restore script runs under /bin/sh, not bash. Process substitution
("< <(...)") is a bashism dash doesn't support, so the previous fix
failed at runtime with a syntax error and the restore never ran.
Write STOPPED_CONTAINERS to a temp file and read the while loops from
that instead — same effect, works under plain POSIX sh.
2026-08-30 16:42:25 -06:00
Mauricio Siu 1805cb9531 fix: address review findings on volume restore container removal
- Only treat exited/created/dead containers as safely removable;
  paused/restarting/removing containers now still block the restore
  like a running one does.
- Re-check each container's live state right before removing it, to
  avoid deleting one that started running again after the initial
  docker ps -a snapshot.
- Stop swallowing docker rm/docker volume rm failures with '|| true';
  abort the restore instead of silently continuing and extracting
  into a volume that may still be attached to a container.
2026-08-30 16:14:57 -06:00
Mauricio Siu 931fa4a3c4 fix: remove stopped containers referencing a volume before restore
Volume restore aborted with "volume is in use" whenever a stopped
container still referenced the target volume, forcing users to SSH in
and remove it manually. Running containers still block the restore,
but stopped ones are now removed automatically before the volume is
recreated.

Fixes #3995
2026-08-30 15:53:46 -06:00
@@ -66,38 +66,98 @@ export const restoreVolume = async (
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
# Only "exited", "created" and "dead" are safe to remove. Anything else
# (running, restarting, paused, removing) is treated as still in use.
RUNNING_CONTAINERS=$(echo "$CONTAINERS_USING_VOLUME" | awk -F'|' '$3 != "exited" && $3 != "created" && $3 != "dead"')
STOPPED_CONTAINERS=$(echo "$CONTAINERS_USING_VOLUME" | awk -F'|' '$3 == "exited" || $3 == "created" || $3 == "dead"')
if [ -n "$RUNNING_CONTAINERS" ]; then
echo ""
done
echo "⚠️ WARNING: Cannot restore volume as it is currently in use!"
echo ""
echo "📋 The following containers are using volume '${volumeName}':"
echo ""
echo "$RUNNING_CONTAINERS" | 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
# Use a temp file instead of process substitution ("< <(...)") so this
# still works under a plain POSIX /bin/sh, not just bash.
STOPPED_CONTAINERS_FILE=$(mktemp)
printf '%s\\n' "$STOPPED_CONTAINERS" > "$STOPPED_CONTAINERS_FILE"
# Re-check each container's live state right before removing it: it may have
# started running again since the "docker ps -a" snapshot above.
ACTIVE_AGAIN=""
while IFS='|' read -r container_id container_name container_state labels; do
CURRENT_STATE=$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || echo "gone")
case "$CURRENT_STATE" in
running|restarting|paused|removing)
ACTIVE_AGAIN="$ACTIVE_AGAIN $container_name"
;;
esac
done < "$STOPPED_CONTAINERS_FILE"
if [ -n "$ACTIVE_AGAIN" ]; then
rm -f "$STOPPED_CONTAINERS_FILE"
echo ""
echo "⚠️ WARNING: Container(s)$ACTIVE_AGAIN became active again, aborting restore"
echo "❌ Volume restore aborted - volume is in use"
exit 1
fi
echo "Volume exists but is only referenced by stopped containers, removing them..."
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"
REMOVE_FAILED=""
while IFS='|' read -r container_id container_name container_state labels; do
echo " 🗑 Removing stopped container: $container_name ($container_id) [status: $container_state]"
if ! docker rm -f "$container_id" >/dev/null 2>&1; then
REMOVE_FAILED="$REMOVE_FAILED $container_name"
fi
done < "$STOPPED_CONTAINERS_FILE"
rm -f "$STOPPED_CONTAINERS_FILE"
if [ -n "$REMOVE_FAILED" ]; then
echo ""
echo "❌ Failed to remove container(s):$REMOVE_FAILED"
echo "❌ Volume restore aborted - could not free the volume"
exit 1
fi
echo ""
echo "❌ Volume restore aborted - volume is in use"
exit 1
echo "Removing existing volume and proceeding with restore"
if ! docker volume rm ${volumeName} --force; then
echo "❌ Volume restore aborted - failed to remove existing volume"
exit 1
fi
${baseRestoreCommand}
fi
fi
`;