#!/bin/bash # ----------------------------------------------------------------------------------------------- # --------------------------------- Container Data Export -------------------------------------- # ----------------------------------------------------------------------------------------------- # Exports a container's appdata directory to a compressed tar archive. # Stops the container before archiving and restarts it after — ensures clean consistent backup. # # Usage: # container_data_export.sh ContainerName /path/to/appdata /path/to/output/dir # container_data_export.sh Emby /mnt/media-servers/Media_Server/Emby /mnt/user/Backups/ # # Output file naming: # ContainerName_YYYY-MM-DD_HH-MM.tar.gz # # Use before major container updates, pool migrations, or when archiving # a container you are removing from the stack. # # Supports --dry-run to show what would be archived without making changes. # ----------------------------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../Master.conf" source "$SCRIPT_DIR/../common.sh" parse_args "$@" # ----------------------------------------------------------------------------------------------- # Args # ----------------------------------------------------------------------------------------------- CONTAINER_NAME="${PARSED_ARGS[0]:-}" APPDATA_PATH="${PARSED_ARGS[1]:-}" OUTPUT_DIR="${PARSED_ARGS[2]:-}" # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_GEAR Setup ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_GEAR Setup ━━━" if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi success "Running as root" if [[ -z "$CONTAINER_NAME" || -z "$APPDATA_PATH" || -z "$OUTPUT_DIR" ]]; then error "Usage: container_data_export.sh " error "Example: container_data_export.sh Emby /mnt/media-servers/Media_Server/Emby /mnt/user/Backups/" exit 1 fi if [[ ! -d "$APPDATA_PATH" ]]; then error "Appdata path not found: $APPDATA_PATH" exit 1 fi if [[ ! -d "$OUTPUT_DIR" ]]; then error "Output directory not found: $OUTPUT_DIR" exit 1 fi # Check free space — rough estimate: appdata size × 1.1 APPDATA_SIZE_KB=$(du -sk "$APPDATA_PATH" 2>/dev/null | cut -f1) OUTPUT_FREE_KB=$(df "$OUTPUT_DIR" --output=avail | tail -1 | tr -d ' ') REQUIRED_KB=$(( APPDATA_SIZE_KB * 11 / 10 )) APPDATA_SIZE_H=$(du -sh "$APPDATA_PATH" 2>/dev/null | cut -f1) OUTPUT_FREE_H=$(df -h "$OUTPUT_DIR" --output=avail | tail -1 | tr -d ' ') info "Container: $CONTAINER_NAME" info "Appdata: $APPDATA_PATH ($APPDATA_SIZE_H)" info "Output dir: $OUTPUT_DIR ($OUTPUT_FREE_H free)" if [[ "$OUTPUT_FREE_KB" -lt "$REQUIRED_KB" ]]; then error "Insufficient space in $OUTPUT_DIR — need ~${APPDATA_SIZE_H}, have ${OUTPUT_FREE_H}" exit 1 fi success "Space check passed" [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_STOP Stop Container ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_STOP Stop Container ━━━" CONTAINER_WAS_RUNNING=false STATUS=$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null) if [[ "$STATUS" == "true" ]]; then CONTAINER_WAS_RUNNING=true info "$ICON_STOP Stopping $CONTAINER_NAME for clean export..." if [[ "$DRY_RUN" == false ]]; then docker stop "$CONTAINER_NAME" >/dev/null 2>&1 && \ success "$ICON_STOPPED $CONTAINER_NAME stopped" || \ { error "Failed to stop $CONTAINER_NAME"; exit 1; } else warn "DRY RUN — would stop $CONTAINER_NAME" fi else info "$CONTAINER_NAME is not running — archiving as-is" fi # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_SYNC Archive ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_SYNC Archive ━━━" TIMESTAMP=$(date '+%Y-%m-%d_%H-%M') ARCHIVE_NAME="${CONTAINER_NAME}_${TIMESTAMP}.tar.gz" ARCHIVE_PATH="${OUTPUT_DIR}/${ARCHIVE_NAME}" info "Creating: $ARCHIVE_PATH" START=$(date +%s) if [[ "$DRY_RUN" == false ]]; then tar -czf "$ARCHIVE_PATH" -C "$(dirname "$APPDATA_PATH")" "$(basename "$APPDATA_PATH")" 2>/dev/null TAR_EXIT=$? if [[ "$TAR_EXIT" -ne 0 ]]; then error "Archive failed (exit code $TAR_EXIT)" # Restart container before exiting [[ "$CONTAINER_WAS_RUNNING" == true ]] && docker start "$CONTAINER_NAME" >/dev/null 2>&1 exit 1 fi ARCHIVE_SIZE=$(du -sh "$ARCHIVE_PATH" 2>/dev/null | cut -f1) success "Archive created: $ARCHIVE_NAME ($ARCHIVE_SIZE)" else warn "DRY RUN — would create: $ARCHIVE_PATH" fi END=$(date +%s) # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_START Restart Container ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_START Restart Container ━━━" if [[ "$CONTAINER_WAS_RUNNING" == true ]]; then info "$ICON_START Restarting $CONTAINER_NAME..." if [[ "$DRY_RUN" == false ]]; then docker start "$CONTAINER_NAME" >/dev/null 2>&1 && \ success "$ICON_STARTED $CONTAINER_NAME restarted" || \ error "Failed to restart $CONTAINER_NAME — start it manually" else warn "DRY RUN — would restart $CONTAINER_NAME" fi else info "$CONTAINER_NAME was not running — not restarting" fi # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_SUMMARY Summary ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━━━ $ICON_SUMMARY CONTAINER EXPORT SUMMARY ━━━━━" echo "$ICON_CONTAINERS Container: $CONTAINER_NAME" echo "$ICON_DISK Appdata: $APPDATA_PATH ($APPDATA_SIZE_H)" echo "$ICON_SYNC Archive: ${ARCHIVE_NAME:-DRY RUN} ${ARCHIVE_SIZE:+($ARCHIVE_SIZE)}" echo "$ICON_TIME Duration: $(format_duration $((END - START)))" if [[ "$DRY_RUN" == true ]]; then echo "$ICON_WARN Status: DRY RUN — no changes made" else echo "$ICON_DONE Status: $ICON_SUCCESS DONE" notify "Container export complete — $CONTAINER_NAME archived to $ARCHIVE_NAME" "Container Export" "normal" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"