2025-10-30 09:15:56 +00:00
#!/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 <environment> [-c <config-file>] [--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
2025-10-30 12:49:04 +00:00
print_error "yq is required for YAML parsing. Install with: apt-get install yq"
2025-10-30 09:15:56 +00:00
exit 1
fi
2025-10-30 12:49:04 +00:00
# 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 " )
2025-10-30 09:15:56 +00:00
2025-10-30 12:49:04 +00:00
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 " )
2025-10-30 09:15:56 +00:00
# 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
2025-11-01 10:28:25 +00:00
execute_remote "df -h /" "Checking disk space"
2025-10-30 09:15:56 +00:00
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"
2025-11-01 16:49:24 +00:00
# Start services - use environment-specific compose file if available
2025-11-01 16:02:26 +00:00
if [ [ " $ENVIRONMENT " = = "production" ] ] && execute_remote " cd $TARGET_DIR && test -f docker-compose.production.yml " "Checking for production compose file" 2>/dev/null; then
execute_remote " cd $TARGET_DIR && sudo docker-compose -f docker-compose.production.yml up -d " "Starting services with production configuration"
2025-11-01 16:49:24 +00:00
elif [ [ " $ENVIRONMENT " = = "test" ] ] && execute_remote " cd $TARGET_DIR && test -f docker-compose.test.yml " "Checking for test compose file" 2>/dev/null; then
execute_remote " cd $TARGET_DIR && sudo docker-compose -f docker-compose.test.yml up -d " "Starting services with test configuration"
2025-11-01 16:02:26 +00:00
else
execute_remote " cd $TARGET_DIR && sudo docker-compose up -d " "Starting services"
fi
2025-10-30 09:15:56 +00:00
# Wait for services to be ready
print_status "Waiting for services to start..."
2025-11-01 16:49:24 +00:00
# Determine health check port based on environment
local health_port = "8080"
if [ [ " $ENVIRONMENT " = = "test" ] ] ; then
health_port = "8081"
fi
2025-10-30 09:15:56 +00:00
for i in { 1..30} ; do
2025-11-01 16:49:24 +00:00
if execute_remote " curl -s http://localhost: $health_port /health > /dev/null " "Checking service health" 2>/dev/null; then
2025-10-30 09:15:56 +00:00
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" )
2025-11-01 16:49:24 +00:00
# Determine validation port based on environment
local validation_port = "8080"
if [ [ " $ENVIRONMENT " = = "test" ] ] ; then
validation_port = "8081"
fi
2025-10-30 09:15:56 +00:00
for endpoint in " ${ endpoints [@] } " ; do
2025-11-01 16:49:24 +00:00
if execute_remote " curl -s -f http://localhost: $validation_port $endpoint > /dev/null " " Testing endpoint: $endpoint " 2>/dev/null; then
2025-10-30 09:15:56 +00:00
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:"
2025-11-01 16:49:24 +00:00
# Determine port based on environment
local summary_port = "8080"
if [ [ " $ENVIRONMENT " = = "test" ] ] ; then
summary_port = "8081"
fi
echo " Dashboard: http:// $SSH_HOST : $summary_port /dashboard "
echo " REST API: http:// $SSH_HOST : $summary_port "
echo " Health Check: http:// $SSH_HOST : $summary_port /health "
2025-10-30 09:15:56 +00:00
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 " $@ "