#!/bin/bash # scripts/deploy/deploy_aitvaras.sh # Deployment script: karl (development) → aitvaras (Ubuntu 24.04 production) # # Usage: # ./scripts/deploy/deploy_aitvaras.sh [--dry-run] [--rollback] [--force] # # Dragons@Work - Furt API-Gateway Production Deployment # Version: 1.0 set -euo pipefail # Exit on error, undefined vars, pipe failures # ============================================================================= # CONFIGURATION # ============================================================================= # Source (karl development) SOURCE_DIR="/home/michael/Develop/DAW/furt/furt-lua" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" # scripts/deploy/ -> scripts/ -> furt/ # Target (aitvaras Ubuntu production) AITVARAS_HOST="aitvaras" # Assumes SSH config entry (as michael user) TARGET_DIR="/opt/furt-api" SERVICE_USER="_furt" SERVICE_GROUP="_furt" SERVICE_NAME="furt-api" # Ubuntu-specific paths CONFIG_DIR="/etc/furt" # ← Angepasst: wie start.sh erwartet LOG_DIR="/var/log/furt-api" RUN_DIR="/var/run/furt-api" BACKUP_DIR="/var/backup/furt-api" SYSTEMD_SERVICE="/etc/systemd/system/furt-api.service" # Backup configuration BACKUP_RETENTION=5 # Keep last 5 backups (production = more backups) # Health check configuration HEALTH_URL="http://localhost:8080/health" HEALTH_TIMEOUT=15 # Longer timeout for production HEALTH_RETRIES=5 # More retries for production # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' NC='\033[0m' # No Color # ============================================================================= # LOGGING FUNCTIONS # ============================================================================= log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "\n${PURPLE}==>${NC} $1" } log_production() { echo -e "${PURPLE}[PRODUCTION]${NC} $1" } # ============================================================================= # UTILITY FUNCTIONS # ============================================================================= usage() { cat << EOF Usage: $0 [OPTIONS] 🚨 PRODUCTION deployment script for furt-api: karl → aitvaras (Ubuntu 24.04) ⚠️ WARNING: This deploys to PRODUCTION server aitvaras! ⚠️ All changes are immediately live and affect production services. 🔐 SECURITY NOTE: This script temporarily enables passwordless sudo for deployment, then automatically disables it for security. OPTIONS: --dry-run Show what would be deployed without making changes --rollback Rollback to previous deployment --force Skip confirmation prompts (USE WITH CAUTION!) --help Show this help message EXAMPLES: $0 # Normal production deployment with confirmation $0 --dry-run # Preview deployment without changes (RECOMMENDED) $0 --force # Deploy without confirmation (DANGEROUS!) $0 --rollback # Rollback to previous version PRODUCTION SAFETY: - Always run --dry-run first - Creates automatic backups before deployment - Health checks ensure successful deployment - Rollback available if deployment fails - Passwordless sudo automatically disabled after deployment EOF } check_dependencies() { log_step "Checking dependencies for production deployment" # Check if source directory exists if [[ ! -d "$SOURCE_DIR" ]]; then log_error "Source directory not found: $SOURCE_DIR" exit 1 fi # Check SSH connectivity to aitvaras if ! ssh -o ConnectTimeout=10 -o BatchMode=yes "$AITVARAS_HOST" exit 2>/dev/null; then log_error "Cannot connect to aitvaras via SSH" log_info "Please ensure SSH key is set up for $AITVARAS_HOST" exit 1 fi # Enable passwordless sudo for deployment if ! enable_passwordless_sudo; then log_error "Cannot enable passwordless sudo on aitvaras" exit 1 fi # Check rsync availability if ! command -v rsync &> /dev/null; then log_error "rsync is required but not installed" exit 1 fi log_success "All dependencies OK" } get_backup_timestamp() { date +"%Y%m%d_%H%M%S" } # ============================================================================= # SSH REMOTE EXECUTION FUNCTIONS # ============================================================================= aitvaras_exec() { ssh "$AITVARAS_HOST" "$@" } aitvaras_exec_sudo() { ssh "$AITVARAS_HOST" "sudo $@" } aitvaras_exec_as_furt() { ssh "$AITVARAS_HOST" "sudo -u $SERVICE_USER $@" } # ============================================================================= # SUDO AUTHENTICATION FUNCTIONS # ============================================================================= enable_passwordless_sudo() { log_step "Enabling temporary passwordless sudo" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would enable passwordless sudo temporarily" return 0 fi log_info "Testing current sudo access..." # Test if sudo works without password (already configured) if ssh "$AITVARAS_HOST" "sudo -n true" 2>/dev/null; then log_success "Passwordless sudo already enabled" return 0 fi # Need to enable passwordless sudo temporarily log_info "Setting up temporary passwordless sudo for deployment..." log_warning "⚠️ This will require your password ONCE to enable passwordless mode" # Use ssh -t for interactive terminal to set up passwordless mode if ssh -t "$AITVARAS_HOST" "echo 'michael ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/99-deployment-passwordless"; then # Verify it works if ssh "$AITVARAS_HOST" "sudo -n true" 2>/dev/null; then log_success "Temporary passwordless sudo enabled" return 0 else log_error "Failed to verify passwordless sudo" return 1 fi else log_error "Failed to enable passwordless sudo" return 1 fi } disable_passwordless_sudo() { if [[ "$DRY_RUN" != "true" ]]; then log_info "Removing temporary passwordless sudo configuration..." ssh "$AITVARAS_HOST" "sudo rm -f /etc/sudoers.d/99-deployment-passwordless" 2>/dev/null || true log_success "Passwordless sudo disabled - security restored" fi } # ============================================================================= # USER MANAGEMENT FUNCTIONS # ============================================================================= create_service_user() { log_step "Creating service user" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would create user $SERVICE_USER with group $SERVICE_GROUP" return 0 fi # Check if user already exists if aitvaras_exec "id $SERVICE_USER &>/dev/null"; then log_info "User $SERVICE_USER already exists" return 0 fi log_info "Creating system user: $SERVICE_USER" # Create system user (Ubuntu style) aitvaras_exec_sudo "useradd --system --shell /usr/sbin/nologin --home-dir $TARGET_DIR --create-home --user-group $SERVICE_USER" # Verify user creation if aitvaras_exec "id $SERVICE_USER &>/dev/null"; then log_success "User $SERVICE_USER created successfully" else log_error "Failed to create user $SERVICE_USER" return 1 fi } # ============================================================================= # DIRECTORY MANAGEMENT FUNCTIONS # ============================================================================= create_directories() { log_step "Creating directory structure" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would create directories:" local directories=("$TARGET_DIR" "$CONFIG_DIR" "$LOG_DIR" "$RUN_DIR" "$BACKUP_DIR") for dir in "${directories[@]}"; do echo " - $dir" done return 0 fi log_info "Creating directory structure..." # Create all directories in one sudo call aitvaras_exec_sudo "mkdir -p $TARGET_DIR $CONFIG_DIR $LOG_DIR $RUN_DIR $BACKUP_DIR" # Set ownership and permissions in batch aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR $CONFIG_DIR $LOG_DIR $RUN_DIR $BACKUP_DIR" aitvaras_exec_sudo "chmod 755 $TARGET_DIR $CONFIG_DIR $RUN_DIR $BACKUP_DIR" aitvaras_exec_sudo "chmod 750 $LOG_DIR" log_success "Directory structure created" } # ============================================================================= # BACKUP FUNCTIONS # ============================================================================= create_backup() { local timestamp=$(get_backup_timestamp) local backup_path="$BACKUP_DIR/furt-api_$timestamp" log_step "Creating production backup" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would create backup at $backup_path" echo "" return fi # Create backup directory if it doesn't exist aitvaras_exec_sudo "mkdir -p $BACKUP_DIR" aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $BACKUP_DIR" # Check if target directory exists and has content if aitvaras_exec "test -d $TARGET_DIR && test -n \"\$(ls -A $TARGET_DIR 2>/dev/null)\""; then log_production "Backing up current deployment to: $backup_path" aitvaras_exec_sudo "cp -r $TARGET_DIR $backup_path" aitvaras_exec_sudo "chown -R $SERVICE_USER:$SERVICE_GROUP $backup_path" # Set backup metadata aitvaras_exec_sudo "sh -c \"echo 'Backup created: \$(date)' > $backup_path/.backup_info\"" aitvaras_exec_sudo "sh -c \"echo 'Original path: $TARGET_DIR' >> $backup_path/.backup_info\"" aitvaras_exec_sudo "sh -c \"echo 'Service: $SERVICE_NAME' >> $backup_path/.backup_info\"" aitvaras_exec_sudo "sh -c \"echo 'Host: \$(hostname)' >> $backup_path/.backup_info\"" aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $backup_path/.backup_info" log_success "Production backup created: $backup_path" echo "$backup_path" # Return backup path else log_warning "No existing deployment found or directory empty, skipping backup" echo "" # Return empty string fi } cleanup_old_backups() { log_step "Cleaning up old backups" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would cleanup old backups (keep last $BACKUP_RETENTION)" return 0 fi local backup_count=$(aitvaras_exec "ls -1 $BACKUP_DIR/furt-api_* 2>/dev/null | wc -l" || echo "0") if [[ $backup_count -gt $BACKUP_RETENTION ]]; then log_info "Found $backup_count backups, keeping last $BACKUP_RETENTION" aitvaras_exec_sudo "ls -1t $BACKUP_DIR/furt-api_* | tail -n +$((BACKUP_RETENTION + 1)) | xargs rm -rf" log_success "Old backups cleaned up" else log_info "Found $backup_count backups, no cleanup needed" fi } list_backups() { log_step "Available production backups" if aitvaras_exec "ls -1 $BACKUP_DIR/furt-api_* 2>/dev/null"; then aitvaras_exec "ls -1t $BACKUP_DIR/furt-api_* | head -n 5 | while read backup; do echo \" \$backup\" if [ -f \"\$backup/.backup_info\" ]; then cat \"\$backup/.backup_info\" | sed 's/^/ /' fi echo done" else log_warning "No backups found" fi } rollback_deployment() { log_step "Rolling back production deployment" # List available backups local latest_backup=$(aitvaras_exec "ls -1t $BACKUP_DIR/furt-api_* 2>/dev/null | head -n 1" || echo "") if [[ -z "$latest_backup" ]]; then log_error "No backups available for rollback" exit 1 fi log_production "Latest backup: $latest_backup" if [[ "$FORCE" != "true" ]]; then echo -n "⚠️ PRODUCTION ROLLBACK - Continue? [y/N]: " read -r response if [[ ! "$response" =~ ^[Yy]$ ]]; then log_info "Rollback cancelled" exit 0 fi fi # Stop service stop_service # Backup current version (before rollback) local rollback_backup=$(create_backup) if [[ -n "$rollback_backup" ]]; then aitvaras_exec_sudo "mv $rollback_backup ${rollback_backup}_pre_rollback" fi # Restore from backup aitvaras_exec_sudo "rm -rf $TARGET_DIR" aitvaras_exec_sudo "cp -r $latest_backup $TARGET_DIR" # Fix permissions fix_permissions # Start service start_service # Health check if health_check; then log_success "Production rollback completed successfully" else log_error "Rollback completed but health check failed" exit 1 fi } # ============================================================================= # SERVICE MANAGEMENT FUNCTIONS # ============================================================================= create_systemd_service() { log_step "Creating systemd service" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would create $SYSTEMD_SERVICE" return 0 fi log_info "Creating systemd service: $SERVICE_NAME" # Create systemd service file using sudo tee aitvaras_exec_sudo "tee $SYSTEMD_SERVICE > /dev/null << 'EOF' [Unit] Description=Furt API Gateway Documentation=https://gitea.dragons-at-work.de/DAW/furt After=network.target [Service] Type=forking User=$SERVICE_USER Group=$SERVICE_GROUP WorkingDirectory=$TARGET_DIR ExecStart=$TARGET_DIR/scripts/start.sh Restart=always RestartSec=5 StandardOutput=journal StandardError=journal # Security settings NoNewPrivileges=true PrivateTmp=true ProtectHome=true ProtectSystem=strict ReadWritePaths=$TARGET_DIR $LOG_DIR $RUN_DIR # Environment Environment=LUA_COMMAND=/usr/bin/lua5.1 Environment=LUA_VERSION=5.1 [Install] WantedBy=multi-user.target EOF" # Reload systemd aitvaras_exec_sudo "systemctl daemon-reload" # Enable service aitvaras_exec_sudo "systemctl enable $SERVICE_NAME" log_success "Systemd service created and enabled" } get_service_status() { if aitvaras_exec "systemctl is-active $SERVICE_NAME >/dev/null 2>&1"; then echo "running" else echo "stopped" fi } stop_service() { log_step "Stopping $SERVICE_NAME service" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would stop $SERVICE_NAME service" return 0 fi local status=$(get_service_status) if [[ "$status" == "running" ]]; then log_production "Stopping production service: $SERVICE_NAME" aitvaras_exec_sudo "systemctl stop $SERVICE_NAME" # Wait for service to stop local attempts=0 while [[ $attempts -lt 10 ]]; do if [[ $(get_service_status) == "stopped" ]]; then log_success "Service stopped successfully" return 0 fi sleep 1 ((attempts++)) done log_error "Service did not stop within 10 seconds" return 1 else log_info "Service already stopped" fi } start_service() { log_step "Starting $SERVICE_NAME service" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would start $SERVICE_NAME service" return 0 fi local status=$(get_service_status) if [[ "$status" == "stopped" ]]; then # Check port availability before starting if ! check_port_availability; then log_error "Cannot start service - port 8080 is occupied" return 1 fi log_production "Starting production service: $SERVICE_NAME" aitvaras_exec_sudo "systemctl start $SERVICE_NAME" # Wait and check if service actually started sleep 3 local new_status=$(get_service_status) if [[ "$new_status" == "running" ]]; then log_success "Service started successfully" else log_error "Failed to start service" # Show service logs for debugging aitvaras_exec_sudo "journalctl -u $SERVICE_NAME --no-pager -n 20" || true return 1 fi else log_info "Service already running" fi } # ============================================================================= # DEPLOYMENT FUNCTIONS # ============================================================================= prepare_source() { log_step "Preparing source files" # Check source directory structure local required_dirs=("src" "config" "scripts") for dir in "${required_dirs[@]}"; do if [[ ! -d "$SOURCE_DIR/$dir" ]]; then log_error "Required directory missing: $SOURCE_DIR/$dir" exit 1 fi done # Check required files local required_files=("src/main.lua" "scripts/start.sh") for file in "${required_files[@]}"; do if [[ ! -f "$SOURCE_DIR/$file" ]]; then log_error "Required file missing: $SOURCE_DIR/$file" exit 1 fi done log_success "Source files validated" } sync_files() { log_step "Syncing files to production" # Prepare rsync excludes local excludes=( "--exclude=.env" "--exclude=.env.*" "--exclude=*.backup" "--exclude=*.orig" "--exclude=*.tmp" "--exclude=.git/" "--exclude=.DS_Store" "--exclude=logs/" "--exclude=*.log" "--exclude=tests/" ) if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would sync the following files:" rsync -avz --dry-run "${excludes[@]}" "$SOURCE_DIR/" "$AITVARAS_HOST:$TARGET_DIR/" return 0 fi # Clean up target directory and recreate with correct permissions log_info "Preparing target directory for sync..." aitvaras_exec_sudo "rm -rf $TARGET_DIR" aitvaras_exec_sudo "mkdir -p $TARGET_DIR" aitvaras_exec_sudo "chown michael:michael $TARGET_DIR" # Sync files as michael user (now has permissions) log_production "Syncing files to production..." rsync -avz "${excludes[@]}" \ -e "ssh" \ "$SOURCE_DIR/" "$AITVARAS_HOST:$TARGET_DIR/" # Fix ownership to _furt after successful sync aitvaras_exec_sudo "chown -R $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR" log_success "Files synced to production" } create_environment_file() { log_step "Creating environment configuration" if [[ "$DRY_RUN" == "true" ]]; then if aitvaras_exec "test -f $CONFIG_DIR/environment"; then log_info "DRY RUN: Environment file exists - would NOT overwrite" else log_info "DRY RUN: Would create $CONFIG_DIR/environment" fi return 0 fi # Check if environment file already exists if aitvaras_exec "test -f $CONFIG_DIR/environment"; then log_success "Environment file already exists - preserving existing configuration" log_info "Existing config preserved at: $CONFIG_DIR/environment" # Still fix permissions in case they got messed up aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $CONFIG_DIR/environment" aitvaras_exec_sudo "chmod 640 $CONFIG_DIR/environment" return 0 fi log_info "Creating new environment file..." # Create environment file for production using sudo tee aitvaras_exec_sudo "tee $CONFIG_DIR/environment > /dev/null << 'EOF' # Furt API Production Environment Configuration # Created by deploy_aitvaras.sh on $(date) # Lua Configuration LUA_COMMAND=/usr/bin/lua5.1 LUA_VERSION=5.1 # Server Configuration SERVER_HOST=127.0.0.1 SERVER_PORT=8080 # Logging LOG_LEVEL=info LOG_FILE=$LOG_DIR/furt-api.log # SMTP Configuration (to be filled manually) # SMTP_USERNAME=your_smtp_username # SMTP_PASSWORD=your_smtp_password # SMTP_HOST=mail.dragons-at-work.de # SMTP_PORT=465 # SMTP_FROM=noreply@dragons-at-work.de # SMTP_TO=michael@dragons-at-work.de # Security # Add your production SMTP credentials here manually after deployment EOF" # Set proper permissions immediately aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $CONFIG_DIR/environment" aitvaras_exec_sudo "chmod 640 $CONFIG_DIR/environment" log_success "Environment file created with correct permissions" log_warning "⚠️ MANUAL STEP REQUIRED: Add SMTP credentials to $CONFIG_DIR/environment" } fix_permissions() { log_step "Fixing permissions" if [[ "$DRY_RUN" == "true" ]]; then log_info "DRY RUN: Would set ownership to $SERVICE_USER:$SERVICE_GROUP" return 0 fi # Set ownership in batch aitvaras_exec_sudo "chown -R $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR $CONFIG_DIR $LOG_DIR $RUN_DIR" # Set permissions for scripts aitvaras_exec_sudo "chmod +x $TARGET_DIR/scripts/*.sh" # Set permissions for config directory aitvaras_exec_sudo "chmod 755 $CONFIG_DIR" # Set permissions for environment file only if it exists if aitvaras_exec "test -f $CONFIG_DIR/environment"; then aitvaras_exec_sudo "chmod 640 $CONFIG_DIR/environment" fi log_success "Permissions fixed" } # ============================================================================= # HEALTH CHECK FUNCTIONS # ============================================================================= check_port_availability() { log_step "Checking port availability" local port="8080" # furt-api port if aitvaras_exec "netstat -an | grep -q ':$port'"; then log_warning "Port $port is already in use" aitvaras_exec "netstat -an | grep ':$port'" || true # Try to identify what's using the port log_info "Checking what's using port $port..." aitvaras_exec "sudo ss -tlnp | grep ':$port'" || true return 1 else log_success "Port $port is available" return 0 fi } health_check() { log_step "Running production health check" local retries=$HEALTH_RETRIES while [[ $retries -gt 0 ]]; do log_info "Health check attempt $((HEALTH_RETRIES - retries + 1))/$HEALTH_RETRIES" if aitvaras_exec "curl -s --max-time $HEALTH_TIMEOUT $HEALTH_URL >/dev/null 2>&1"; then log_success "Production health check passed" # Get health check response local health_response=$(aitvaras_exec "curl -s --max-time $HEALTH_TIMEOUT $HEALTH_URL" || echo "") if [[ -n "$health_response" ]]; then log_info "Health response: $health_response" fi # Additional status info local service_status=$(get_service_status) log_info "Service status: $service_status" return 0 fi ((retries--)) if [[ $retries -gt 0 ]]; then log_info "Health check failed, retrying in 5 seconds..." sleep 5 fi done log_error "Production health check failed after $HEALTH_RETRIES attempts" log_info "Debugging service status..." local service_status=$(get_service_status) log_info "Service status: $service_status" # Show service logs log_info "Recent service logs:" aitvaras_exec_sudo "journalctl -u $SERVICE_NAME --no-pager -n 10" || true # Check if port is at least listening if aitvaras_exec "netstat -an | grep -q ':8080.*LISTEN'"; then log_warning "Port 8080 is listening but health check failed - possible service issue" else log_error "Port 8080 is not listening - service not running" fi return 1 } # ============================================================================= # MAIN DEPLOYMENT FUNCTION # ============================================================================= deploy() { log_step "Starting PRODUCTION deployment: karl → aitvaras" log_production "⚠️ THIS IS A PRODUCTION DEPLOYMENT ⚠️" # Pre-deployment checks check_dependencies prepare_source # Show deployment summary log_info "Production Deployment Summary:" log_info " Source: $SOURCE_DIR" log_info " Target: $AITVARAS_HOST:$TARGET_DIR" log_info " Service: $SERVICE_NAME (systemd)" log_info " User: $SERVICE_USER" log_info " Dry Run: $DRY_RUN" if [[ "$DRY_RUN" == "true" ]]; then log_warning "DRY RUN MODE - No changes will be made" else log_production "🚨 PRODUCTION MODE - Changes will be applied immediately!" fi # Production confirmation prompt if [[ "$FORCE" != "true" && "$DRY_RUN" != "true" ]]; then echo "" echo -e "${RED}⚠️ PRODUCTION DEPLOYMENT WARNING ⚠️${NC}" echo -e "This will deploy to the live production server aitvaras." echo -e "All changes will immediately affect live services." echo "" echo -n "Continue with PRODUCTION deployment? [y/N]: " read -r response if [[ ! "$response" =~ ^[Yy]$ ]]; then log_info "Production deployment cancelled" exit 0 fi fi # Create service user and directories create_service_user create_directories # Create backup before deployment local backup_path="" if [[ "$DRY_RUN" != "true" ]]; then backup_path=$(create_backup) fi # Stop service if running if [[ "$DRY_RUN" != "true" ]]; then stop_service fi # Deploy files sync_files # Create configuration create_environment_file # Fix permissions (after environment file exists) fix_permissions # Create systemd service create_systemd_service # Start service if [[ "$DRY_RUN" != "true" ]]; then # Check if port is available before starting if ! check_port_availability; then log_error "Cannot start service - port conflict detected" log_info "Check what's using port 8080: sudo ss -tlnp | grep :8080" exit 1 fi if start_service; then # Health check if health_check; then log_success "🎉 PRODUCTION DEPLOYMENT COMPLETED SUCCESSFULLY!" log_production "Service is live at: https://api.dragons-at-work.de" # Cleanup old backups cleanup_old_backups # Disable passwordless sudo (security) disable_passwordless_sudo # Show next steps echo "" log_info "📝 MANUAL STEPS REQUIRED:" log_info "1. Add SMTP credentials to: $CONFIG_DIR/environment" log_info "2. Test the API: curl https://api.dragons-at-work.de/health" log_info "3. Monitor logs: sudo journalctl -u $SERVICE_NAME -f" else log_error "Production deployment failed health check" # Disable passwordless sudo even on failure disable_passwordless_sudo # Offer rollback if [[ -n "$backup_path" ]]; then echo -n "🔄 Rollback to previous version? [y/N]: " read -r response if [[ "$response" =~ ^[Yy]$ ]]; then log_production "Rolling back production deployment..." # Need to re-enable passwordless sudo for rollback enable_passwordless_sudo aitvaras_exec_sudo "rm -rf $TARGET_DIR" aitvaras_exec_sudo "cp -r $backup_path $TARGET_DIR" fix_permissions start_service health_check disable_passwordless_sudo fi fi exit 1 fi else log_error "Failed to start service after production deployment" disable_passwordless_sudo exit 1 fi else log_success "Dry run completed - no changes made to production" echo "" log_info "To execute this deployment for real:" log_info " $0" fi } # ============================================================================= # MAIN SCRIPT LOGIC # ============================================================================= # Parse command line arguments DRY_RUN=false ROLLBACK=false FORCE=false while [[ $# -gt 0 ]]; do case $1 in --dry-run) DRY_RUN=true shift ;; --rollback) ROLLBACK=true shift ;; --force) FORCE=true shift ;; --help) usage exit 0 ;; *) log_error "Unknown option: $1" usage exit 1 ;; esac done # Main execution main() { log_production "Furt API Production Deployment Script - karl → aitvaras" log_info "$(date)" if [[ "$ROLLBACK" == "true" ]]; then rollback_deployment else deploy fi } # Trap for cleanup on exit cleanup() { local exit_code=$? # Always disable passwordless sudo (security!) disable_passwordless_sudo if [[ $exit_code -ne 0 && "$DRY_RUN" != "true" ]]; then log_error "Production deployment failed (exit code: $exit_code)" log_info "Check service status: ssh $AITVARAS_HOST \"sudo systemctl status $SERVICE_NAME\"" log_info "Check logs: ssh $AITVARAS_HOST \"sudo journalctl -u $SERVICE_NAME -n 20\"" fi } trap cleanup EXIT # Run main function main "$@"