Files
Gondulf/deployment/scripts/backup.sh
Phil Skentelbery 01dcaba86b feat(deploy): merge Phase 5a deployment configuration
Complete containerized deployment system with Docker/Podman support.

Key features:
- Multi-stage Dockerfile with Python 3.11-slim base
- Docker Compose configurations for production and development
- Nginx reverse proxy with security headers and rate limiting
- Systemd service units for Docker, Podman, and docker-compose
- Backup/restore scripts with integrity verification
- Podman compatibility (ADR-009)

All tests pass including Podman verification testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 19:16:54 -07:00

157 lines
4.7 KiB
Bash
Executable File

#!/bin/bash
#
# Gondulf SQLite Database Backup Script
# Compatible with both Podman and Docker (auto-detects)
#
# Usage: ./backup.sh [backup_dir]
#
# Environment Variables:
# GONDULF_DATABASE_URL - Database URL (default: sqlite:////data/gondulf.db)
# BACKUP_DIR - Backup directory (default: ./backups)
# BACKUP_RETENTION_DAYS - Days to keep backups (default: 7)
# COMPRESS_BACKUPS - Compress backups with gzip (default: true)
# CONTAINER_NAME - Container name (default: gondulf)
# CONTAINER_ENGINE - Force specific engine: podman or docker (default: auto-detect)
#
set -euo pipefail
# Auto-detect container engine
detect_container_engine() {
if [ -n "${CONTAINER_ENGINE:-}" ]; then
echo "$CONTAINER_ENGINE"
elif command -v podman &> /dev/null; then
echo "podman"
elif command -v docker &> /dev/null; then
echo "docker"
else
echo "ERROR: Neither podman nor docker found" >&2
exit 1
fi
}
ENGINE=$(detect_container_engine)
CONTAINER_NAME="${CONTAINER_NAME:-gondulf}"
echo "========================================="
echo "Gondulf Database Backup"
echo "========================================="
echo "Container engine: $ENGINE"
echo "Container name: $CONTAINER_NAME"
echo ""
# Configuration
DATABASE_URL="${GONDULF_DATABASE_URL:-sqlite:////data/gondulf.db}"
BACKUP_DIR="${1:-${BACKUP_DIR:-./backups}}"
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
COMPRESS="${COMPRESS_BACKUPS:-true}"
# Extract database path from URL (handle both 3-slash and 4-slash formats)
if [[ "$DATABASE_URL" =~ ^sqlite:////(.+)$ ]]; then
# Four slashes = absolute path
DB_PATH="/${BASH_REMATCH[1]}"
elif [[ "$DATABASE_URL" =~ ^sqlite:///(.+)$ ]]; then
# Three slashes = relative path (assume /data in container)
DB_PATH="/data/${BASH_REMATCH[1]}"
else
echo "ERROR: Invalid DATABASE_URL format: $DATABASE_URL" >&2
exit 1
fi
echo "Database path: $DB_PATH"
# Verify container is running
if ! $ENGINE ps | grep -q "$CONTAINER_NAME"; then
echo "ERROR: Container '$CONTAINER_NAME' is not running" >&2
echo "Start the container first with: $ENGINE start $CONTAINER_NAME" >&2
exit 1
fi
# Create backup directory on host if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Generate backup filename with timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE_CONTAINER="/tmp/gondulf_backup_${TIMESTAMP}.db"
BACKUP_FILE_HOST="$BACKUP_DIR/gondulf_backup_${TIMESTAMP}.db"
echo "Starting backup..."
echo " Backup file: $BACKUP_FILE_HOST"
echo ""
# Perform backup using SQLite VACUUM INTO (safe hot backup)
# This creates a clean, optimized copy of the database
echo "Creating database backup (this may take a moment)..."
$ENGINE exec "$CONTAINER_NAME" sqlite3 "$DB_PATH" "VACUUM INTO '$BACKUP_FILE_CONTAINER'" || {
echo "ERROR: Backup failed" >&2
exit 1
}
# Copy backup out of container to host
echo "Copying backup to host..."
$ENGINE cp "$CONTAINER_NAME:$BACKUP_FILE_CONTAINER" "$BACKUP_FILE_HOST" || {
echo "ERROR: Failed to copy backup from container" >&2
$ENGINE exec "$CONTAINER_NAME" rm -f "$BACKUP_FILE_CONTAINER" 2>/dev/null || true
exit 1
}
# Clean up temporary file in container
$ENGINE exec "$CONTAINER_NAME" rm -f "$BACKUP_FILE_CONTAINER"
# Verify backup was created on host
if [ ! -f "$BACKUP_FILE_HOST" ]; then
echo "ERROR: Backup file was not created on host" >&2
exit 1
fi
# Verify backup integrity
echo "Verifying backup integrity..."
if sqlite3 "$BACKUP_FILE_HOST" "PRAGMA integrity_check;" | grep -q "ok"; then
echo "✓ Backup integrity check passed"
else
echo "ERROR: Backup integrity check failed" >&2
rm -f "$BACKUP_FILE_HOST"
exit 1
fi
echo "✓ Backup created successfully"
# Compress backup if enabled
if [ "$COMPRESS" = "true" ]; then
echo "Compressing backup..."
gzip "$BACKUP_FILE_HOST"
BACKUP_FILE_HOST="$BACKUP_FILE_HOST.gz"
echo "✓ Backup compressed"
fi
# Calculate and display backup size
BACKUP_SIZE=$(du -h "$BACKUP_FILE_HOST" | cut -f1)
echo "Backup size: $BACKUP_SIZE"
# Clean up old backups
echo ""
echo "Cleaning up backups older than $RETENTION_DAYS days..."
DELETED_COUNT=$(find "$BACKUP_DIR" -name "gondulf_backup_*.db*" -type f -mtime +$RETENTION_DAYS -delete -print | wc -l)
if [ "$DELETED_COUNT" -gt 0 ]; then
echo "✓ Deleted $DELETED_COUNT old backup(s)"
else
echo " No old backups to delete"
fi
# List current backups
echo ""
echo "Current backups:"
if ls "$BACKUP_DIR"/gondulf_backup_*.db* 1> /dev/null 2>&1; then
ls -lht "$BACKUP_DIR"/gondulf_backup_*.db* | head -10
else
echo " (none)"
fi
echo ""
echo "========================================="
echo "Backup complete!"
echo "========================================="
echo "Backup location: $BACKUP_FILE_HOST"
echo "Container engine: $ENGINE"
echo ""