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>
207 lines
6.2 KiB
Bash
Executable File
207 lines
6.2 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Gondulf SQLite Database Restore Script
|
|
# Compatible with both Podman and Docker (auto-detects)
|
|
#
|
|
# Usage: ./restore.sh <backup_file>
|
|
#
|
|
# CAUTION: This will REPLACE the current database!
|
|
# A safety backup will be created before restoration.
|
|
#
|
|
|
|
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
|
|
}
|
|
|
|
# Check arguments
|
|
if [ $# -ne 1 ]; then
|
|
echo "Usage: $0 <backup_file>"
|
|
echo ""
|
|
echo "Example:"
|
|
echo " $0 ./backups/gondulf_backup_20251120_120000.db.gz"
|
|
echo " $0 ./backups/gondulf_backup_20251120_120000.db"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
BACKUP_FILE="$1"
|
|
ENGINE=$(detect_container_engine)
|
|
CONTAINER_NAME="${CONTAINER_NAME:-gondulf}"
|
|
|
|
echo "========================================="
|
|
echo "Gondulf Database Restore"
|
|
echo "========================================="
|
|
echo "Container engine: $ENGINE"
|
|
echo "Container name: $CONTAINER_NAME"
|
|
echo "Backup file: $BACKUP_FILE"
|
|
echo ""
|
|
echo "⚠️ WARNING: This will REPLACE the current database!"
|
|
echo ""
|
|
|
|
# Validate backup file exists
|
|
if [ ! -f "$BACKUP_FILE" ]; then
|
|
echo "ERROR: Backup file not found: $BACKUP_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Configuration
|
|
DATABASE_URL="${GONDULF_DATABASE_URL:-sqlite:////data/gondulf.db}"
|
|
|
|
# Extract database path from URL
|
|
if [[ "$DATABASE_URL" =~ ^sqlite:////(.+)$ ]]; then
|
|
DB_PATH="/${BASH_REMATCH[1]}"
|
|
elif [[ "$DATABASE_URL" =~ ^sqlite:///(.+)$ ]]; then
|
|
DB_PATH="/data/${BASH_REMATCH[1]}"
|
|
else
|
|
echo "ERROR: Invalid DATABASE_URL format: $DATABASE_URL" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Database path in container: $DB_PATH"
|
|
|
|
# Check if container is running
|
|
CONTAINER_RUNNING=false
|
|
if $ENGINE ps | grep -q "$CONTAINER_NAME"; then
|
|
CONTAINER_RUNNING=true
|
|
echo "Container status: running"
|
|
echo ""
|
|
echo "⚠️ Container is running. It will be stopped during restoration."
|
|
read -p "Continue? [y/N] " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Restore cancelled."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Stopping container..."
|
|
$ENGINE stop "$CONTAINER_NAME"
|
|
else
|
|
echo "Container status: stopped"
|
|
fi
|
|
|
|
# Decompress if needed
|
|
TEMP_FILE=""
|
|
RESTORE_FILE=""
|
|
if [[ "$BACKUP_FILE" == *.gz ]]; then
|
|
echo "Decompressing backup..."
|
|
TEMP_FILE=$(mktemp)
|
|
gunzip -c "$BACKUP_FILE" > "$TEMP_FILE"
|
|
RESTORE_FILE="$TEMP_FILE"
|
|
echo "✓ Decompressed to temporary file"
|
|
else
|
|
RESTORE_FILE="$BACKUP_FILE"
|
|
fi
|
|
|
|
# Verify backup integrity before restore
|
|
echo "Verifying backup integrity..."
|
|
if ! sqlite3 "$RESTORE_FILE" "PRAGMA integrity_check;" | grep -q "ok"; then
|
|
echo "ERROR: Backup integrity check failed" >&2
|
|
[ -n "$TEMP_FILE" ] && rm -f "$TEMP_FILE"
|
|
exit 1
|
|
fi
|
|
echo "✓ Backup integrity verified"
|
|
|
|
# Create temporary container to access volume if container is stopped
|
|
if [ "$CONTAINER_RUNNING" = false ]; then
|
|
echo "Creating temporary container to access volume..."
|
|
TEMP_CONTAINER="${CONTAINER_NAME}_restore_temp"
|
|
$ENGINE run -d --name "$TEMP_CONTAINER" \
|
|
-v gondulf_data:/data \
|
|
alpine:latest sleep 300
|
|
CONTAINER_NAME="$TEMP_CONTAINER"
|
|
fi
|
|
|
|
# Create safety backup of current database
|
|
echo "Creating safety backup of current database..."
|
|
SAFETY_BACKUP_CONTAINER="/data/gondulf_pre_restore_$(date +%Y%m%d_%H%M%S).db"
|
|
if $ENGINE exec "$CONTAINER_NAME" test -f "$DB_PATH" 2>/dev/null; then
|
|
$ENGINE exec "$CONTAINER_NAME" cp "$DB_PATH" "$SAFETY_BACKUP_CONTAINER" || {
|
|
echo "WARNING: Failed to create safety backup" >&2
|
|
}
|
|
echo "✓ Safety backup created: $SAFETY_BACKUP_CONTAINER"
|
|
else
|
|
echo " No existing database found (first time setup)"
|
|
fi
|
|
|
|
# Copy restore file into container
|
|
RESTORE_FILE_CONTAINER="/tmp/restore_db.tmp"
|
|
echo "Copying backup to container..."
|
|
$ENGINE cp "$RESTORE_FILE" "$CONTAINER_NAME:$RESTORE_FILE_CONTAINER"
|
|
|
|
# Perform restore
|
|
echo "Restoring database..."
|
|
$ENGINE exec "$CONTAINER_NAME" sh -c "cp '$RESTORE_FILE_CONTAINER' '$DB_PATH'"
|
|
|
|
# Verify restored database
|
|
echo "Verifying restored database..."
|
|
if $ENGINE exec "$CONTAINER_NAME" sqlite3 "$DB_PATH" "PRAGMA integrity_check;" | grep -q "ok"; then
|
|
echo "✓ Restored database integrity verified"
|
|
else
|
|
echo "ERROR: Restored database integrity check failed" >&2
|
|
echo "Attempting to restore from safety backup..."
|
|
|
|
if $ENGINE exec "$CONTAINER_NAME" test -f "$SAFETY_BACKUP_CONTAINER" 2>/dev/null; then
|
|
$ENGINE exec "$CONTAINER_NAME" cp "$SAFETY_BACKUP_CONTAINER" "$DB_PATH"
|
|
echo "✓ Reverted to safety backup"
|
|
fi
|
|
|
|
# Clean up
|
|
$ENGINE exec "$CONTAINER_NAME" rm -f "$RESTORE_FILE_CONTAINER"
|
|
[ -n "$TEMP_FILE" ] && rm -f "$TEMP_FILE"
|
|
|
|
# Stop temporary container if created
|
|
if [ "$CONTAINER_RUNNING" = false ]; then
|
|
$ENGINE stop "$TEMP_CONTAINER" 2>/dev/null || true
|
|
$ENGINE rm "$TEMP_CONTAINER" 2>/dev/null || true
|
|
fi
|
|
|
|
exit 1
|
|
fi
|
|
|
|
# Clean up temporary restore file in container
|
|
$ENGINE exec "$CONTAINER_NAME" rm -f "$RESTORE_FILE_CONTAINER"
|
|
|
|
# Clean up temporary decompressed file on host
|
|
[ -n "$TEMP_FILE" ] && rm -f "$TEMP_FILE"
|
|
|
|
# Stop and remove temporary container if we created one
|
|
if [ "$CONTAINER_RUNNING" = false ]; then
|
|
echo "Cleaning up temporary container..."
|
|
$ENGINE stop "$TEMP_CONTAINER" 2>/dev/null || true
|
|
$ENGINE rm "$TEMP_CONTAINER" 2>/dev/null || true
|
|
CONTAINER_NAME="${CONTAINER_NAME%_restore_temp}" # Restore original name
|
|
fi
|
|
|
|
# Restart original container if it was running
|
|
if [ "$CONTAINER_RUNNING" = true ]; then
|
|
echo "Starting container..."
|
|
$ENGINE start "$CONTAINER_NAME"
|
|
echo "Waiting for container to be healthy..."
|
|
sleep 5
|
|
fi
|
|
|
|
echo ""
|
|
echo "========================================="
|
|
echo "Restore complete!"
|
|
echo "========================================="
|
|
echo "Backup restored from: $BACKUP_FILE"
|
|
echo "Safety backup location: $SAFETY_BACKUP_CONTAINER"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo "1. Verify the application is working correctly"
|
|
echo "2. Once verified, you may delete the safety backup with:"
|
|
echo " $ENGINE exec $CONTAINER_NAME rm $SAFETY_BACKUP_CONTAINER"
|
|
echo ""
|