#!/bin/bash # ========================================== # Nanaxe Server Deployment Script # ========================================== set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration COMPOSE_FILE="docker-compose.prod.yml" BACKUP_DIR="./backups" LOG_FILE="./logs/deploy-$(date +%Y%m%d-%H%M%S).log" # ========================================== # Helper Functions # ========================================== log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" } error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" } warning() { echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" } # ========================================== # Pre-deployment Checks # ========================================== check_prerequisites() { log "Checking prerequisites..." # Check Docker if ! command -v docker &> /dev/null; then error "Docker is not installed" exit 1 fi # Check Docker Compose if ! docker compose version &> /dev/null; then error "Docker Compose is not installed" exit 1 fi # Check .env file if [ ! -f .env ]; then error ".env file not found. Please create it from .env.example" exit 1 fi # Check compose file if [ ! -f "$COMPOSE_FILE" ]; then error "Compose file $COMPOSE_FILE not found" exit 1 fi success "Prerequisites check passed" } # ========================================== # Backup # ========================================== create_backup() { log "Creating backup..." mkdir -p "$BACKUP_DIR" # Backup database if docker compose ps postgres | grep -q "running"; then log "Backing up PostgreSQL database..." docker compose exec -T postgres pg_dump -U gitea gitea > "$BACKUP_DIR/db-$(date +%Y%m%d-%H%M%S).sql" 2>/dev/null || warning "Database backup failed" fi # Backup mindmaps if [ -d "mindmap-data" ]; then log "Backing up mindmaps..." tar -czf "$BACKUP_DIR/mindmaps-$(date +%Y%m%d-%H%M%S).tar.gz" mindmap-data/ 2>/dev/null || warning "Mindmaps backup failed" fi success "Backup completed" } # ========================================== # Deployment # ========================================== deploy() { log "Starting deployment..." # Pull latest images log "Pulling latest images..." docker compose -f "$COMPOSE_FILE" pull # Deploy with zero downtime log "Deploying services..." docker compose -f "$COMPOSE_FILE" up -d --remove-orphans success "Deployment completed" } # ========================================== # Health Checks # ========================================== health_check() { log "Running health checks..." local max_attempts=30 local attempt=1 # Wait for services to start sleep 10 # Check API log "Checking API health..." while [ $attempt -le $max_attempts ]; do if curl -sf http://localhost:8080/health > /dev/null 2>&1; then success "API is healthy" break fi if [ $attempt -eq $max_attempts ]; then error "API health check failed after $max_attempts attempts" show_logs exit 1 fi log "Attempt $attempt/$max_attempts: API not ready yet..." sleep 5 ((attempt++)) done # Check Gitea log "Checking Gitea health..." attempt=1 while [ $attempt -le $max_attempts ]; do if curl -sf http://localhost:3000/api/healthz > /dev/null 2>&1; then success "Gitea is healthy" break fi if [ $attempt -eq $max_attempts ]; then warning "Gitea health check failed (may still be initializing)" break fi log "Attempt $attempt/$max_attempts: Gitea not ready yet..." sleep 5 ((attempt++)) done success "All health checks passed" } # ========================================== # Cleanup # ========================================== cleanup() { log "Cleaning up..." # Remove old backups (keep last 7) if [ -d "$BACKUP_DIR" ]; then ls -t "$BACKUP_DIR"/*.sql 2>/dev/null | tail -n +8 | xargs -r rm ls -t "$BACKUP_DIR"/*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm fi # Clean Docker docker system prune -f docker volume prune -f success "Cleanup completed" } # ========================================== # Rollback # ========================================== rollback() { error "Deployment failed! Rolling back..." # Get previous image local previous_image=$(docker images ghcr.io/*/nanaxe-api --format "{{.Repository}}:{{.Tag}}" | grep -v latest | head -1) if [ -n "$previous_image" ]; then log "Reverting to: $previous_image" docker compose -f "$COMPOSE_FILE" stop api docker compose -f "$COMPOSE_FILE" rm -f api docker run -d --name nanaxe-api-rollback \ --network nanaxe-network \ -v mindmap-data:/app/mindmaps \ -e NODE_ENV=production \ -e PORT=8080 \ -p 8080:8080 \ "$previous_image" success "Rollback completed" else error "No previous image found for rollback" fi } # ========================================== # Show Logs # ========================================== show_logs() { log "Showing recent logs..." docker compose -f "$COMPOSE_FILE" logs --tail=50 } # ========================================== # Main # ========================================== main() { log "======================================" log "Nanaxe Server Deployment" log "======================================" # Create logs directory mkdir -p logs # Run deployment steps check_prerequisites create_backup # Deploy with error handling if deploy; then if health_check; then cleanup success "======================================" success "Deployment completed successfully!" success "======================================" log "API: http://localhost:8080" log "Gitea: http://localhost:3000" else rollback exit 1 fi else rollback exit 1 fi } # Run main function main "$@"