qvest-task/scripts/auto-update.sh
gitea_admin 685de1816d feat: implement update automation and backup system with CI tests (#1)
- Diun monitors Docker images
- Automated updates for nginx, manual approval for gitea/postgres
- Weekly cert renewal automation via cron
- Health checks with automatic rollback on failure
- AWS SES email notifications on update failures
- Daily S3 backups + pre-update snapshots
- Integration tests with Gitea Actions quality gate
- Change domain from gitea.poll-streams.com to git.poll-streams.com
- Add diagrams
2026-06-11 15:51:48 +00:00

255 lines
7.8 KiB
Bash

#!/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 <container1> [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 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"
# 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() {
local message="[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1"
echo -e "${YELLOW}${message}${NC}"
echo "${message}" >> "${LOG_FILE}"
}
log_success() {
local message="[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $1"
echo -e "${GREEN}${message}${NC}"
echo "${message}" >> "${LOG_FILE}"
}
log_error() {
local message="[$(date '+%Y-%m-%d %H:%M:%S')] [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
}
# ============================================================================
# Validation Functions
# ============================================================================
validate_args() {
if [ $# -eq 0 ]; then
error_exit "No containers specified. Usage: $0 <container1> [container2] [...]"
fi
for container in "$@"; do
if ! docker compose -f "${DOCKER_COMPOSE_DIR}/docker-compose.yml" 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=$(docker compose -f "${DOCKER_COMPOSE_DIR}/docker-compose.yml" 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
cd "${DOCKER_COMPOSE_DIR}" || error_exit "Failed to change to ${DOCKER_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}..."
docker 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..."
cd "${DOCKER_COMPOSE_DIR}" || error_exit "Failed to change to ${DOCKER_COMPOSE_DIR}"
for container in "$@"; do
log_info "Pulling image for ${container}..."
if ! docker 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..."
cd "${DOCKER_COMPOSE_DIR}" || error_exit "Failed to change to ${DOCKER_COMPOSE_DIR}"
if ! docker compose up -d "$@"; then
error_exit "Failed to recreate containers"
fi
# Wait for containers to start
log_info "Waiting for containers to start..."
sleep 10
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 "$@"