From 9ea0cb43e49318d53997854c9fcb48e5610b8813 Mon Sep 17 00:00:00 2001 From: michael Date: Tue, 24 Jun 2025 11:04:19 +0200 Subject: [PATCH] fix(deploy): prevent environment config overwrite and fix SMTP_HOST variable - Fix create_environment_file() to preserve existing production config - Change SMTP_SERVER to SMTP_HOST for consistency with other configs - Add config existence check before creating new environment file - Preserve permissions on existing config files - Prevent accidental production config loss on redeployment Fixes #50 --- scripts/deploy/deploy_aitvaras.sh | 966 ++++++++++++++++++++++++++++++ 1 file changed, 966 insertions(+) create mode 100755 scripts/deploy/deploy_aitvaras.sh diff --git a/scripts/deploy/deploy_aitvaras.sh b/scripts/deploy/deploy_aitvaras.sh new file mode 100755 index 0000000..3220f6f --- /dev/null +++ b/scripts/deploy/deploy_aitvaras.sh @@ -0,0 +1,966 @@ +#!/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 "$@" +