#!/bin/bash # ============================================================================ # Gitea Restore Script # ============================================================================ # Restores PostgreSQL database, Gitea data, and configuration from S3 backups # # Usage: ./restore.sh # Example: ./restore.sh 20260611_140530 # # This will restore backups with the specified timestamp from S3 # ============================================================================ set -e # ============================================================================ # Configuration # ============================================================================ readonly S3_BUCKET="qvest-task-backups" readonly S3_PREFIX="backups" readonly RESTORE_DIR="/tmp/gitea-restore" readonly LOG_FILE="/var/log/gitea-restore.log" readonly DB_CONTAINER="gitea-postgres" readonly DB_USER="gitea" readonly DB_NAME="gitea" readonly DATA_VOLUME="gitea_gitea-data" readonly CONFIG_DIR="/opt/gitea" # Output colors readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly RED='\033[0;31m' readonly NC='\033[0m' # ============================================================================ # Logging Functions # ============================================================================ log_info() { echo -e "${YELLOW}[INFO]${NC} $1" | tee -a "${LOG_FILE}" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "${LOG_FILE}" } log_error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "${LOG_FILE}" >&2 } error_exit() { log_error "$1" cleanup exit 1 } # ============================================================================ # Validation Functions # ============================================================================ validate_timestamp() { if [[ ! "$1" =~ ^[0-9]{8}_[0-9]{6}$ ]]; then error_exit "Invalid timestamp format. Expected: YYYYMMDD_HHMMSS" fi } check_s3_backup_exists() { local timestamp="$1" local file="$2" if ! aws s3 ls "s3://${S3_BUCKET}/${S3_PREFIX}/${file}-${timestamp}.tar.gz" &>/dev/null && \ ! aws s3 ls "s3://${S3_BUCKET}/${S3_PREFIX}/${file}-${timestamp}.sql.gz" &>/dev/null; then return 1 fi return 0 } # ============================================================================ # Core Functions # ============================================================================ cleanup() { if [ -d "${RESTORE_DIR}" ]; then rm -rf "${RESTORE_DIR}" fi } create_restore_dir() { mkdir -p "${RESTORE_DIR}" || error_exit "Failed to create restore directory" } download_backups() { local timestamp="$1" log_info "Downloading backups from S3..." aws s3 cp "s3://${S3_BUCKET}/${S3_PREFIX}/database-${timestamp}.sql.gz" \ "${RESTORE_DIR}/" || error_exit "Failed to download database backup" aws s3 cp "s3://${S3_BUCKET}/${S3_PREFIX}/gitea-data-${timestamp}.tar.gz" \ "${RESTORE_DIR}/" || error_exit "Failed to download Gitea data backup" aws s3 cp "s3://${S3_BUCKET}/${S3_PREFIX}/config-${timestamp}.tar.gz" \ "${RESTORE_DIR}/" || error_exit "Failed to download configuration backup" log_success "Backups downloaded successfully" } stop_services() { log_info "Stopping Gitea services..." cd "${CONFIG_DIR}" || error_exit "Failed to change to config directory" docker compose stop gitea || error_exit "Failed to stop Gitea" log_success "Services stopped" } restore_database() { local timestamp="$1" log_info "Restoring database..." # Drop and recreate database docker exec "${DB_CONTAINER}" psql -U "${DB_USER}" -d postgres \ -c "DROP DATABASE IF EXISTS ${DB_NAME};" || error_exit "Failed to drop database" docker exec "${DB_CONTAINER}" psql -U "${DB_USER}" -d postgres \ -c "CREATE DATABASE ${DB_NAME};" || error_exit "Failed to create database" # Restore from backup gunzip -c "${RESTORE_DIR}/database-${timestamp}.sql.gz" | \ docker exec -i "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${DB_NAME}" \ || error_exit "Failed to restore database" log_success "Database restored" } restore_gitea_data() { local timestamp="$1" log_info "Restoring Gitea data..." # Clear existing data docker run --rm \ -v "${DATA_VOLUME}:/data" \ alpine sh -c "rm -rf /data/*" \ || error_exit "Failed to clear Gitea data" # Restore from backup docker run --rm \ -v "${DATA_VOLUME}:/data" \ -v "${RESTORE_DIR}:/backup:ro" \ alpine tar xzf "/backup/gitea-data-${timestamp}.tar.gz" -C /data \ || error_exit "Failed to restore Gitea data" log_success "Gitea data restored" } restore_configuration() { local timestamp="$1" log_info "Restoring configuration files..." # Extract configuration backup tar xzf "${RESTORE_DIR}/config-${timestamp}.tar.gz" -C "${CONFIG_DIR}" \ || error_exit "Failed to restore configuration" log_success "Configuration restored" } start_services() { log_info "Starting Gitea services..." cd "${CONFIG_DIR}" || error_exit "Failed to change to config directory" docker compose up -d || error_exit "Failed to start services" log_info "Waiting for services to be ready..." sleep 10 log_success "Services started" } verify_restore() { log_info "Verifying restore..." # Check if Gitea is responding if curl -f -s http://localhost:3000 > /dev/null; then log_success "Gitea is responding" else log_error "Gitea is not responding - manual verification required" fi # Check database connection if docker exec "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${DB_NAME}" \ -c "SELECT 1 FROM public.user LIMIT 1;" &>/dev/null; then log_success "Database is accessible" else log_error "Database verification failed" fi } # ============================================================================ # Main Execution # ============================================================================ main() { if [ $# -ne 1 ]; then echo "Usage: $0 " echo " Example: $0 20260611_140530" echo "" echo "Available backups:" aws s3 ls "s3://${S3_BUCKET}/${S3_PREFIX}/" | grep "database-" | \ sed 's/.*database-\([0-9_]*\)\.sql\.gz/ \1/' | sort -u exit 1 fi local timestamp="$1" log_info "Starting restore process for timestamp: ${timestamp}" validate_timestamp "${timestamp}" if ! check_s3_backup_exists "${timestamp}" "database"; then error_exit "Backup with timestamp ${timestamp} not found in S3" fi # Confirm restore echo "" log_error "WARNING: This will replace all current data!" read -p "Are you sure you want to continue? (yes/no): " confirm if [ "$confirm" != "yes" ]; then echo "Restore cancelled" exit 0 fi create_restore_dir download_backups "${timestamp}" stop_services restore_database "${timestamp}" restore_gitea_data "${timestamp}" restore_configuration "${timestamp}" start_services verify_restore cleanup log_success "Restore completed successfully" echo "" log_info "Please verify the system is functioning correctly:" log_info " 1. Access https://git.poll-streams.com" log_info " 2. Login with your credentials" log_info " 3. Verify repositories are accessible" log_info " 4. Check system settings" } main "$@"