#!/usr/bin/env bash # scripts/1_backup.sh — Step 1: Backup before Docker migration # Run this FIRST before any migration steps. set -euo pipefail REPO_DIR="/opt/sandbox-notebookllamalm-nextjs" BACKUP_BASE="/opt/backups" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="${BACKUP_BASE}/notebookllama_${TIMESTAMP}" POSTGRES_CONTAINER="sandbox-nextjs-postgres" VOLUME_PREFIX="sandbox-notebookllamalm-nextjs_" # Read postgres credentials from backend .env (keys: pgql_user, pgql_psw, pgql_db) _read_env() { grep -E "^${1}=" "${REPO_DIR}/backend/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'" ; } PG_USER=$(_read_env pgql_user) PG_PSW=$(_read_env pgql_psw) PG_DB=$(_read_env pgql_db) # ── Colors ────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } # ── Preflight ──────────────────────────────────────────────────────────────── info "Starting backup — ${TIMESTAMP}" if [[ ! -d "$REPO_DIR" ]]; then error "Repo directory not found: $REPO_DIR" exit 1 fi if ! docker info &>/dev/null; then error "Docker is not available" exit 1 fi sudo mkdir -p "$BACKUP_DIR" # Give current user ownership so subsequent writes don't need sudo sudo chown "$(id -u):$(id -g)" "$BACKUP_DIR" info "Backup directory: $BACKUP_DIR" # ── 1. PostgreSQL dump ──────────────────────────────────────────────────────── info "Dumping PostgreSQL..." if docker ps --format '{{.Names}}' | grep -q "^${POSTGRES_CONTAINER}$"; then PGPASSWORD="$PG_PSW" docker exec -e PGPASSWORD="$PG_PSW" "$POSTGRES_CONTAINER" \ pg_dump -U "$PG_USER" "$PG_DB" \ > "${BACKUP_DIR}/postgres_dump.sql" info " → postgres_dump.sql ($(du -sh "${BACKUP_DIR}/postgres_dump.sql" | cut -f1))" else warn "Postgres container '${POSTGRES_CONTAINER}' is not running — skipping SQL dump" fi # ── 2. Archive application files ───────────────────────────────────────────── info "Archiving application files..." # conversation_*.mp3 in backend root MP3_COUNT=$(find "${REPO_DIR}/backend" -maxdepth 1 -name 'conversation_*.mp3' 2>/dev/null | wc -l | tr -d ' ') if [[ "$MP3_COUNT" -gt 0 ]]; then info " → Found ${MP3_COUNT} MP3 file(s) in backend/" tar -czf "${BACKUP_DIR}/mp3_root.tar.gz" \ -C "${REPO_DIR}/backend" \ $(find "${REPO_DIR}/backend" -maxdepth 1 -name 'conversation_*.mp3' -printf '%f\n') info " → mp3_root.tar.gz ($(du -sh "${BACKUP_DIR}/mp3_root.tar.gz" | cut -f1))" else info " → No MP3 files in backend root (already moved or not yet generated)" fi # backend/conversations/ (may already exist) if [[ -d "${REPO_DIR}/backend/conversations" ]]; then tar -czf "${BACKUP_DIR}/conversations.tar.gz" \ -C "${REPO_DIR}/backend" conversations/ info " → conversations.tar.gz ($(du -sh "${BACKUP_DIR}/conversations.tar.gz" | cut -f1))" else info " → backend/conversations/ not found, skipping" fi # backend/failed_uploads/ if [[ -d "${REPO_DIR}/backend/failed_uploads" ]]; then tar -czf "${BACKUP_DIR}/failed_uploads.tar.gz" \ -C "${REPO_DIR}/backend" failed_uploads/ info " → failed_uploads.tar.gz ($(du -sh "${BACKUP_DIR}/failed_uploads.tar.gz" | cut -f1))" else info " → backend/failed_uploads/ not found, skipping" fi # .env files for envfile in \ "${REPO_DIR}/backend/.env" \ "${REPO_DIR}/frontend/.env.production" \ "${REPO_DIR}/.env"; do if [[ -f "$envfile" ]]; then rel="${envfile#${REPO_DIR}/}" dest="${BACKUP_DIR}/$(echo "$rel" | tr '/' '_')" cp "$envfile" "$dest" info " → $(basename "$dest") (env file)" fi done # ── 3. Docker volume backup (pgdata) ───────────────────────────────────────── PGDATA_VOLUME="${VOLUME_PREFIX}pgdata_nextjs" info "Backing up Docker volume: ${PGDATA_VOLUME}..." if docker volume inspect "$PGDATA_VOLUME" &>/dev/null; then docker run --rm \ -v "${PGDATA_VOLUME}:/source:ro" \ -v "${BACKUP_DIR}:/backup" \ alpine \ tar -czf /backup/pgdata_volume.tar.gz -C /source . info " → pgdata_volume.tar.gz ($(du -sh "${BACKUP_DIR}/pgdata_volume.tar.gz" | cut -f1))" else warn "Volume '${PGDATA_VOLUME}' not found — skipping volume backup" fi # ── 4. Summary ─────────────────────────────────────────────────────────────── echo "" info "── Backup complete ──────────────────────────────────────────────" info "Location : $BACKUP_DIR" info "Contents :" ls -lh "$BACKUP_DIR" echo "" TOTAL=$(du -sh "$BACKUP_DIR" | cut -f1) info "Total size: ${TOTAL}" echo "" info "To restore PostgreSQL:" echo " PGPASSWORD=\$pgql_psw docker exec -i ${POSTGRES_CONTAINER} psql -U \$pgql_user \$pgql_db < ${BACKUP_DIR}/postgres_dump.sql" echo "" info "Backup finished successfully at $(date)"