#!/usr/bin/env bash # Export CaseGambling database (+ uploads) FROM Docker (production). # Run this on the prod server next to docker-compose.yml. # # Usage: # ./scripts/docker-db-export.sh # ./scripts/docker-db-export.sh ./backups/my-backup # # Output: # backups/casegambling-YYYYMMDD-HHMMSS/ # prod.db (+ optional -wal/-shm) # uploads/ # manifest.txt # backups/casegambling-YYYYMMDD-HHMMSS.tar.gz # # Then copy the .tar.gz to your machine and import locally: # ./scripts/local-db-import.sh ./backups/casegambling-….tar.gz set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" if [[ -f "$SCRIPT_DIR/../docker-compose.yml" ]]; then ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" elif [[ -f "$SCRIPT_DIR/docker-compose.yml" ]]; then ROOT="$SCRIPT_DIR" else echo "Error: docker-compose.yml not found near $SCRIPT_DIR" >&2 exit 1 fi cd "$ROOT" ENV_FILE="${ENV_FILE:-.env.prod}" COMPOSE=(docker compose) if [[ -f "$ENV_FILE" ]]; then COMPOSE=(docker compose --env-file "$ENV_FILE") fi SERVICE="${SERVICE:-casegambling}" STAMP="$(date +%Y%m%d-%H%M%S)" OUT="${1:-$ROOT/backups/casegambling-$STAMP}" if ! command -v docker >/dev/null 2>&1; then echo "Error: docker not found" >&2 exit 1 fi if ! "${COMPOSE[@]}" ps -q "$SERVICE" >/dev/null 2>&1; then echo "Error: compose service '$SERVICE' not found" >&2 echo "Tip: run from the folder that has docker-compose.yml" >&2 exit 1 fi mkdir -p "$OUT" echo "==> Exporting Docker database → $OUT/prod.db" echo " service: $SERVICE · volume path: /data/prod.db" TMP_UPLOADS="$(mktemp -d)" cleanup() { rm -rf "$TMP_UPLOADS" } trap cleanup EXIT WAS_RUNNING=0 if [[ -n "$("${COMPOSE[@]}" ps -q --status running "$SERVICE" 2>/dev/null || true)" ]]; then WAS_RUNNING=1 fi # Prefer a consistent SQLite snapshot inside a running container if [[ "$WAS_RUNNING" -eq 1 ]] \ && "${COMPOSE[@]}" exec -T "$SERVICE" sh -c 'command -v sqlite3 >/dev/null && test -f /data/prod.db'; then "${COMPOSE[@]}" exec -T "$SERVICE" sh -c "sqlite3 /data/prod.db \".backup '/tmp/prod-export.db'\"" "${COMPOSE[@]}" cp "$SERVICE:/tmp/prod-export.db" "$OUT/prod.db" "${COMPOSE[@]}" exec -T "$SERVICE" rm -f /tmp/prod-export.db >/dev/null 2>&1 || true else # Safe file copy: stop if running so WAL is flushed if [[ "$WAS_RUNNING" -eq 1 ]]; then echo "==> Stopping $SERVICE for a consistent copy" "${COMPOSE[@]}" stop "$SERVICE" >/dev/null fi "${COMPOSE[@]}" cp "$SERVICE:/data/prod.db" "$OUT/prod.db" "${COMPOSE[@]}" cp "$SERVICE:/data/prod.db-wal" "$OUT/prod.db-wal" 2>/dev/null || true "${COMPOSE[@]}" cp "$SERVICE:/data/prod.db-shm" "$OUT/prod.db-shm" 2>/dev/null || true if [[ "$WAS_RUNNING" -eq 1 ]]; then echo "==> Starting $SERVICE" "${COMPOSE[@]}" start "$SERVICE" >/dev/null 2>&1 || "${COMPOSE[@]}" up -d "$SERVICE" >/dev/null fi fi # Also write as dev.db so local import can use either name cp -f "$OUT/prod.db" "$OUT/dev.db" [[ -f "$OUT/prod.db-wal" ]] && cp -f "$OUT/prod.db-wal" "$OUT/dev.db-wal" [[ -f "$OUT/prod.db-shm" ]] && cp -f "$OUT/prod.db-shm" "$OUT/dev.db-shm" echo "==> Exporting Docker uploads → $OUT/uploads" mkdir -p "$OUT/uploads" if "${COMPOSE[@]}" cp "$SERVICE:/app/uploads/." "$TMP_UPLOADS/" 2>/dev/null; then cp -a "$TMP_UPLOADS"/. "$OUT/uploads/" 2>/dev/null || true else echo " (no uploads or empty volume)" fi { echo "exported_at=$STAMP" echo "source=docker" echo "service=$SERVICE" echo "database=prod.db" echo "uploads=uploads/" } > "$OUT/manifest.txt" TAR="$OUT.tar.gz" tar -czf "$TAR" -C "$(dirname "$OUT")" "$(basename "$OUT")" DB_SIZE="$(du -h "$OUT/prod.db" | awk '{print $1}')" UPLOAD_COUNT="$(find "$OUT/uploads" -type f 2>/dev/null | wc -l | tr -d ' ')" echo "==> Archive: $TAR" echo " db size: $DB_SIZE · uploads files: $UPLOAD_COUNT" echo "==> Done. On your laptop:" echo " scp user@prod:$TAR ./backups/" echo " ./scripts/local-db-import.sh ./backups/$(basename "$TAR")"