185 lines
5.0 KiB
Bash
Executable File
185 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SRC="${SRC:-/mnt/replicas}"
|
|
DEST="${DEST:-/mnt2/homey-backup}"
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
MANIFEST="$DEST/manifest.json"
|
|
|
|
PVC_MAPPING=(
|
|
"pvc-0310a337-9642-464b-a458-fcb3439328e7-fbc07d5a:ldap-pvc"
|
|
"pvc-1cdc51ee-b965-4cab-baf7-077cc6df6f11-0fcfb9cd:authelia-pvc"
|
|
"pvc-4888bf84-62c8-4340-adbc-cb31073d8fd2-d065d20b:gitea-pvc"
|
|
"pvc-5c1f48e3-346f-4c35-8e6a-8fc0c4c3a842-96d72815:nextcloud-data-pvc"
|
|
"pvc-c5b28179-1b9c-462a-be5b-05c4f0bb36ca-5f2dbf4d:nextcloud-postgres-pvc"
|
|
"pvc-7f73ee94-5583-4e4a-9788-cba054214b1c-f767850a:radicale-pvc"
|
|
"pvc-9e75f35a-27c3-4251-b25a-1a876f82f6c7-c9c8b185:jellyfin-config-pvc"
|
|
"pvc-dfe2aa08-bbb8-423b-9001-fb6aea181597-baf06a7f:jellyfin-data-pvc"
|
|
"pvc-dd4a069a-a638-49c0-8c95-f954510816e5-7e81a6f6:transmission-config-pvc"
|
|
"pvc-e4ba414d-d9c2-4927-b0ae-f6bfb90ce311-a0963101:unknown-pvc-1"
|
|
"pvc-ec6afe10-aca3-42ce-9d89-32fc4ac77f9a-8d6baa34:unknown-pvc-2"
|
|
)
|
|
|
|
progress_bar() {
|
|
local current=$1
|
|
local total=$2
|
|
local width=40
|
|
local percent=$((current * 100 / total))
|
|
local filled=$((current * width / total))
|
|
local empty=$((width - filled))
|
|
printf "\r["
|
|
printf "%${filled}s" | tr ' ' '='
|
|
printf "%${empty}s" | tr ' ' ' '
|
|
printf "] %3d%% (%d/%d)" "$percent" "$current" "$total"
|
|
}
|
|
|
|
get_pvc_name() {
|
|
local pvc_id="$1"
|
|
for mapping in "${PVC_MAPPING[@]}"; do
|
|
if [[ "$mapping" == "$pvc_id:"* ]]; then
|
|
echo "${mapping#*:}"
|
|
return
|
|
fi
|
|
done
|
|
echo "unknown"
|
|
}
|
|
|
|
echo "========================================"
|
|
echo " Longhorn Volume Backup Tool"
|
|
echo "========================================"
|
|
echo ""
|
|
echo "Source: $SRC"
|
|
echo "Destination: $DEST"
|
|
echo "Timestamp: $TIMESTAMP"
|
|
echo ""
|
|
|
|
mkdir -p "$DEST/volumes"
|
|
mkdir -p "$DEST/metadata"
|
|
|
|
VOLUMES=()
|
|
TOTAL_SIZE=0
|
|
|
|
echo "Scanning volumes..."
|
|
for pvc_dir in "$SRC"/*/; do
|
|
pvc_name=$(basename "$pvc_dir")
|
|
friendly_name=$(get_pvc_name "$pvc_name")
|
|
VOLUMES+=("$pvc_name:$friendly_name")
|
|
|
|
size=$(sudo du -sb "$pvc_dir" 2>/dev/null | awk '{print $1}' || echo "0")
|
|
TOTAL_SIZE=$((TOTAL_SIZE + size))
|
|
|
|
printf " %-50s %s\n" "$friendly_name" "$(numfmt --to=iec-i --suffix=B "$size" 2>/dev/null || echo "${size}B")"
|
|
done
|
|
|
|
TOTAL_VOLUMES=${#VOLUMES[@]}
|
|
echo ""
|
|
echo "Found $TOTAL_VOLUMES volumes, total size: $(numfmt --to=iec-i --suffix=B "$TOTAL_SIZE")"
|
|
echo ""
|
|
read -p "Continue with backup? [y/N] " -n 1 -r
|
|
echo ""
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Aborted."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Starting backup..."
|
|
echo ""
|
|
|
|
COPIED_SIZE=0
|
|
START_TIME=$(date +%s)
|
|
|
|
for i in "${!VOLUMES[@]}"; do
|
|
volume="${VOLUMES[$i]}"
|
|
pvc_name="${volume%%:*}"
|
|
friendly_name="${volume#*:}"
|
|
|
|
CURRENT=$((i + 1))
|
|
progress_bar "$CURRENT" "$TOTAL_VOLUMES"
|
|
echo " - $friendly_name"
|
|
|
|
sudo rsync -a --no-owner --no-group --info=progress2 \
|
|
"$SRC/$pvc_name/" \
|
|
"$DEST/volumes/$pvc_name/" 2>&1 | while read -r line; do
|
|
if [[ "$line" =~ to-chk=*([0-9]+)/([0-9]+) ]]; then
|
|
printf "\r %s" "$line"
|
|
fi
|
|
done
|
|
|
|
sudo chown -R "$USER:$USER" "$DEST/volumes/$pvc_name" 2>/dev/null || true
|
|
|
|
if [[ -f "$SRC/$pvc_name/volume.meta" ]]; then
|
|
sudo cp "$SRC/$pvc_name/volume.meta" "$DEST/metadata/${pvc_name}.meta" 2>/dev/null || true
|
|
fi
|
|
|
|
echo ""
|
|
done
|
|
|
|
echo ""
|
|
echo "Generating manifest..."
|
|
|
|
cat > "$MANIFEST" << EOF
|
|
{
|
|
"backup_timestamp": "$TIMESTAMP",
|
|
"source_path": "$SRC",
|
|
"destination_path": "$DEST",
|
|
"total_volumes": $TOTAL_VOLUMES,
|
|
"total_size_bytes": $TOTAL_SIZE,
|
|
"volumes": [
|
|
EOF
|
|
|
|
FIRST=true
|
|
for volume in "${VOLUMES[@]}"; do
|
|
pvc_name="${volume%%:*}"
|
|
friendly_name="${volume#*:}"
|
|
|
|
vol_size=$(sudo du -sb "$SRC/$pvc_name" 2>/dev/null | awk '{print $1}' || echo "0")
|
|
vol_size_hr=$(numfmt --to=iec-i --suffix=B "$vol_size" 2>/dev/null || echo "${vol_size}B")
|
|
|
|
head_file=$(sudo find "$DEST/volumes/$pvc_name" -name "volume-head-*.img" 2>/dev/null | head -1)
|
|
head_file=$(basename "$head_file" 2>/dev/null || echo "")
|
|
|
|
if [[ "$FIRST" == "true" ]]; then
|
|
FIRST=false
|
|
else
|
|
echo "," >> "$MANIFEST"
|
|
fi
|
|
|
|
cat >> "$MANIFEST" << EOF
|
|
{
|
|
"pvc_id": "$pvc_name",
|
|
"friendly_name": "$friendly_name",
|
|
"size_bytes": $vol_size,
|
|
"size_human": "$vol_size_hr",
|
|
"volume_head": "$head_file",
|
|
"backup_path": "volumes/$pvc_name"
|
|
}
|
|
EOF
|
|
done
|
|
|
|
cat >> "$MANIFEST" << EOF
|
|
]
|
|
}
|
|
EOF
|
|
|
|
END_TIME=$(date +%s)
|
|
DURATION=$((END_TIME - START_TIME))
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo " Backup Complete!"
|
|
echo "========================================"
|
|
echo ""
|
|
echo "Duration: $((DURATION / 60))m $((DURATION % 60))s"
|
|
echo "Location: $DEST"
|
|
echo "Manifest: $MANIFEST"
|
|
echo ""
|
|
echo "Backup size:"
|
|
sudo du -sh "$DEST/volumes"
|
|
echo ""
|
|
echo "To mount a volume, run:"
|
|
echo " ./scripts/mount-longhorn-volume.sh <pvc-name-or-friendly-name>"
|
|
echo ""
|
|
echo "To restore a volume, run:"
|
|
echo " ./scripts/restore-longhorn-volume.sh <pvc-name-or-friendly-name>"
|