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
This commit is contained in:
parent
0abc9791d3
commit
9ea0cb43e4
1 changed files with 966 additions and 0 deletions
966
scripts/deploy/deploy_aitvaras.sh
Executable file
966
scripts/deploy/deploy_aitvaras.sh
Executable file
|
|
@ -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 "$@"
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue