furt/scripts/deploy/deploy_aitvaras.sh

967 lines
30 KiB
Bash
Raw Normal View History

#!/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 "$@"