diff --git a/.gitignore b/.gitignore index 8c13e6c..f60c0e8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,12 @@ Thumbs.db .env .env.local +# Deployment configuration +deploy/config/* +deploy/keys/* +!deploy/config/example*.yml +!deploy/keys/README.md + # Temporary files *.tmp *.temp \ No newline at end of file diff --git a/deploy/SSH_DEPLOYMENT.md b/deploy/SSH_DEPLOYMENT.md new file mode 100644 index 0000000..b026bb6 --- /dev/null +++ b/deploy/SSH_DEPLOYMENT.md @@ -0,0 +1,355 @@ +# SSH Deployment Guide + +This guide explains how to deploy the Calejo Control Adapter to remote servers using SSH. + +## 🚀 Quick Start + +### 1. Setup SSH Keys + +Generate and deploy SSH keys for each environment: + +```bash +# Generate production key +ssh-keygen -t ed25519 -f deploy/keys/production_key -C "calejo-production-deploy" -N "" + +# Deploy public key to production server +ssh-copy-id -i deploy/keys/production_key.pub calejo@production-server.company.com + +# Set proper permissions +chmod 600 deploy/keys/* +``` + +### 2. Create Configuration + +Copy the example configuration and customize: + +```bash +# For production +cp deploy/config/example-production.yml deploy/config/production.yml + +# Edit with your server details +nano deploy/config/production.yml +``` + +### 3. Deploy + +```bash +# Deploy to production +./deploy/ssh/deploy-remote.sh -e production + +# Dry run first +./deploy/ssh/deploy-remote.sh -e production --dry-run + +# Verbose output +./deploy/ssh/deploy-remote.sh -e production --verbose +``` + +## 📁 Configuration Structure + +``` +deploy/ +├── ssh/ +│ └── deploy-remote.sh # Main deployment script +├── config/ +│ ├── example-production.yml # Example production config +│ ├── example-staging.yml # Example staging config +│ ├── production.yml # Production config (gitignored) +│ └── staging.yml # Staging config (gitignored) +└── keys/ + ├── README.md # Key management guide + ├── production_key # Production SSH key (gitignored) + ├── production_key.pub # Production public key (gitignored) + ├── staging_key # Staging SSH key (gitignored) + └── staging_key.pub # Staging public key (gitignored) +``` + +## 🔧 Configuration Files + +### Production Configuration (`deploy/config/production.yml`) + +```yaml +# SSH Connection Details +ssh: + host: "production-server.company.com" + port: 22 + username: "calejo" + key_file: "deploy/keys/production_key" + +# Deployment Settings +deployment: + target_dir: "/opt/calejo-control-adapter" + backup_dir: "/var/backup/calejo" + log_dir: "/var/log/calejo" + config_dir: "/etc/calejo" + +# Application Configuration +app: + port: 8080 + host: "0.0.0.0" + debug: false +``` + +### Staging Configuration (`deploy/config/staging.yml`) + +```yaml +ssh: + host: "staging-server.company.com" + port: 22 + username: "calejo" + key_file: "deploy/keys/staging_key" + +deployment: + target_dir: "/opt/calejo-control-adapter" + backup_dir: "/var/backup/calejo" + log_dir: "/var/log/calejo" + config_dir: "/etc/calejo" +``` + +## 🔑 SSH Key Management + +### Generating Keys + +```bash +# Generate ED25519 key (recommended) +ssh-keygen -t ed25519 -f deploy/keys/production_key -C "calejo-production" -N "" + +# Generate RSA key (alternative) +ssh-keygen -t rsa -b 4096 -f deploy/keys/production_key -C "calejo-production" -N "" + +# Set secure permissions +chmod 600 deploy/keys/production_key +chmod 644 deploy/keys/production_key.pub +``` + +### Deploying Public Keys + +```bash +# Copy to remote server +ssh-copy-id -i deploy/keys/production_key.pub calejo@production-server.company.com + +# Manual method +cat deploy/keys/production_key.pub | ssh calejo@production-server.company.com 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys' +``` + +### Testing SSH Connection + +```bash +# Test connection +ssh -i deploy/keys/production_key calejo@production-server.company.com + +# Test with specific port +ssh -i deploy/keys/production_key -p 2222 calejo@production-server.company.com +``` + +## 🛠️ Deployment Script Usage + +### Basic Usage + +```bash +# Deploy to staging +./deploy/ssh/deploy-remote.sh -e staging + +# Deploy to production +./deploy/ssh/deploy-remote.sh -e production + +# Use custom config file +./deploy/ssh/deploy-remote.sh -e production -c deploy/config/custom.yml +``` + +### Advanced Options + +```bash +# Dry run (show what would be deployed) +./deploy/ssh/deploy-remote.sh -e production --dry-run + +# Verbose output +./deploy/ssh/deploy-remote.sh -e production --verbose + +# Help +./deploy/ssh/deploy-remote.sh --help +``` + +### Environment Variables + +You can also use environment variables for sensitive data: + +```bash +export CALEJO_DEPLOY_KEY_PATH="deploy/keys/production_key" +export CALEJO_DEPLOY_PASSPHRASE="your-passphrase" +``` + +## 🔄 Deployment Process + +The deployment script performs the following steps: + +1. **Configuration Validation** + - Loads environment configuration + - Validates SSH key and connection details + - Checks remote prerequisites + +2. **Remote Setup** + - Creates necessary directories + - Backs up existing deployment (if any) + - Transfers application files + +3. **Application Deployment** + - Sets up remote configuration + - Builds Docker images + - Starts services + - Waits for services to be ready + +4. **Validation** + - Runs deployment validation + - Tests key endpoints + - Generates deployment summary + +## 🔒 Security Best Practices + +### SSH Key Security + +- **Use different keys** for different environments +- **Set proper permissions**: `chmod 600` for private keys +- **Use passphrase-protected keys** in production +- **Rotate keys regularly** (every 6-12 months) +- **Never commit private keys** to version control + +### Server Security + +- **Use non-root user** for deployment +- **Configure sudo access** for specific commands only +- **Use firewall** to restrict SSH access +- **Enable fail2ban** for SSH protection +- **Use SSH key authentication only** (disable password auth) + +### Configuration Security + +- **Store sensitive data** in environment variables +- **Use encrypted configuration** for production +- **Regularly audit** access logs +- **Monitor deployment activities** + +## 🐛 Troubleshooting + +### Common Issues + +1. **SSH Connection Failed** + ```bash + # Check key permissions + chmod 600 deploy/keys/production_key + + # Test connection manually + ssh -i deploy/keys/production_key -v calejo@production-server.company.com + ``` + +2. **Permission Denied** + ```bash + # Ensure user has sudo access + ssh -i deploy/keys/production_key calejo@production-server.company.com 'sudo -v' + ``` + +3. **Docker Not Installed** + ```bash + # Check Docker installation + ssh -i deploy/keys/production_key calejo@production-server.company.com 'docker --version' + ``` + +4. **Port Already in Use** + ```bash + # Check running services + ssh -i deploy/keys/production_key calejo@production-server.company.com 'sudo netstat -tulpn | grep :8080' + ``` + +### Debug Mode + +Enable verbose output to see detailed execution: + +```bash +./deploy/ssh/deploy-remote.sh -e production --verbose +``` + +### Log Files + +- **Local logs**: Check script output +- **Remote logs**: `/var/log/calejo/` on target server +- **Docker logs**: `docker-compose logs` on target server + +## 🔄 Post-Deployment Tasks + +### Health Checks + +```bash +# Run health check +ssh -i deploy/keys/production_key calejo@production-server.company.com 'cd /opt/calejo-control-adapter && ./scripts/health-check.sh' + +# Check service status +ssh -i deploy/keys/production_key calejo@production-server.company.com 'cd /opt/calejo-control-adapter && docker-compose ps' +``` + +### Backup Setup + +```bash +# Create initial backup +ssh -i deploy/keys/production_key calejo@production-server.company.com 'cd /opt/calejo-control-adapter && ./scripts/backup-full.sh' + +# Schedule regular backups (add to crontab) +0 2 * * * /opt/calejo-control-adapter/scripts/backup-full.sh +``` + +### Monitoring Setup + +```bash +# Check monitoring +ssh -i deploy/keys/production_key calejo@production-server.company.com 'cd /opt/calejo-control-adapter && ./validate-deployment.sh' +``` + +## 📋 Deployment Checklist + +### Pre-Deployment +- [ ] SSH keys generated and deployed +- [ ] Configuration files created and tested +- [ ] Remote server prerequisites installed +- [ ] Backup strategy in place +- [ ] Deployment window scheduled + +### During Deployment +- [ ] Dry run completed successfully +- [ ] Backup of existing deployment created +- [ ] Application files transferred +- [ ] Services started successfully +- [ ] Health checks passed + +### Post-Deployment +- [ ] Application accessible via web interface +- [ ] API endpoints responding +- [ ] Monitoring configured +- [ ] Backup tested +- [ ] Documentation updated + +## 🎯 Best Practices + +### Deployment Strategy + +- **Use blue-green deployment** for zero downtime +- **Test in staging** before production +- **Rollback plan** in place +- **Monitor during deployment** + +### Configuration Management + +- **Version control** for configuration +- **Environment-specific** configurations +- **Sensitive data** in environment variables +- **Regular backups** of configuration + +### Security + +- **Least privilege** principle +- **Regular security updates** +- **Access logging** and monitoring +- **Incident response** plan + +--- + +**Deployment Status**: ✅ Production Ready +**Last Updated**: $(date) +**Version**: 1.0.0 \ No newline at end of file diff --git a/deploy/config/example-production.yml b/deploy/config/example-production.yml new file mode 100644 index 0000000..00eb128 --- /dev/null +++ b/deploy/config/example-production.yml @@ -0,0 +1,63 @@ +# Production Environment Configuration +# Copy this file to production.yml and update with actual values + +# SSH Connection Details +ssh: + host: "production-server.company.com" + port: 22 + username: "calejo" + key_file: "deploy/keys/production_key" + +# Deployment Settings +deployment: + target_dir: "/opt/calejo-control-adapter" + backup_dir: "/var/backup/calejo" + log_dir: "/var/log/calejo" + config_dir: "/etc/calejo" + +# Application Configuration +app: + port: 8080 + host: "0.0.0.0" + debug: false + +# Database Configuration +database: + host: "localhost" + port: 5432 + name: "calejo_production" + username: "calejo_user" + password: "${DB_PASSWORD}" # Will be replaced from environment + +# SCADA Integration +scada: + opcua_enabled: true + opcua_endpoint: "opc.tcp://scada-server:4840" + modbus_enabled: true + modbus_host: "scada-server" + modbus_port: 502 + +# Optimization Integration +optimization: + enabled: true + endpoint: "http://optimization-server:8081" + +# Security Settings +security: + enable_auth: true + enable_ssl: true + ssl_cert: "/etc/ssl/certs/calejo.crt" + ssl_key: "/etc/ssl/private/calejo.key" + +# Monitoring +monitoring: + prometheus_enabled: true + prometheus_port: 9090 + grafana_enabled: true + grafana_port: 3000 + +# Backup Settings +backup: + enabled: true + schedule: "0 2 * * *" # Daily at 2 AM + retention_days: 30 \ No newline at end of file diff --git a/deploy/config/example-staging.yml b/deploy/config/example-staging.yml new file mode 100644 index 0000000..ede7e79 --- /dev/null +++ b/deploy/config/example-staging.yml @@ -0,0 +1,61 @@ +# Staging Environment Configuration +# Copy this file to staging.yml and update with actual values + +# SSH Connection Details +ssh: + host: "staging-server.company.com" + port: 22 + username: "calejo" + key_file: "deploy/keys/staging_key" + +# Deployment Settings +deployment: + target_dir: "/opt/calejo-control-adapter" + backup_dir: "/var/backup/calejo" + log_dir: "/var/log/calejo" + config_dir: "/etc/calejo" + +# Application Configuration +app: + port: 8080 + host: "0.0.0.0" + debug: true + +# Database Configuration +database: + host: "localhost" + port: 5432 + name: "calejo_staging" + username: "calejo_user" + password: "${DB_PASSWORD}" # Will be replaced from environment + +# SCADA Integration +scada: + opcua_enabled: false + opcua_endpoint: "opc.tcp://localhost:4840" + modbus_enabled: false + modbus_host: "localhost" + modbus_port: 502 + +# Optimization Integration +optimization: + enabled: false + endpoint: "http://localhost:8081" + +# Security Settings +security: + enable_auth: false + enable_ssl: false + +# Monitoring +monitoring: + prometheus_enabled: true + prometheus_port: 9090 + grafana_enabled: true + grafana_port: 3000 + +# Backup Settings +backup: + enabled: true + schedule: "0 2 * * *" # Daily at 2 AM + retention_days: 7 \ No newline at end of file diff --git a/deploy/keys/README.md b/deploy/keys/README.md new file mode 100644 index 0000000..26d0152 --- /dev/null +++ b/deploy/keys/README.md @@ -0,0 +1,69 @@ +# SSH Key Management + +This directory should contain SSH private keys for deployment to different environments. + +## Setup Instructions + +### 1. Generate SSH Key Pairs + +For each environment, generate a dedicated SSH key pair: + +```bash +# Generate production key +ssh-keygen -t ed25519 -f deploy/keys/production_key -C "calejo-production-deploy" -N "" + +# Generate staging key +ssh-keygen -t ed25519 -f deploy/keys/staging_key -C "calejo-staging-deploy" -N "" + +# Set proper permissions +chmod 600 deploy/keys/* +``` + +### 2. Deploy Public Keys to Servers + +Copy the public keys to the target servers: + +```bash +# For production +ssh-copy-id -i deploy/keys/production_key.pub calejo@production-server.company.com + +# For staging +ssh-copy-id -i deploy/keys/staging_key.pub calejo@staging-server.company.com +``` + +### 3. Configure SSH on Servers + +On each server, ensure the deployment user has proper permissions: + +```bash +# Add to sudoers (if needed) +echo "calejo ALL=(ALL) NOPASSWD: /usr/bin/docker-compose, /bin/systemctl" | sudo tee /etc/sudoers.d/calejo +``` + +## Security Notes + +- **Never commit private keys** to version control +- **Set proper permissions**: `chmod 600 deploy/keys/*` +- **Use passphrase-protected keys** in production +- **Rotate keys regularly** +- **Use different keys** for different environments + +## File Structure + +``` +deploy/keys/ +├── README.md # This file +├── production_key # Production SSH private key (gitignored) +├── production_key.pub # Production SSH public key (gitignored) +├── staging_key # Staging SSH private key (gitignored) +└── staging_key.pub # Staging SSH public key (gitignored) +``` + +## Environment Variables + +For additional security, you can also use environment variables: + +```bash +export CALEJO_DEPLOY_KEY_PATH="deploy/keys/production_key" +export CALEJO_DEPLOY_PASSPHRASE="your-passphrase" +``` \ No newline at end of file diff --git a/deploy/ssh/deploy-remote.py b/deploy/ssh/deploy-remote.py new file mode 100755 index 0000000..1c4f1ba --- /dev/null +++ b/deploy/ssh/deploy-remote.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +Calejo Control Adapter - Python SSH Deployment Script +Alternative deployment script using Python for more complex deployments +""" + +import os +import sys +import yaml +import paramiko +import argparse +import tempfile +import tarfile +from pathlib import Path +from typing import Dict, Any + + +class SSHDeployer: + """SSH-based deployment manager""" + + def __init__(self, config_file: str): + self.config_file = config_file + self.config = self.load_config() + self.ssh_client = None + self.sftp_client = None + + def load_config(self) -> Dict[str, Any]: + """Load deployment configuration from YAML file""" + try: + with open(self.config_file, 'r') as f: + config = yaml.safe_load(f) + + # Validate required configuration + required = ['ssh.host', 'ssh.username', 'ssh.key_file'] + for req in required: + keys = req.split('.') + current = config + for key in keys: + if key not in current: + raise ValueError(f"Missing required configuration: {req}") + current = current[key] + + return config + + except Exception as e: + print(f"❌ Error loading configuration: {e}") + sys.exit(1) + + def connect(self) -> bool: + """Establish SSH connection""" + try: + ssh_config = self.config['ssh'] + + # Create SSH client + self.ssh_client = paramiko.SSHClient() + self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # Load private key + key_path = ssh_config['key_file'] + if not os.path.exists(key_path): + print(f"❌ SSH key file not found: {key_path}") + return False + + private_key = paramiko.Ed25519Key.from_private_key_file(key_path) + + # Connect + port = ssh_config.get('port', 22) + self.ssh_client.connect( + hostname=ssh_config['host'], + port=port, + username=ssh_config['username'], + pkey=private_key, + timeout=30 + ) + + # Create SFTP client + self.sftp_client = self.ssh_client.open_sftp() + + print(f"✅ Connected to {ssh_config['host']}") + return True + + except Exception as e: + print(f"❌ SSH connection failed: {e}") + return False + + def execute_remote(self, command: str, description: str = "") -> bool: + """Execute command on remote server""" + try: + if description: + print(f"🔧 {description}") + + stdin, stdout, stderr = self.ssh_client.exec_command(command) + exit_status = stdout.channel.recv_exit_status() + + if exit_status == 0: + if description: + print(f" ✅ {description} completed") + return True + else: + error_output = stderr.read().decode() + print(f" ❌ {description} failed: {error_output}") + return False + + except Exception as e: + print(f" ❌ {description} failed: {e}") + return False + + def transfer_file(self, local_path: str, remote_path: str, description: str = "") -> bool: + """Transfer file to remote server""" + try: + if description: + print(f"📁 {description}") + + self.sftp_client.put(local_path, remote_path) + + if description: + print(f" ✅ {description} completed") + return True + + except Exception as e: + print(f" ❌ {description} failed: {e}") + return False + + def create_deployment_package(self) -> str: + """Create deployment package excluding sensitive files""" + temp_dir = tempfile.mkdtemp() + package_path = os.path.join(temp_dir, "deployment.tar.gz") + + # Create tar.gz package + with tarfile.open(package_path, "w:gz") as tar: + # Add all files except deployment config and keys + for root, dirs, files in os.walk('.'): + # Skip deployment directories + if 'deploy/config' in root or 'deploy/keys' in root: + continue + + # Skip hidden directories + dirs[:] = [d for d in dirs if not d.startswith('.')] + + for file in files: + if not file.startswith('.'): + file_path = os.path.join(root, file) + arcname = os.path.relpath(file_path, '.') + tar.add(file_path, arcname=arcname) + + return package_path + + def deploy(self, dry_run: bool = False): + """Main deployment process""" + print("🚀 Starting SSH deployment...") + + if dry_run: + print("🔍 DRY RUN MODE - No changes will be made") + + # Connect to server + if not self.connect(): + return False + + try: + deployment_config = self.config['deployment'] + target_dir = deployment_config['target_dir'] + + # Check prerequisites + print("🔍 Checking prerequisites...") + if not self.execute_remote("command -v docker", "Checking Docker"): + return False + if not self.execute_remote("command -v docker-compose", "Checking Docker Compose"): + return False + + # Create directories + print("📁 Creating directories...") + dirs = [ + target_dir, + deployment_config.get('backup_dir', '/var/backup/calejo'), + deployment_config.get('log_dir', '/var/log/calejo'), + deployment_config.get('config_dir', '/etc/calejo') + ] + + for dir_path in dirs: + cmd = f"sudo mkdir -p {dir_path} && sudo chown {self.config['ssh']['username']}:{self.config['ssh']['username']} {dir_path}" + if not self.execute_remote(cmd, f"Creating {dir_path}"): + return False + + # Create deployment package + print("📦 Creating deployment package...") + package_path = self.create_deployment_package() + + if dry_run: + print(f" 📦 Would transfer package: {package_path}") + os.remove(package_path) + return True + + # Transfer package + remote_package_path = os.path.join(target_dir, "deployment.tar.gz") + if not self.transfer_file(package_path, remote_package_path, "Transferring deployment package"): + return False + + # Extract package + if not self.execute_remote(f"cd {target_dir} && tar -xzf deployment.tar.gz && rm deployment.tar.gz", "Extracting package"): + return False + + # Set permissions + if not self.execute_remote(f"chmod +x {target_dir}/scripts/*.sh", "Setting script permissions"): + return False + + # Build and start services + print("🐳 Building and starting services...") + if not self.execute_remote(f"cd {target_dir} && sudo docker-compose build", "Building Docker images"): + return False + if not self.execute_remote(f"cd {target_dir} && sudo docker-compose up -d", "Starting services"): + return False + + # Wait for services + print("⏳ Waiting for services to start...") + for i in range(30): + if self.execute_remote("curl -s http://localhost:8080/health > /dev/null", "", silent=True): + print(" ✅ Services started successfully") + break + print(f" ⏳ Waiting... ({i+1}/30)") + import time + time.sleep(2) + else: + print(" ❌ Services failed to start within 60 seconds") + return False + + # Validate deployment + print("🔍 Validating deployment...") + self.execute_remote(f"cd {target_dir} && ./validate-deployment.sh", "Running validation") + + print("🎉 Deployment completed successfully!") + return True + + finally: + # Cleanup + if hasattr(self, 'package_path') and os.path.exists(self.package_path): + os.remove(self.package_path) + + # Close connections + if self.sftp_client: + self.sftp_client.close() + if self.ssh_client: + self.ssh_client.close() + + +def main(): + """Main function""" + parser = argparse.ArgumentParser(description='Calejo Control Adapter - SSH Deployment') + parser.add_argument('-c', '--config', required=True, help='Deployment configuration file') + parser.add_argument('--dry-run', action='store_true', help='Dry run mode') + + args = parser.parse_args() + + # Check if config file exists + if not os.path.exists(args.config): + print(f"❌ Configuration file not found: {args.config}") + sys.exit(1) + + # Run deployment + deployer = SSHDeployer(args.config) + success = deployer.deploy(dry_run=args.dry_run) + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/deploy/ssh/deploy-remote.sh b/deploy/ssh/deploy-remote.sh new file mode 100755 index 0000000..bb9d495 --- /dev/null +++ b/deploy/ssh/deploy-remote.sh @@ -0,0 +1,454 @@ +#!/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: snap install yq or brew install yq" + exit 1 + fi + + # Extract configuration values + SSH_HOST=$(yq e '.ssh.host' "$CONFIG_FILE") + SSH_PORT=$(yq e '.ssh.port' "$CONFIG_FILE") + SSH_USERNAME=$(yq e '.ssh.username' "$CONFIG_FILE") + SSH_KEY_FILE=$(yq e '.ssh.key_file' "$CONFIG_FILE") + + TARGET_DIR=$(yq e '.deployment.target_dir' "$CONFIG_FILE") + BACKUP_DIR=$(yq e '.deployment.backup_dir' "$CONFIG_FILE") + LOG_DIR=$(yq e '.deployment.log_dir' "$CONFIG_FILE") + CONFIG_DIR=$(yq e '.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 / | awk 'NR==2 {print \$5}'" "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 "$@" \ No newline at end of file