sandbox-notebookllamalm-nextjs/scripts/1_backup.sh
Vadym Samoilenko 4608ca3c5e Add Docker migration: Dockerfiles, compose config, and deployment scripts
- backend/Dockerfile: Python 3.13-slim with uv, ffmpeg, psycopg2
- frontend/Dockerfile: multi-stage Node 22 build with standalone output
- docker-compose.yml: add backend/frontend services with named volumes
- backend/.dockerignore, frontend/.dockerignore: exclude build artifacts
- audio.py: write podcasts to PODCAST_DATA_DIR env var (default: conversations/)
- background_tasks.py: handle .md files directly without LlamaParse
- pyproject.toml: add python-pptx, weasyprint, matplotlib deps
- page.tsx: add Markdown to supported file types hint
- scripts/1_backup.sh: pg_dump + tar files + Docker volume backup
- scripts/2_deploy.sh: full systemd→Docker migration with health checks
- scripts/3_cleanup.sh: post-migration cleanup of build artifacts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 21:34:59 +00:00

127 lines
5.6 KiB
Bash
Executable file

#!/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)"