#!/usr/bin/env bash # Import SQLite DB (+ uploads) into the LOCAL project # (server/prisma/dev.db + server/uploads/). # # Usage: # ./scripts/local-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS # ./scripts/local-db-import.sh ./backups/casegambling-YYYYMMDD-HHMMSS.tar.gz # ./scripts/local-db-import.sh ./backups/….tar.gz # player-only merge # # Typical flow (prod → laptop): # # on prod: # ./scripts/docker-db-export.sh # # copy the .tar.gz here, then: # ./scripts/local-db-import.sh ./backups/casegambling-….tar.gz # # or only one player into the existing local DB: # ./scripts/local-db-import.sh ./backups/casegambling-….tar.gz SomePlayer # # Stop `npm run dev` before importing so SQLite is not locked. set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" MERGE_PLAYER_SCRIPT="$ROOT/scripts/merge-player-db.sh" if [[ -z "${1:-}" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then echo "Usage: $0 [username]" >&2 echo "Example: $0 ./backups/casegambling-20260116-203000.tar.gz" >&2 echo " $0 ./backups/casegambling-20260116-203000.tar.gz SomePlayer" >&2 echo "Create a backup from Docker: ./scripts/docker-db-export.sh" >&2 exit 1 fi BACKUP_ARG="$1" USERNAME="${2:-}" # Resolve backup path from the caller's cwd if [[ "$BACKUP_ARG" = /* ]]; then SRC="$BACKUP_ARG" else SRC="$(pwd)/$BACKUP_ARG" fi LOCAL_DB="${LOCAL_DB:-$ROOT/server/prisma/dev.db}" LOCAL_UPLOADS="${LOCAL_UPLOADS:-$ROOT/server/uploads}" TMP="" cleanup() { if [[ -n "$TMP" && -d "$TMP" ]]; then rm -rf "$TMP" fi } trap cleanup EXIT is_archive() { case "$1" in *.tar.gz|*.tgz) return 0 ;; *) return 1 ;; esac } copy_player_avatar() { local avatar_url="$1" local uploads_src="$2" local uploads_dst="$3" [[ -z "$avatar_url" || ! -d "$uploads_src" ]] && return 0 local rel="${avatar_url#/}" rel="${rel#uploads/}" rel="${rel#./}" case "$rel" in ''|http:*|https:*|*..*) return 0 ;; esac local src_file="$uploads_src/$rel" if [[ -f "$src_file" ]]; then mkdir -p "$uploads_dst/$(dirname "$rel")" cp -f "$src_file" "$uploads_dst/$rel" echo "==> Copied avatar upload: $rel" fi } if [[ -f "$SRC" ]] && is_archive "$SRC"; then echo "==> Extracting $(basename "$SRC")" TMP="$(mktemp -d)" tar -xzf "$SRC" -C "$TMP" mapfile -t DIRS < <(find "$TMP" -mindepth 1 -maxdepth 1 -type d | sort) if [[ ${#DIRS[@]} -ge 1 ]]; then SRC="${DIRS[0]}" else SRC="$TMP" fi elif [[ -f "$SRC" ]]; then echo "Error: not a directory or .tar.gz archive: $SRC" >&2 exit 1 fi if [[ ! -d "$SRC" ]]; then echo "Error: backup not found: $SRC" >&2 echo "Pass a folder or a .tar.gz from docker-db-export.sh / local-db-export.sh" >&2 exit 1 fi DB="" for candidate in "$SRC/dev.db" "$SRC/prod.db"; do if [[ -f "$candidate" ]]; then DB="$candidate" break fi done if [[ -z "$DB" ]]; then echo "Error: no dev.db or prod.db in $SRC" >&2 ls -la "$SRC" >&2 || true exit 1 fi # ── Player-only merge into existing local DB ───────────────────────────────── if [[ -n "$USERNAME" ]]; then if [[ ! -f "$LOCAL_DB" ]]; then echo "Error: local DB not found: $LOCAL_DB" >&2 echo "Tip: run a full import first (without username), or start the app once." >&2 exit 1 fi if [[ ! -x "$MERGE_PLAYER_SCRIPT" ]]; then echo "Error: merge helper missing or not executable: $MERGE_PLAYER_SCRIPT" >&2 exit 1 fi # Drop WAL/SHM so we merge against the real file contents rm -f "${LOCAL_DB}-wal" "${LOCAL_DB}-shm" "$MERGE_PLAYER_SCRIPT" "$DB" "$LOCAL_DB" "$USERNAME" AVATAR_URL="$(sqlite3 "$DB" "SELECT avatarUrl FROM User WHERE username = '${USERNAME//\'/\'\'}' LIMIT 1;" 2>/dev/null || true)" if [[ -d "$SRC/uploads" ]]; then copy_player_avatar "${AVATAR_URL:-}" "$SRC/uploads" "$LOCAL_UPLOADS" fi echo "==> Done (player-only)." echo " db: $LOCAL_DB" echo " Tip: stop npm run dev before import if the DB was locked; then restart:" echo " npm run dev" exit 0 fi # ── Full import (replace local DB + uploads) ───────────────────────────────── mkdir -p "$(dirname "$LOCAL_DB")" echo "==> Importing database → $LOCAL_DB" echo " source: $DB" # Drop local WAL/SHM so the new file is the sole source of truth rm -f "${LOCAL_DB}-wal" "${LOCAL_DB}-shm" if command -v sqlite3 >/dev/null 2>&1; then # Normalize into a clean single-file DB sqlite3 "$DB" ".backup '$LOCAL_DB'" else cp -f "$DB" "$LOCAL_DB" WAL="" SHM="" [[ -f "$SRC/dev.db-wal" ]] && WAL="$SRC/dev.db-wal" [[ -z "$WAL" && -f "$SRC/prod.db-wal" ]] && WAL="$SRC/prod.db-wal" [[ -f "$SRC/dev.db-shm" ]] && SHM="$SRC/dev.db-shm" [[ -z "$SHM" && -f "$SRC/prod.db-shm" ]] && SHM="$SRC/prod.db-shm" [[ -n "$WAL" ]] && cp -f "$WAL" "${LOCAL_DB}-wal" [[ -n "$SHM" ]] && cp -f "$SHM" "${LOCAL_DB}-shm" fi if [[ -d "$SRC/uploads" ]]; then echo "==> Importing uploads → $LOCAL_UPLOADS" mkdir -p "$LOCAL_UPLOADS" # Keep .gitkeep if present; replace other files find "$LOCAL_UPLOADS" -mindepth 1 ! -name '.gitkeep' -exec rm -rf {} + 2>/dev/null || true cp -a "$SRC/uploads"/. "$LOCAL_UPLOADS/" 2>/dev/null || true fi DB_SIZE="$(du -h "$LOCAL_DB" | awk '{print $1}')" UPLOAD_COUNT="$(find "$LOCAL_UPLOADS" -type f ! -name '.gitkeep' 2>/dev/null | wc -l | tr -d ' ')" echo "==> Done." echo " db: $LOCAL_DB ($DB_SIZE)" echo " uploads files: $UPLOAD_COUNT" echo " Tip: stop npm run dev before import if the DB was locked; then restart:" echo " npm run db:push && npm run dev"