CalejoControl/deploy/ssh/deploy-remote.sh

478 lines
15 KiB
Bash

#!/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
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 - use environment-specific compose file if available
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"
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"
else
execute_remote "cd $TARGET_DIR && sudo docker-compose up -d" "Starting services"
fi
# Wait for services to be ready
print_status "Waiting for services to start..."
# Determine health check port based on environment
local health_port="8080"
if [[ "$ENVIRONMENT" == "test" ]]; then
health_port="8081"
fi
for i in {1..30}; do
if execute_remote "curl -s http://localhost:$health_port/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")
# Determine validation port based on environment
local validation_port="8080"
if [[ "$ENVIRONMENT" == "test" ]]; then
validation_port="8081"
fi
for endpoint in "${endpoints[@]}"; do
if execute_remote "curl -s -f http://localhost:$validation_port$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:"
# 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"
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 "$@"