#!/bin/bash # Generic Service Backup Script for Docker Compose services # Location: /home/citadel/services/paperless/backup.sh # Runs daily at 3 AM via systemd timer set -e # Service Configuration SERVICE_NAME="paperless" SERVICE_DIR="/home/citadel/services/$SERVICE_NAME" DATA_DIR="/home/citadel/data/$SERVICE_NAME" TEMP_BACKUP_DIR="/tmp/$SERVICE_NAME" CONFIG_FILE="/home/citadel/backup/restic.conf" COMPOSE_FILE="$SERVICE_DIR/docker-compose.yml" # Logging LOG_FILE="/var/log/$SERVICE_NAME-backup.log" exec 1> >(tee -a "$LOG_FILE") exec 2> >(tee -a "$LOG_FILE" >&2) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } cleanup() { log "Cleaning up temporary files..." rm -rf "$TEMP_BACKUP_DIR" # Ensure containers are running in case of error if docker compose -f "$COMPOSE_FILE" ps --services --filter "status=exited" | grep -q .; then log "Some containers are stopped, restarting..." cd "$SERVICE_DIR" docker compose up -d fi } # Set up cleanup on exit trap cleanup EXIT log "=== Starting $SERVICE_NAME Backup ===" # Check if configuration exists if [ ! -f "$CONFIG_FILE" ]; then log "ERROR: Configuration file $CONFIG_FILE not found!" exit 1 fi # Source Restic configuration source "$CONFIG_FILE" # Create temporary backup directory log "Creating temporary backup directory: $TEMP_BACKUP_DIR" mkdir -p "$TEMP_BACKUP_DIR" # Navigate to service directory cd "$SERVICE_DIR" # Check if compose file exists if [ ! -f "$COMPOSE_FILE" ]; then log "ERROR: Docker compose file $COMPOSE_FILE not found!" exit 1 fi log "Stopping $SERVICE_NAME containers..." docker compose down # Wait a moment for containers to fully stop sleep 5 log "Creating PostgreSQL database dump..." # Start only the database container for backup docker compose up -d db # Wait for database to be ready sleep 10 # Get database credentials from .env if [ -f "$SERVICE_DIR/.env" ]; then source "$SERVICE_DIR/.env" else log "ERROR: .env file not found!" exit 1 fi # Create database dump DUMP_FILE="$TEMP_BACKUP_DIR/${SERVICE_NAME}_db_$(date +%Y%m%d_%H%M%S).sql" log "Creating database dump: $DUMP_FILE" docker compose exec -T db pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" > "$DUMP_FILE" if [ $? -eq 0 ]; then log "✅ Database dump created successfully" else log "❌ Database dump failed!" exit 1 fi # Stop database container docker compose down log "Copying application data to temporary directory..." # Copy data directories cp -r "$DATA_DIR"/* "$TEMP_BACKUP_DIR/" 2>/dev/null || true # Copy service configuration cp -r "$SERVICE_DIR" "$TEMP_BACKUP_DIR/service_config" log "Creating Restic backup..." restic backup "$TEMP_BACKUP_DIR" \ --tag "$SERVICE_NAME" \ --tag "daily" if [ $? -eq 0 ]; then log "✅ Restic backup completed successfully with tag: $SERVICE_NAME" else log "❌ Restic backup failed!" exit 1 fi log "Restarting $SERVICE_NAME containers..." docker compose up -d # Wait for services to be ready sleep 15 # Check if services are running if docker compose ps --services --filter "status=running" | grep -q "webserver"; then log "✅ $SERVICE_NAME containers restarted successfully" else log "⚠️ Warning: Some containers may not be running properly" fi log "Running Restic maintenance (forget old snapshots)..." # Keep: 7 daily, 4 weekly, 12 monthly, 2 yearly restic forget \ --tag "$SERVICE_NAME" \ --keep-daily 7 \ --keep-weekly 4 \ --keep-monthly 12 \ --keep-yearly 2 \ --prune log "=== Backup completed successfully ===" # Show backup statistics log "Current repository stats:" restic stats --mode raw-data