#!/bin/bash # ============================================================================ # Gitea Auto-Update Script # ============================================================================ # Automatically updates low-risk containers (nginx, certbot) with backup, # health checks, and automatic rollback on failure. # # Usage: ./auto-update.sh [container2] [...] # Example: ./auto-update.sh nginx certbot # ============================================================================ set -e # ============================================================================ # Configuration # ============================================================================ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly DOCKER_COMPOSE_DIR="/opt/gitea" readonly COMPOSE_FILE="${DOCKER_COMPOSE_DIR}/docker-compose.yml" readonly BACKUP_SCRIPT="${SCRIPT_DIR}/backup.sh" readonly HEALTH_CHECK_SCRIPT="${SCRIPT_DIR}/health-check.sh" readonly LOG_FILE="/var/log/gitea-auto-update.log" readonly ROLLBACK_INFO="/tmp/gitea-rollback-info-$$.json" # Wait timeouts (seconds) readonly CONTAINER_STARTUP_WAIT=10 # Output colors readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly RED='\033[0;31m' readonly NC='\033[0m' # ============================================================================ # Logging Functions # ============================================================================ get_timestamp() { date '+%Y-%m-%d %H:%M:%S' } log_info() { local message="[$(get_timestamp)] [INFO] $1" echo -e "${YELLOW}${message}${NC}" echo "${message}" >> "${LOG_FILE}" } log_success() { local message="[$(get_timestamp)] [SUCCESS] $1" echo -e "${GREEN}${message}${NC}" echo "${message}" >> "${LOG_FILE}" } log_error() { local message="[$(get_timestamp)] [ERROR] $1" echo -e "${RED}${message}${NC}" >&2 echo "${message}" >> "${LOG_FILE}" } error_exit() { log_error "$1" cleanup exit 1 } # ============================================================================ # Cleanup Function # ============================================================================ cleanup() { if [ -f "${ROLLBACK_INFO}" ]; then rm -f "${ROLLBACK_INFO}" fi } # ============================================================================ # Helper Functions # ============================================================================ change_to_compose_dir() { cd "${DOCKER_COMPOSE_DIR}" || error_exit "Failed to change to ${DOCKER_COMPOSE_DIR}" } run_compose() { docker compose -f "${COMPOSE_FILE}" "$@" } # ============================================================================ # Validation Functions # ============================================================================ validate_args() { if [ $# -eq 0 ]; then error_exit "No containers specified. Usage: $0 [container2] [...]" fi for container in "$@"; do if ! run_compose config --services | grep -q "^${container}$"; then error_exit "Container '${container}' not found in docker-compose.yml" fi done log_success "Container validation passed" } # ============================================================================ # Rollback Management Functions # ============================================================================ save_current_images() { log_info "Saving current image versions for rollback..." echo "{" > "${ROLLBACK_INFO}" local first=true for container in "$@"; do local image=$(run_compose images -q "${container}" 2>/dev/null | head -n1) if [ -n "${image}" ]; then if [ "${first}" = true ]; then first=false else echo "," >> "${ROLLBACK_INFO}" fi echo " \"${container}\": \"${image}\"" >> "${ROLLBACK_INFO}" log_info "Saved ${container}: ${image}" fi done echo "}" >> "${ROLLBACK_INFO}" log_success "Current image versions saved" } rollback() { log_error "Rolling back to previous versions..." if [ ! -f "${ROLLBACK_INFO}" ]; then log_error "No rollback information found" return 1 fi change_to_compose_dir # Extract containers from rollback info and restore local containers=$(grep -o '"[^"]*":' "${ROLLBACK_INFO}" | tr -d '":' | tr '\n' ' ') for container in ${containers}; do log_info "Rolling back ${container}..." run_compose up -d "${container}" || log_error "Failed to rollback ${container}" done log_success "Rollback completed" } # ============================================================================ # Update Functions # ============================================================================ run_backup() { log_info "Running backup before update..." if ! bash "${BACKUP_SCRIPT}"; then error_exit "Backup failed - aborting update" fi log_success "Backup completed successfully" } pull_new_images() { log_info "Pulling new images..." change_to_compose_dir for container in "$@"; do log_info "Pulling image for ${container}..." if ! run_compose pull "${container}"; then error_exit "Failed to pull image for ${container}" fi done log_success "All images pulled successfully" } recreate_containers() { log_info "Recreating containers..." change_to_compose_dir if ! run_compose up -d "$@"; then error_exit "Failed to recreate containers" fi # Wait for containers to start log_info "Waiting for containers to start..." sleep "${CONTAINER_STARTUP_WAIT}" log_success "Containers recreated successfully" } run_health_check() { log_info "Running health check..." if bash "${HEALTH_CHECK_SCRIPT}"; then log_success "Health check passed" return 0 else log_error "Health check failed" return 1 fi } send_failure_notification() { local subject="$1" local body="$2" # Placeholder for email notification # Will be configured with proper email settings in Task 6 log_error "NOTIFICATION: ${subject}" log_error "${body}" # TODO: Implement actual email sending via mail command or SMTP # echo "${body}" | mail -s "${subject}" admin@example.com } # ============================================================================ # Main Execution # ============================================================================ main() { log_info "==========================================" log_info "Gitea Auto-Update Started" log_info "Containers: $*" log_info "==========================================" # Validate input validate_args "$@" # Save current state for rollback save_current_images "$@" # Run backup run_backup # Pull new images pull_new_images "$@" # Recreate containers recreate_containers "$@" # Run health check if run_health_check; then log_success "==========================================" log_success "Update completed successfully" log_success "Updated containers: $*" log_success "==========================================" cleanup exit 0 else log_error "Health check failed after update" rollback # Run health check again after rollback if run_health_check; then log_success "Rollback successful - services restored" send_failure_notification \ "Gitea Update Failed - Rolled Back" \ "Update of containers [$*] failed health check and was rolled back. Services are now healthy." else log_error "Critical: Services still unhealthy after rollback" send_failure_notification \ "CRITICAL: Gitea Update Failed - Manual Intervention Required" \ "Update of containers [$*] failed and rollback did not restore health. IMMEDIATE ATTENTION REQUIRED." fi cleanup exit 1 fi } main "$@"