#!/bin/bash # Calejo Control Adapter - Remote SSH Deployment Script # Deploys the application to remote servers over SSH set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Default configuration CONFIG_FILE="" ENVIRONMENT="" DRY_RUN=false VERBOSE=false # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Function to display usage usage() { echo "Usage: $0 -e [-c ] [--dry-run] [--verbose]" echo "" echo "Options:" echo " -e, --environment Deployment environment (production, staging)" echo " -c, --config Custom configuration file" echo " --dry-run Show what would be deployed without actually deploying" echo " --verbose Enable verbose output" echo " -h, --help Show this help message" echo "" echo "Examples:" echo " $0 -e staging # Deploy to staging" echo " $0 -e production --dry-run # Dry run for production" echo " $0 -e production -c custom.yml # Use custom config" } # Function to parse command line arguments parse_arguments() { while [[ $# -gt 0 ]]; do case $1 in -e|--environment) ENVIRONMENT="$2" shift 2 ;; -c|--config) CONFIG_FILE="$2" shift 2 ;; --dry-run) DRY_RUN=true shift ;; --verbose) VERBOSE=true shift ;; -h|--help) usage exit 0 ;; *) print_error "Unknown option: $1" usage exit 1 ;; esac done # Validate required arguments if [[ -z "$ENVIRONMENT" ]]; then print_error "Environment is required" usage exit 1 fi # Set default config file if not provided if [[ -z "$CONFIG_FILE" ]]; then CONFIG_FILE="deploy/config/${ENVIRONMENT}.yml" fi # Validate config file exists if [[ ! -f "$CONFIG_FILE" ]]; then print_error "Configuration file not found: $CONFIG_FILE" echo "Available configurations:" ls -1 deploy/config/*.yml 2>/dev/null | sed 's|deploy/config/||' || echo " (none)" exit 1 fi } # Function to load configuration load_configuration() { print_status "Loading configuration from: $CONFIG_FILE" # Check if yq is available for YAML parsing if ! command -v yq &> /dev/null; then print_error "yq is required for YAML parsing. Install with: apt-get install yq" exit 1 fi # Extract configuration values (yq with jq syntax) SSH_HOST=$(yq -r '.ssh.host' "$CONFIG_FILE") SSH_PORT=$(yq -r '.ssh.port' "$CONFIG_FILE") SSH_USERNAME=$(yq -r '.ssh.username' "$CONFIG_FILE") SSH_KEY_FILE=$(yq -r '.ssh.key_file' "$CONFIG_FILE") TARGET_DIR=$(yq -r '.deployment.target_dir' "$CONFIG_FILE") BACKUP_DIR=$(yq -r '.deployment.backup_dir' "$CONFIG_FILE") LOG_DIR=$(yq -r '.deployment.log_dir' "$CONFIG_FILE") CONFIG_DIR=$(yq -r '.deployment.config_dir' "$CONFIG_FILE") # Validate required configuration if [[ -z "$SSH_HOST" || -z "$SSH_USERNAME" || -z "$SSH_KEY_FILE" ]]; then print_error "Missing required SSH configuration in $CONFIG_FILE" exit 1 fi # Validate SSH key file exists if [[ ! -f "$SSH_KEY_FILE" ]]; then print_error "SSH key file not found: $SSH_KEY_FILE" echo "Available keys:" ls -1 deploy/keys/ 2>/dev/null || echo " (none)" exit 1 fi # Set default port if not specified if [[ -z "$SSH_PORT" ]]; then SSH_PORT=22 fi if [[ "$VERBOSE" == "true" ]]; then print_status "Configuration loaded:" echo " SSH Host: $SSH_HOST" echo " SSH Port: $SSH_PORT" echo " SSH Username: $SSH_USERNAME" echo " SSH Key: $SSH_KEY_FILE" echo " Target Directory: $TARGET_DIR" fi } # Function to build SSH command build_ssh_command() { local cmd="$1" local ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30" if [[ "$VERBOSE" == "true" ]]; then ssh_opts="$ssh_opts -v" fi echo "ssh -i $SSH_KEY_FILE -p $SSH_PORT $ssh_opts $SSH_USERNAME@$SSH_HOST '$cmd'" } # Function to execute remote command execute_remote() { local cmd="$1" local description="$2" print_status "$description" if [[ "$DRY_RUN" == "true" ]]; then echo " [DRY RUN] Would execute: $cmd" return 0 fi local ssh_cmd=$(build_ssh_command "$cmd") if [[ "$VERBOSE" == "true" ]]; then echo " Executing: $ssh_cmd" fi if eval "$ssh_cmd"; then print_success "$description completed" return 0 else print_error "$description failed" return 1 fi } # Function to transfer files transfer_files() { local local_path="$1" local remote_path="$2" local description="$3" print_status "$description" if [[ "$DRY_RUN" == "true" ]]; then echo " [DRY RUN] Would transfer: $local_path -> $SSH_USERNAME@$SSH_HOST:$remote_path" return 0 fi local scp_cmd="scp -i $SSH_KEY_FILE -P $SSH_PORT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -r $local_path $SSH_USERNAME@$SSH_HOST:$remote_path" if [[ "$VERBOSE" == "true" ]]; then echo " Executing: $scp_cmd" fi if eval "$scp_cmd"; then print_success "$description completed" return 0 else print_error "$description failed" return 1 fi } # Function to check remote prerequisites check_remote_prerequisites() { print_status "Checking remote prerequisites..." # Check Docker execute_remote "command -v docker" "Checking Docker installation" || { print_error "Docker is not installed on remote server" return 1 } # Check Docker Compose execute_remote "command -v docker-compose" "Checking Docker Compose installation" || { print_error "Docker Compose is not installed on remote server" return 1 } # Check disk space execute_remote "df -h /" "Checking disk space" print_success "Remote prerequisites check passed" } # Function to create remote directories create_remote_directories() { print_status "Creating remote directories..." local dirs=("$TARGET_DIR" "$BACKUP_DIR" "$LOG_DIR" "$CONFIG_DIR") for dir in "${dirs[@]}"; do execute_remote "sudo mkdir -p $dir && sudo chown $SSH_USERNAME:$SSH_USERNAME $dir" "Creating directory: $dir" done print_success "Remote directories created" } # Function to backup existing deployment backup_existing_deployment() { print_status "Checking for existing deployment..." # Check if target directory exists and has content if execute_remote "[ -d $TARGET_DIR ] && [ \"$(ls -A $TARGET_DIR)\" ]" "Checking existing deployment" 2>/dev/null; then print_warning "Existing deployment found, creating backup..." local timestamp=$(date +%Y%m%d_%H%M%S) local backup_file="calejo-backup-$timestamp.tar.gz" execute_remote "cd $TARGET_DIR && tar -czf $BACKUP_DIR/$backup_file ." "Creating backup: $backup_file" print_success "Backup created: $BACKUP_DIR/$backup_file" else print_status "No existing deployment found" fi } # Function to transfer application files transfer_application() { print_status "Transferring application files..." # Create temporary deployment package local temp_dir=$(mktemp -d) local package_name="calejo-deployment-$(date +%Y%m%d_%H%M%S).tar.gz" # Copy files to temporary directory (excluding deployment config and keys) print_status "Creating deployment package..." cp -r . "$temp_dir/" # Remove sensitive deployment files from package rm -rf "$temp_dir/deploy/config" rm -rf "$temp_dir/deploy/keys" # Create package cd "$temp_dir" && tar -czf "/tmp/$package_name" . && cd - > /dev/null # Transfer package transfer_files "/tmp/$package_name" "$TARGET_DIR/" "Transferring deployment package" # Extract package on remote execute_remote "cd $TARGET_DIR && tar -xzf $package_name && rm $package_name" "Extracting deployment package" # Clean up rm -rf "$temp_dir" rm -f "/tmp/$package_name" print_success "Application files transferred" } # Function to setup remote configuration setup_remote_configuration() { print_status "Setting up remote configuration..." # Transfer configuration files if [[ -f "config/settings.py" ]]; then transfer_files "config/settings.py" "$CONFIG_DIR/" "Transferring configuration file" fi # Set permissions on scripts execute_remote "chmod +x $TARGET_DIR/scripts/*.sh" "Setting script permissions" execute_remote "chmod +x $TARGET_DIR/deploy-onprem.sh" "Setting deployment script permissions" print_success "Remote configuration setup completed" } # Function to build and start services build_and_start_services() { print_status "Building and starting services..." # Build services execute_remote "cd $TARGET_DIR && sudo docker-compose build" "Building Docker images" # Start services execute_remote "cd $TARGET_DIR && sudo docker-compose up -d" "Starting services" # Wait for services to be ready print_status "Waiting for services to start..." for i in {1..30}; do if execute_remote "curl -s http://localhost:8080/health > /dev/null" "Checking service health" 2>/dev/null; then print_success "Services started successfully" break fi echo " Waiting... (attempt $i/30)" sleep 2 if [[ $i -eq 30 ]]; then print_error "Services failed to start within 60 seconds" execute_remote "cd $TARGET_DIR && sudo docker-compose logs" "Checking service logs" return 1 fi done } # Function to validate deployment validate_deployment() { print_status "Validating deployment..." # Run remote validation script if execute_remote "cd $TARGET_DIR && ./validate-deployment.sh" "Running deployment validation" 2>/dev/null; then print_success "Deployment validation passed" else print_warning "Deployment validation completed with warnings" fi # Test key endpoints local endpoints=("/health" "/dashboard" "/api/v1/status") for endpoint in "${endpoints[@]}"; do if execute_remote "curl -s -f http://localhost:8080$endpoint > /dev/null" "Testing endpoint: $endpoint" 2>/dev/null; then print_success "Endpoint $endpoint is accessible" else print_error "Endpoint $endpoint is not accessible" fi done } # Function to display deployment summary display_deployment_summary() { print_success "Deployment to $ENVIRONMENT completed successfully!" echo "" echo "==================================================" echo " DEPLOYMENT SUMMARY" echo "==================================================" echo "" echo "🌍 Environment: $ENVIRONMENT" echo "🏠 Server: $SSH_HOST" echo "📁 Application: $TARGET_DIR" echo "" echo "🔗 Access URLs:" echo " Dashboard: http://$SSH_HOST:8080/dashboard" echo " REST API: http://$SSH_HOST:8080" echo " Health Check: http://$SSH_HOST:8080/health" echo "" echo "🔧 Management Commands:" echo " View logs: ssh -i $SSH_KEY_FILE $SSH_USERNAME@$SSH_HOST 'cd $TARGET_DIR && docker-compose logs -f'" echo " Health check: ssh -i $SSH_KEY_FILE $SSH_USERNAME@$SSH_HOST 'cd $TARGET_DIR && ./scripts/health-check.sh'" echo " Backup: ssh -i $SSH_KEY_FILE $SSH_USERNAME@$SSH_HOST 'cd $TARGET_DIR && ./scripts/backup-full.sh'" echo "" echo "==================================================" } # Main deployment function main() { echo "" echo "🚀 Calejo Control Adapter - Remote SSH Deployment" echo "==================================================" echo "" # Parse command line arguments parse_arguments "$@" # Load configuration load_configuration # Display deployment info echo "Deploying to: $ENVIRONMENT" echo "Server: $SSH_HOST" echo "Config: $CONFIG_FILE" if [[ "$DRY_RUN" == "true" ]]; then echo "Mode: DRY RUN (no changes will be made)" fi echo "" # Check remote prerequisites check_remote_prerequisites # Create remote directories create_remote_directories # Backup existing deployment backup_existing_deployment # Transfer application files transfer_application # Setup remote configuration setup_remote_configuration # Build and start services build_and_start_services # Validate deployment validate_deployment # Display summary display_deployment_summary echo "" print_success "Remote deployment completed!" } # Run main function main "$@"