#!/bin/sh set -eu usage() { echo "usage: $0 --project-name NAME --input FILE --confirm-project NAME" >&2 exit 2 } project_name= input= confirm_project= while [ "$#" -gt 0 ]; do case "$1" in --project-name) project_name=${2-}; shift 2 ;; --input) input=${2-}; shift 2 ;; --confirm-project) confirm_project=${2-}; shift 2 ;; *) usage ;; esac done [ -n "$project_name" ] && [ -n "$input" ] && [ -n "$confirm_project" ] || usage [ "$confirm_project" = "$project_name" ] || { echo "restore confirmation must match --project-name exactly" >&2 exit 2 } [ -r "$input" ] || { echo "backup input is not readable" >&2; exit 2; } resolve_volume() { names=$(docker volume ls \ --filter "label=com.docker.compose.project=$project_name" \ --filter "label=com.docker.compose.volume=qdrant-data" \ --format '{{.Name}}') count=$(printf '%s\n' "$names" | sed '/^$/d' | wc -l | tr -d ' ') [ "$count" -eq 1 ] || { echo "expected exactly one qdrant-data volume for compose project $project_name" >&2 exit 2 } printf '%s\n' "$names" | sed -n '/./{p;q;}' } resolve_mountpoint() { mountpoint=$(docker volume inspect --format '{{ .Mountpoint }}' "$1") [ -n "$mountpoint" ] || { echo "docker did not return a qdrant-data mountpoint" >&2; exit 2; } case "$mountpoint" in /*) ;; *) echo "qdrant-data mountpoint is not absolute: $mountpoint" >&2; exit 2 ;; esac [ -d "$mountpoint" ] || { echo "qdrant-data mountpoint is not a directory: $mountpoint" >&2; exit 2; } printf '%s\n' "$mountpoint" } volume_name=$(resolve_volume) mountpoint=$(resolve_mountpoint "$volume_name") running_container=$(docker compose --project-name "$project_name" ps --status running -q qdrant) restart_qdrant=0 staging_dir= extract_dir= cleanup() { status=$? if [ "$status" -ne 0 ] && [ -n "${staging_dir:-}" ] && [ -d "${staging_dir:-}" ]; then find "$mountpoint" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + find "$staging_dir" -mindepth 1 -maxdepth 1 -exec mv {} "$mountpoint"/ \; fi if [ -n "${staging_dir:-}" ] && [ -d "${staging_dir:-}" ]; then rm -rf "$staging_dir" fi if [ -n "${extract_dir:-}" ] && [ -d "${extract_dir:-}" ]; then rm -rf "$extract_dir" fi if [ "$restart_qdrant" -eq 1 ]; then docker compose --project-name "$project_name" start qdrant >/dev/null fi exit "$status" } trap cleanup EXIT HUP INT TERM if [ -n "$running_container" ]; then docker compose --project-name "$project_name" stop qdrant >/dev/null restart_qdrant=1 fi parent_dir=$(dirname "$mountpoint") staging_dir=$(mktemp -d "$parent_dir/.qdrant-restore-staging.XXXXXX") extract_dir=$(mktemp -d "${TMPDIR:-/tmp}/qdrant-restore.XXXXXX") tar -C "$extract_dir" -xf "$input" find "$mountpoint" -mindepth 1 -maxdepth 1 -exec mv {} "$staging_dir"/ \; find "$extract_dir" -mindepth 1 -maxdepth 1 -exec mv {} "$mountpoint"/ \; rm -rf "$staging_dir" staging_dir= rm -rf "$extract_dir" extract_dir= echo "Qdrant restore completed into $volume_name for compose project $project_name"