| |
| """ |
| Production Deployment Configuration |
| Advanced Workflow Automation - Production Readiness |
| |
| This script implements: |
| - Production configuration management |
| - Environment setup and validation |
| - Database configuration and migration |
| - Security configuration for production |
| - Monitoring and logging setup |
| - Deployment automation |
| """ |
|
|
| from dataclasses import dataclass, field |
| from datetime import datetime |
| import json |
| import logging |
| import os |
| from pathlib import Path |
| import sys |
| from typing import Any, Dict, List, Optional |
| import uuid |
| import yaml |
|
|
| |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @dataclass |
| class ProductionConfig: |
| """Production configuration settings""" |
| environment: str = "production" |
| debug: bool = False |
| log_level: str = "INFO" |
| |
| |
| database_url: str = "" |
| database_pool_size: int = 20 |
| database_max_overflow: int = 30 |
| database_pool_timeout: int = 30 |
| database_pool_recycle: int = 3600 |
| |
| |
| redis_url: str = "" |
| redis_db: int = 0 |
| redis_password: Optional[str] = None |
| redis_max_connections: int = 100 |
| |
| |
| websocket_host: str = "0.0.0.0" |
| websocket_port: int = 8765 |
| websocket_ssl_enabled: bool = True |
| websocket_cert_file: str = "" |
| websocket_key_file: str = "" |
| |
| |
| secret_key: str = "" |
| jwt_secret_key: str = "" |
| jwt_expiration_hours: int = 24 |
| session_timeout_minutes: int = 30 |
| cors_origins: List[str] = field(default_factory=list) |
| rate_limit_enabled: bool = True |
| rate_limit_requests: int = 1000 |
| rate_limit_window_minutes: int = 60 |
| |
| |
| prometheus_enabled: bool = True |
| prometheus_port: int = 9090 |
| health_check_enabled: bool = True |
| health_check_port: int = 8080 |
| metrics_collection_enabled: bool = True |
| log_analytics_enabled: bool = True |
| |
| |
| max_concurrent_workflows: int = 1000 |
| workflow_timeout_minutes: int = 60 |
| task_queue_max_size: int = 10000 |
| cache_ttl_seconds: int = 3600 |
| |
| |
| gmail_api_key: str = "" |
| slack_api_key: str = "" |
| github_api_key: str = "" |
| asana_api_key: str = "" |
| trello_api_key: str = "" |
| |
| |
| backup_enabled: bool = True |
| backup_schedule_hours: int = 24 |
| backup_retention_days: int = 30 |
| auto_recovery_enabled: bool = True |
| |
| |
| smtp_server: str = "" |
| smtp_port: int = 587 |
| smtp_username: str = "" |
| smtp_password: str = "" |
| smtp_use_tls: bool = True |
|
|
|
|
| class ProductionDeploymentManager: |
| """Manages production deployment and configuration""" |
| |
| def __init__(self): |
| self.config = ProductionConfig() |
| self.deployment_path = Path("/opt/atom/production") |
| self.config_path = self.deployment_path / "config" |
| self.logs_path = self.deployment_path / "logs" |
| self.backups_path = self.deployment_path / "backups" |
| |
| def setup_production_environment(self) -> Dict[str, Any]: |
| """Setup production environment""" |
| try: |
| print("🚀 Setting Up Production Environment") |
| print("=" * 60) |
| |
| |
| self._create_directory_structure() |
| |
| |
| self._generate_configuration_files() |
| |
| |
| self._setup_security_configuration() |
| |
| |
| self._setup_monitoring_configuration() |
| |
| |
| self._setup_database_configuration() |
| |
| |
| self._create_deployment_scripts() |
| |
| |
| self._setup_health_checks() |
| |
| print("✅ Production environment setup completed") |
| return {"success": True, "message": "Production environment configured successfully"} |
| |
| except Exception as e: |
| logger.error(f"Error setting up production environment: {str(e)}") |
| return {"success": False, "error": str(e)} |
| |
| def _create_directory_structure(self): |
| """Create production directory structure""" |
| print("\n📁 Creating Directory Structure...") |
| |
| directories = [ |
| self.deployment_path, |
| self.config_path, |
| self.logs_path, |
| self.backups_path, |
| self.deployment_path / "scripts", |
| self.deployment_path / "ssl", |
| self.deployment_path / "data", |
| self.deployment_path / "temp" |
| ] |
| |
| for directory in directories: |
| directory.mkdir(parents=True, exist_ok=True) |
| print(f" ✅ Created: {directory}") |
| |
| def _generate_configuration_files(self): |
| """Generate production configuration files""" |
| print("\n⚙️ Generating Configuration Files...") |
| |
| |
| config_data = { |
| "environment": self.config.environment, |
| "debug": self.config.debug, |
| "log_level": self.config.log_level, |
| |
| "database": { |
| "url": self.config.database_url, |
| "pool_size": self.config.database_pool_size, |
| "max_overflow": self.config.database_max_overflow, |
| "pool_timeout": self.config.database_pool_timeout, |
| "pool_recycle": self.config.database_pool_recycle |
| }, |
| |
| "redis": { |
| "url": self.config.redis_url, |
| "db": self.config.redis_db, |
| "password": self.config.redis_password, |
| "max_connections": self.config.redis_max_connections |
| }, |
| |
| "websocket": { |
| "host": self.config.websocket_host, |
| "port": self.config.websocket_port, |
| "ssl_enabled": self.config.websocket_ssl_enabled, |
| "cert_file": self.config.websocket_cert_file, |
| "key_file": self.config.websocket_key_file |
| }, |
| |
| "security": { |
| "secret_key": self.config.secret_key or str(uuid.uuid4()), |
| "jwt_secret_key": self.config.jwt_secret_key or str(uuid.uuid4()), |
| "jwt_expiration_hours": self.config.jwt_expiration_hours, |
| "session_timeout_minutes": self.config.session_timeout_minutes, |
| "cors_origins": self.config.cors_origins, |
| "rate_limit_enabled": self.config.rate_limit_enabled, |
| "rate_limit_requests": self.config.rate_limit_requests, |
| "rate_limit_window_minutes": self.config.rate_limit_window_minutes |
| }, |
| |
| "monitoring": { |
| "prometheus_enabled": self.config.prometheus_enabled, |
| "prometheus_port": self.config.prometheus_port, |
| "health_check_enabled": self.config.health_check_enabled, |
| "health_check_port": self.config.health_check_port, |
| "metrics_collection_enabled": self.config.metrics_collection_enabled, |
| "log_analytics_enabled": self.config.log_analytics_enabled |
| }, |
| |
| "performance": { |
| "max_concurrent_workflows": self.config.max_concurrent_workflows, |
| "workflow_timeout_minutes": self.config.workflow_timeout_minutes, |
| "task_queue_max_size": self.config.task_queue_max_size, |
| "cache_ttl_seconds": self.config.cache_ttl_seconds |
| }, |
| |
| "backup": { |
| "enabled": self.config.backup_enabled, |
| "schedule_hours": self.config.backup_schedule_hours, |
| "retention_days": self.config.backup_retention_days, |
| "auto_recovery_enabled": self.config.auto_recovery_enabled |
| } |
| } |
| |
| |
| config_file = self.config_path / "production.yaml" |
| with open(config_file, 'w') as f: |
| yaml.dump(config_data, f, default_flow_style=False) |
| print(f" ✅ Created: {config_file}") |
| |
| |
| json_config_file = self.config_path / "production.json" |
| with open(json_config_file, 'w') as f: |
| json.dump(config_data, f, indent=2) |
| print(f" ✅ Created: {json_config_file}") |
| |
| |
| env_file = self.config_path / ".env" |
| env_content = f""" |
| # Production Environment Variables |
| ATOM_ENV={self.config.environment} |
| ATOM_DEBUG={self.config.debug} |
| ATOM_LOG_LEVEL={self.config.log_level} |
| |
| # Database |
| DATABASE_URL={self.config.database_url} |
| DATABASE_POOL_SIZE={self.config.database_pool_size} |
| |
| # Redis |
| REDIS_URL={self.config.redis_url} |
| REDIS_DB={self.config.redis_db} |
| |
| # WebSocket |
| WEBSOCKET_HOST={self.config.websocket_host} |
| WEBSOCKET_PORT={self.config.websocket_port} |
| WEBSOCKET_SSL_ENABLED={self.config.websocket_ssl_enabled} |
| |
| # Security |
| SECRET_KEY={config_data['security']['secret_key']} |
| JWT_SECRET_KEY={config_data['security']['jwt_secret_key']} |
| JWT_EXPIRATION_HOURS={self.config.jwt_expiration_hours} |
| |
| # Monitoring |
| PROMETHEUS_ENABLED={self.config.prometheus_enabled} |
| PROMETHEUS_PORT={self.config.prometheus_port} |
| HEALTH_CHECK_ENABLED={self.config.health_check_enabled} |
| HEALTH_CHECK_PORT={self.config.health_check_port} |
| |
| # Performance |
| MAX_CONCURRENT_WORKFLOWS={self.config.max_concurrent_workflows} |
| WORKFLOW_TIMEOUT_MINUTES={self.config.workflow_timeout_minutes} |
| |
| # Backup |
| BACKUP_ENABLED={self.config.backup_enabled} |
| BACKUP_SCHEDULE_HOURS={self.config.backup_schedule_hours} |
| BACKUP_RETENTION_DAYS={self.config.backup_retention_days} |
| """ |
| |
| with open(env_file, 'w') as f: |
| f.write(env_content.strip()) |
| print(f" ✅ Created: {env_file}") |
| |
| def _setup_security_configuration(self): |
| """Setup security configuration""" |
| print("\n🔒 Setting Up Security Configuration...") |
| |
| |
| ssl_config = { |
| "country": "US", |
| "state": "California", |
| "locality": "San Francisco", |
| "organization": "Atom Workflow Automation", |
| "common_name": "localhost", |
| "email": "noreply@atom.com" |
| } |
| |
| ssl_config_file = self.config_path / "ssl_config.json" |
| with open(ssl_config_file, 'w') as f: |
| json.dump(ssl_config, f, indent=2) |
| print(f" ✅ Created: {ssl_config_file}") |
| |
| |
| nginx_config = """ |
| server { |
| listen 80; |
| server_name localhost; |
| return 301 https://$server_name$request_uri; |
| } |
| |
| server { |
| listen 443 ssl http2; |
| server_name localhost; |
| |
| ssl_certificate /opt/atom/production/ssl/cert.pem; |
| ssl_certificate_key /opt/atom/production/ssl/key.pem; |
| ssl_protocols TLSv1.2 TLSv1.3; |
| ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; |
| ssl_prefer_server_ciphers off; |
| ssl_session_cache shared:SSL:10m; |
| ssl_session_timeout 10m; |
| |
| # WebSocket proxy |
| location /ws { |
| proxy_pass http://localhost:8765; |
| proxy_http_version 1.1; |
| proxy_set_header Upgrade $http_upgrade; |
| proxy_set_header Connection "upgrade"; |
| proxy_set_header Host $host; |
| proxy_set_header X-Real-IP $remote_addr; |
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| proxy_set_header X-Forwarded-Proto $scheme; |
| |
| # WebSocket specific headers |
| proxy_read_timeout 86400s; |
| proxy_send_timeout 86400s; |
| } |
| |
| # API proxy |
| location /api { |
| proxy_pass http://localhost:8000; |
| proxy_set_header Host $host; |
| proxy_set_header X-Real-IP $remote_addr; |
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| proxy_set_header X-Forwarded-Proto $scheme; |
| } |
| |
| # Health check |
| location /health { |
| proxy_pass http://localhost:8080; |
| access_log off; |
| } |
| |
| # Static files |
| location /static { |
| alias /opt/atom/production/static; |
| expires 1y; |
| add_header Cache-Control "public, immutable"; |
| } |
| |
| # Security headers |
| add_header X-Frame-Options "SAMEORIGIN" always; |
| add_header X-XSS-Protection "1; mode=block" always; |
| add_header X-Content-Type-Options "nosniff" always; |
| add_header Referrer-Policy "no-referrer-when-downgrade" always; |
| add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; |
| } |
| """ |
| |
| nginx_config_file = self.config_path / "nginx.conf" |
| with open(nginx_config_file, 'w') as f: |
| f.write(nginx_config.strip()) |
| print(f" ✅ Created: {nginx_config_file}") |
| |
| |
| security_policies = { |
| "password_policy": { |
| "min_length": 12, |
| "require_uppercase": True, |
| "require_lowercase": True, |
| "require_numbers": True, |
| "require_symbols": True, |
| "max_age_days": 90 |
| }, |
| "session_policy": { |
| "timeout_minutes": 30, |
| "max_concurrent_sessions": 3, |
| "require_reauth_minutes": 60 |
| }, |
| "api_policy": { |
| "rate_limit_per_minute": 100, |
| "rate_limit_per_hour": 1000, |
| "max_request_size_mb": 10, |
| "allowed_methods": ["GET", "POST", "PUT", "DELETE", "PATCH"], |
| "cors_policy": { |
| "allowed_origins": ["https://localhost"], |
| "allowed_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"], |
| "allowed_headers": ["Authorization", "Content-Type", "X-Requested-With"], |
| "max_age_seconds": 3600 |
| } |
| } |
| } |
| |
| security_policies_file = self.config_path / "security_policies.json" |
| with open(security_policies_file, 'w') as f: |
| json.dump(security_policies, f, indent=2) |
| print(f" ✅ Created: {security_policies_file}") |
| |
| def _setup_monitoring_configuration(self): |
| """Setup monitoring and logging configuration""" |
| print("\n📊 Setting Up Monitoring Configuration...") |
| |
| |
| prometheus_config = """ |
| global: |
| scrape_interval: 15s |
| evaluation_interval: 15s |
| |
| rule_files: |
| - "workflow_alerts.yml" |
| |
| alerting: |
| alertmanagers: |
| - static_configs: |
| - targets: |
| - alertmanager:9093 |
| |
| scrape_configs: |
| - job_name: 'atom-workflow-api' |
| static_configs: |
| - targets: ['localhost:8000'] |
| metrics_path: '/metrics' |
| scrape_interval: 30s |
| |
| - job_name: 'atom-websocket-server' |
| static_configs: |
| - targets: ['localhost:8765'] |
| metrics_path: '/metrics' |
| scrape_interval: 30s |
| |
| - job_name: 'atom-health-checks' |
| static_configs: |
| - targets: ['localhost:8080'] |
| metrics_path: '/metrics' |
| scrape_interval: 60s |
| |
| - job_name: 'node-exporter' |
| static_configs: |
| - targets: ['localhost:9100'] |
| |
| - job_name: 'redis-exporter' |
| static_configs: |
| - targets: ['localhost:9121'] |
| |
| - job_name: 'postgres-exporter' |
| static_configs: |
| - targets: ['localhost:9187'] |
| """ |
| |
| prometheus_config_file = self.config_path / "prometheus.yml" |
| with open(prometheus_config_file, 'w') as f: |
| f.write(prometheus_config.strip()) |
| print(f" ✅ Created: {prometheus_config_file}") |
| |
| |
| workflow_alerts = """ |
| groups: |
| - name: workflow_alerts |
| rules: |
| - alert: WorkflowExecutionFailure |
| expr: workflow_execution_failures_total > 0 |
| for: 5m |
| labels: |
| severity: warning |
| annotations: |
| summary: "Workflow execution failed" |
| description: "Workflow {{ $labels.workflow_id }} has failed {{ $value }} times in the last 5 minutes" |
| |
| - alert: HighWorkflowExecutionTime |
| expr: workflow_execution_duration_seconds > 300 |
| for: 10m |
| labels: |
| severity: warning |
| annotations: |
| summary: "High workflow execution time" |
| description: "Workflow {{ $labels.workflow_id }} has been running for {{ $value }} seconds" |
| |
| - alert: WebSocketConnectionFailure |
| expr: websocket_connection_errors_total > 10 |
| for: 2m |
| labels: |
| severity: critical |
| annotations: |
| summary: "High WebSocket connection errors" |
| description: "{{ $value }} WebSocket connection errors in the last 2 minutes" |
| |
| - alert: DatabaseConnectionFailure |
| expr: up{job="postgres-exporter"} == 0 |
| for: 1m |
| labels: |
| severity: critical |
| annotations: |
| summary: "Database connection failed" |
| description: "Database is down for more than 1 minute" |
| |
| - alert: RedisConnectionFailure |
| expr: up{job="redis-exporter"} == 0 |
| for: 1m |
| labels: |
| severity: critical |
| annotations: |
| summary: "Redis connection failed" |
| description: "Redis is down for more than 1 minute" |
| |
| - alert: HighMemoryUsage |
| expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.8 |
| for: 5m |
| labels: |
| severity: warning |
| annotations: |
| summary: "High memory usage" |
| description: "Memory usage is {{ $value | humanizePercentage }}" |
| |
| - alert: HighCPUUsage |
| expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 |
| for: 10m |
| labels: |
| severity: warning |
| annotations: |
| summary: "High CPU usage" |
| description: "CPU usage is {{ $value | humanizePercentage }}" |
| """ |
| |
| workflow_alerts_file = self.config_path / "workflow_alerts.yml" |
| with open(workflow_alerts_file, 'w') as f: |
| f.write(workflow_alerts.strip()) |
| print(f" ✅ Created: {workflow_alerts_file}") |
| |
| |
| grafana_dashboard = { |
| "dashboard": { |
| "id": None, |
| "title": "Atom Workflow Automation Dashboard", |
| "tags": ["atom", "workflow", "automation"], |
| "timezone": "browser", |
| "panels": [ |
| { |
| "id": 1, |
| "title": "Workflow Executions", |
| "type": "graph", |
| "targets": [ |
| { |
| "expr": "rate(workflow_executions_total[5m])", |
| "legendFormat": "Executions/sec" |
| }, |
| { |
| "expr": "rate(workflow_execution_failures_total[5m])", |
| "legendFormat": "Failures/sec" |
| } |
| ], |
| "yAxes": [ |
| {"label": "Rate per second"} |
| ] |
| }, |
| { |
| "id": 2, |
| "title": "WebSocket Connections", |
| "type": "stat", |
| "targets": [ |
| { |
| "expr": "websocket_connections_active", |
| "legendFormat": "Active Connections" |
| } |
| ] |
| }, |
| { |
| "id": 3, |
| "title": "Workflow Execution Duration", |
| "type": "heatmap", |
| "targets": [ |
| { |
| "expr": "workflow_execution_duration_seconds", |
| "legendFormat": "{{ workflow_id }}" |
| } |
| ] |
| }, |
| { |
| "id": 4, |
| "title": "System Resources", |
| "type": "graph", |
| "targets": [ |
| { |
| "expr": "100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)", |
| "legendFormat": "CPU %" |
| }, |
| { |
| "expr": "(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100", |
| "legendFormat": "Memory %" |
| } |
| ] |
| } |
| ], |
| "time": { |
| "from": "now-1h", |
| "to": "now" |
| }, |
| "refresh": "30s" |
| } |
| } |
| |
| grafana_dashboard_file = self.config_path / "grafana_dashboard.json" |
| with open(grafana_dashboard_file, 'w') as f: |
| json.dump(grafana_dashboard, f, indent=2) |
| print(f" ✅ Created: {grafana_dashboard_file}") |
| |
| |
| logging_config = { |
| "version": 1, |
| "disable_existing_loggers": False, |
| "formatters": { |
| "detailed": { |
| "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
| }, |
| "json": { |
| "()": "pythonjsonlogger.jsonlogger.JsonFormatter", |
| "format": "%(asctime)s %(name)s %(levelname)s %(message)s" |
| } |
| }, |
| "handlers": { |
| "console": { |
| "class": "logging.StreamHandler", |
| "level": "INFO", |
| "formatter": "detailed", |
| "stream": "ext://sys.stdout" |
| }, |
| "file": { |
| "class": "logging.handlers.RotatingFileHandler", |
| "level": "DEBUG", |
| "formatter": "json", |
| "filename": "/opt/atom/production/logs/atom.log", |
| "maxBytes": 10485760, |
| "backupCount": 5 |
| }, |
| "workflow_file": { |
| "class": "logging.handlers.RotatingFileHandler", |
| "level": "INFO", |
| "formatter": "json", |
| "filename": "/opt/atom/production/logs/workflows.log", |
| "maxBytes": 10485760, |
| "backupCount": 10 |
| }, |
| "websocket_file": { |
| "class": "logging.handlers.RotatingFileHandler", |
| "level": "INFO", |
| "formatter": "json", |
| "filename": "/opt/atom/production/logs/websocket.log", |
| "maxBytes": 10485760, |
| "backupCount": 10 |
| } |
| }, |
| "loggers": { |
| "": { |
| "level": "INFO", |
| "handlers": ["console", "file"] |
| }, |
| "atom.workflows": { |
| "level": "INFO", |
| "handlers": ["workflow_file"], |
| "propagate": False |
| }, |
| "atom.websocket": { |
| "level": "INFO", |
| "handlers": ["websocket_file"], |
| "propagate": False |
| } |
| } |
| } |
| |
| logging_config_file = self.config_path / "logging.yaml" |
| with open(logging_config_file, 'w') as f: |
| yaml.dump(logging_config, f) |
| print(f" ✅ Created: {logging_config_file}") |
| |
| def _setup_database_configuration(self): |
| """Setup database configuration""" |
| print("\n🗄️ Setting Up Database Configuration...") |
| |
| |
| postgres_config = """ |
| # PostgreSQL Configuration for Atom Workflow Automation |
| |
| # Connection Settings |
| listen_addresses = 'localhost' |
| port = 5432 |
| max_connections = 200 |
| |
| # Memory Settings |
| shared_buffers = 256MB |
| effective_cache_size = 1GB |
| work_mem = 4MB |
| maintenance_work_mem = 64MB |
| |
| # WAL Settings |
| wal_level = replica |
| max_wal_size = 1GB |
| min_wal_size = 80MB |
| checkpoint_completion_target = 0.9 |
| |
| # Query Performance |
| random_page_cost = 1.1 |
| effective_io_concurrency = 200 |
| |
| # Logging Settings |
| log_statement = 'all' |
| log_min_duration_statement = 1000 |
| log_checkpoints = on |
| log_connections = on |
| log_disconnections = on |
| log_lock_waits = on |
| |
| # Security Settings |
| ssl = on |
| password_encryption = scram-sha-256 |
| """ |
| |
| postgres_config_file = self.config_path / "postgresql.conf" |
| with open(postgres_config_file, 'w') as f: |
| f.write(postgres_config.strip()) |
| print(f" ✅ Created: {postgres_config_file}") |
| |
| |
| migration_script = """ |
| #!/bin/bash |
| # Database Migration Script for Atom Workflow Automation |
| |
| set -e |
| |
| echo "🗄️ Starting Database Migration..." |
| |
| # Database connection parameters |
| DB_HOST="localhost" |
| DB_PORT="5432" |
| DB_NAME="atom_production" |
| DB_USER="atom_user" |
| DB_PASSWORD="CHANGE_THIS_PASSWORD" |
| |
| # Create database if it doesn't exist |
| echo "📝 Creating database..." |
| PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U postgres -c "CREATE DATABASE IF NOT EXISTS $DB_NAME;" |
| |
| # Create user if it doesn't exist |
| echo "👤 Creating user..." |
| PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U postgres -c "DO $$\\nBEGIN;\\nIF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$DB_USER') THEN\\n CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';\\nEND IF;\\n$$;" |
| |
| # Grant privileges |
| echo "🔐 Granting privileges..." |
| PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;" |
| |
| # Run migration files |
| echo "🔄 Running migrations..." |
| export PGPASSWORD=$DB_PASSWORD |
| |
| # Create tables |
| psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME << 'EOF' |
| -- Workflows table |
| CREATE TABLE IF NOT EXISTS workflows ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| name VARCHAR(255) NOT NULL, |
| description TEXT, |
| category VARCHAR(100), |
| user_id UUID NOT NULL, |
| parameters JSONB DEFAULT '{}', |
| template_id UUID, |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| version INTEGER DEFAULT 1 |
| ); |
| |
| -- Workflow executions table |
| CREATE TABLE IF NOT EXISTS workflow_executions ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| workflow_id UUID NOT NULL REFERENCES workflows(id), |
| status VARCHAR(50) NOT NULL, |
| input_data JSONB DEFAULT '{}', |
| output_data JSONB DEFAULT '{}', |
| error_message TEXT, |
| execution_time_seconds DECIMAL, |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| started_at TIMESTAMP WITH TIME ZONE, |
| completed_at TIMESTAMP WITH TIME ZONE, |
| user_id UUID NOT NULL |
| ); |
| |
| -- Workflow steps table |
| CREATE TABLE IF NOT EXISTS workflow_steps ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| execution_id UUID NOT NULL REFERENCES workflow_executions(id), |
| step_order INTEGER NOT NULL, |
| service VARCHAR(100) NOT NULL, |
| action VARCHAR(100) NOT NULL, |
| parameters JSONB DEFAULT '{}', |
| status VARCHAR(50) NOT NULL, |
| result JSONB, |
| error_message TEXT, |
| execution_time_seconds DECIMAL, |
| started_at TIMESTAMP WITH TIME ZONE, |
| completed_at TIMESTAMP WITH TIME ZONE |
| ); |
| |
| -- Templates table |
| CREATE TABLE IF NOT EXISTS workflow_templates ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| name VARCHAR(255) NOT NULL, |
| description TEXT, |
| category VARCHAR(100), |
| author VARCHAR(100) NOT NULL, |
| version VARCHAR(50) NOT NULL, |
| parameters JSONB DEFAULT '{}', |
| steps JSONB NOT NULL, |
| tags TEXT[], |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() |
| ); |
| |
| -- Users table |
| CREATE TABLE IF NOT EXISTS users ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| username VARCHAR(100) UNIQUE NOT NULL, |
| email VARCHAR(255) UNIQUE NOT NULL, |
| password_hash VARCHAR(255) NOT NULL, |
| is_active BOOLEAN DEFAULT TRUE, |
| is_admin BOOLEAN DEFAULT FALSE, |
| last_login TIMESTAMP WITH TIME ZONE, |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() |
| ); |
| |
| -- Sessions table |
| CREATE TABLE IF NOT EXISTS sessions ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| user_id UUID NOT NULL REFERENCES users(id), |
| session_token VARCHAR(255) UNIQUE NOT NULL, |
| expires_at TIMESTAMP WITH TIME ZONE NOT NULL, |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() |
| ); |
| |
| -- Audit log table |
| CREATE TABLE IF NOT EXISTS audit_log ( |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
| user_id UUID REFERENCES users(id), |
| action VARCHAR(100) NOT NULL, |
| resource_type VARCHAR(100), |
| resource_id UUID, |
| old_values JSONB, |
| new_values JSONB, |
| ip_address INET, |
| user_agent TEXT, |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() |
| ); |
| |
| -- Integration catalog table |
| CREATE TABLE IF NOT EXISTS integration_catalog ( |
| id TEXT PRIMARY KEY, |
| name TEXT NOT NULL, |
| description TEXT, |
| category TEXT NOT NULL, |
| icon TEXT, |
| color TEXT DEFAULT '#6366F1', |
| auth_type TEXT DEFAULT 'none', |
| native_id TEXT, |
| triggers JSONB DEFAULT '[]', |
| actions JSONB DEFAULT '[]', |
| popular BOOLEAN DEFAULT FALSE, |
| created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() |
| ); |
| |
| -- Indexes for performance |
| CREATE INDEX IF NOT EXISTS idx_workflows_user_id ON workflows(user_id); |
| CREATE INDEX IF NOT EXISTS idx_workflows_template_id ON workflows(template_id); |
| CREATE INDEX IF NOT EXISTS idx_workflow_executions_workflow_id ON workflow_executions(workflow_id); |
| CREATE INDEX IF NOT EXISTS idx_workflow_executions_user_id ON workflow_executions(user_id); |
| CREATE INDEX IF NOT EXISTS idx_workflow_executions_status ON workflow_executions(status); |
| CREATE INDEX IF NOT EXISTS idx_workflow_steps_execution_id ON workflow_steps(execution_id); |
| CREATE INDEX IF NOT EXISTS idx_workflow_steps_status ON workflow_steps(status); |
| CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); |
| CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token); |
| CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id); |
| CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at); |
| CREATE INDEX IF NOT EXISTS idx_integration_catalog_category ON integration_catalog(category); |
| CREATE INDEX IF NOT EXISTS idx_integration_catalog_popular ON integration_catalog(popular); |
| |
| EOF |
| |
| echo "✅ Database migration completed successfully" |
| """ |
| |
| migration_script_file = self.config_path / "migrate_database.sh" |
| with open(migration_script_file, 'w') as f: |
| f.write(migration_script.strip()) |
| |
| |
| os.chmod(migration_script_file, 0o755) |
| print(f" ✅ Created: {migration_script_file}") |
| |
| |
| redis_config = """ |
| # Redis Configuration for Atom Workflow Automation |
| |
| # Network |
| bind 127.0.0.1 |
| port 6379 |
| protected-mode yes |
| requirepass CHANGE_THIS_REDIS_PASSWORD |
| |
| # Memory |
| maxmemory 512mb |
| maxmemory-policy allkeys-lru |
| |
| # Persistence |
| save 900 1 |
| save 300 10 |
| save 60 10000 |
| |
| # Security |
| rename-command FLUSHDB "" |
| rename-command FLUSHALL "" |
| rename-command DEBUG "" |
| rename-command CONFIG "" |
| |
| # Performance |
| tcp-keepalive 300 |
| timeout 0 |
| |
| # Logging |
| loglevel notice |
| logfile /var/log/redis/redis-server.log |
| |
| # Clients |
| maxclients 10000 |
| """ |
| |
| redis_config_file = self.config_path / "redis.conf" |
| with open(redis_config_file, 'w') as f: |
| f.write(redis_config.strip()) |
| print(f" ✅ Created: {redis_config_file}") |
| |
| def _create_deployment_scripts(self): |
| """Create deployment and management scripts""" |
| print("\n🚀 Creating Deployment Scripts...") |
| |
| |
| deploy_script = """#!/bin/bash |
| # Main Deployment Script for Atom Workflow Automation |
| |
| set -e |
| |
| DEPLOYMENT_PATH="/opt/atom/production" |
| BACKUP_PATH="/opt/atom/production/backups" |
| LOG_FILE="/opt/atom/production/logs/deploy.log" |
| |
| echo "🚀 Starting Atom Workflow Automation Deployment..." |
| echo "$(date): Deployment started" >> $LOG_FILE |
| |
| # Function to log messages |
| log() { |
| echo "$1" |
| echo "$(date): $1" >> $LOG_FILE |
| } |
| |
| # Check if running as root |
| if [ "$EUID" -ne 0 ]; then |
| log "❌ This script must be run as root" |
| exit 1 |
| fi |
| |
| # Create backup if this is not a fresh deployment |
| if [ -d "$DEPLOYMENT_PATH" ] && [ "$(ls -A $DEPLOYMENT_PATH)" ]; then |
| log "📦 Creating backup..." |
| BACKUP_NAME="backup_$(date +%Y%m%d_%H%M%S)" |
| mkdir -p "$BACKUP_PATH/$BACKUP_NAME" |
| cp -r $DEPLOYMENT_PATH/* "$BACKUP_PATH/$BACKUP_NAME/" 2>/dev/null || true |
| log "✅ Backup created: $BACKUP_NAME" |
| fi |
| |
| # Stop existing services |
| log "🛑 Stopping existing services..." |
| systemctl stop atom-workflow-api || true |
| systemctl stop atom-websocket-server || true |
| systemctl stop atom-scheduler || true |
| |
| # Update application code |
| log "📥 Updating application code..." |
| cd $DEPLOYMENT_PATH |
| if [ -d "git" ]; then |
| cd git |
| git pull origin main |
| cd .. |
| rsync -av --exclude '.git' git/ $DEPLOYMENT_PATH/ |
| fi |
| |
| # Install dependencies |
| log "📦 Installing Python dependencies..." |
| python3 -m pip install -r requirements.txt --upgrade |
| |
| # Run database migrations |
| log "🗄️ Running database migrations..." |
| $DEPLOYMENT_PATH/config/migrate_database.sh |
| |
| # Update configuration |
| log "⚙️ Updating configuration..." |
| if [ ! -f "$DEPLOYMENT_PATH/config/.env" ]; then |
| cp $DEPLOYMENT_PATH/config/.env.example $DEPLOYMENT_PATH/config/.env |
| log "⚠️ Please configure environment variables in $DEPLOYMENT_PATH/config/.env" |
| fi |
| |
| # Build static assets |
| log "🎨 Building static assets..." |
| npm run build || echo "⚠️ npm build failed, continuing..." |
| |
| # Set permissions |
| log "🔒 Setting permissions..." |
| chown -R atom:atom $DEPLOYMENT_PATH |
| chmod +x $DEPLOYMENT_PATH/scripts/*.sh |
| |
| # Start services |
| log "🚀 Starting services..." |
| systemctl daemon-reload |
| systemctl enable atom-workflow-api |
| systemctl enable atom-websocket-server |
| systemctl enable atom-scheduler |
| systemctl start atom-workflow-api |
| systemctl start atom-websocket-server |
| systemctl start atom-scheduler |
| |
| # Health check |
| log "🏥 Running health checks..." |
| sleep 10 |
| |
| if curl -f http://localhost:8080/health > /dev/null 2>&1; then |
| log "✅ Health check passed" |
| else |
| log "❌ Health check failed" |
| echo "$(date): Health check failed" >> $LOG_FILE |
| exit 1 |
| fi |
| |
| log "🎉 Deployment completed successfully!" |
| echo "$(date): Deployment completed" >> $LOG_FILE |
| |
| # Display status |
| systemctl status atom-workflow-api --no-pager -l |
| systemctl status atom-websocket-server --no-pager -l |
| systemctl status atom-scheduler --no-pager -l |
| """ |
| |
| deploy_script_file = self.deployment_path / "scripts" / "deploy.sh" |
| with open(deploy_script_file, 'w') as f: |
| f.write(deploy_script.strip()) |
| os.chmod(deploy_script_file, 0o755) |
| print(f" ✅ Created: {deploy_script_file}") |
| |
| |
| workflow_api_service = """[Unit] |
| Description=Atom Workflow API |
| After=network.target postgresql.service redis.service |
| |
| [Service] |
| Type=exec |
| User=atom |
| Group=atom |
| WorkingDirectory=/opt/atom/production |
| Environment=PATH=/opt/atom/production/venv/bin |
| ExecStart=/opt/atom/production/venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8000 |
| Restart=always |
| RestartSec=10 |
| StandardOutput=journal |
| StandardError=journal |
| |
| [Install] |
| WantedBy=multi-user.target |
| """ |
| |
| workflow_api_service_file = self.deployment_path / "scripts" / "atom-workflow-api.service" |
| with open(workflow_api_service_file, 'w') as f: |
| f.write(workflow_api_service.strip()) |
| print(f" ✅ Created: {workflow_api_service_file}") |
| |
| websocket_server_service = """[Unit] |
| Description=Atom WebSocket Server |
| After=network.target postgresql.service redis.service |
| |
| [Service] |
| Type=exec |
| User=atom |
| Group=atom |
| WorkingDirectory=/opt/atom/production |
| Environment=PATH=/opt/atom/production/venv/bin |
| ExecStart=/opt/atom/production/venv/bin/python websocket_server.py |
| Restart=always |
| RestartSec=10 |
| StandardOutput=journal |
| StandardError=journal |
| |
| [Install] |
| WantedBy=multi-user.target |
| """ |
| |
| websocket_server_service_file = self.deployment_path / "scripts" / "atom-websocket-server.service" |
| with open(websocket_server_service_file, 'w') as f: |
| f.write(websocket_server_service.strip()) |
| print(f" ✅ Created: {websocket_server_service_file}") |
| |
| |
| monitoring_script = """#!/bin/bash |
| # Monitoring Script for Atom Workflow Automation |
| |
| DEPLOYMENT_PATH="/opt/atom/production" |
| LOG_FILE="/opt/atom/production/logs/monitoring.log" |
| |
| log() { |
| echo "$1" |
| echo "$(date): $1" >> $LOG_FILE |
| } |
| |
| # Check service status |
| check_service() { |
| local service=$1 |
| if systemctl is-active --quiet $service; then |
| log "✅ $service is running" |
| else |
| log "❌ $service is not running" |
| systemctl restart $service |
| fi |
| } |
| |
| # Check system resources |
| check_resources() { |
| # CPU usage |
| CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%* id.*/\\1/" | awk '{print 100 - $1}') |
| if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then |
| log "⚠️ High CPU usage: ${CPU_USAGE}%" |
| fi |
| |
| # Memory usage |
| MEMORY_USAGE=$(free | grep Mem | awk '{printf("%.2f", $3/$2 * 100.0)}') |
| if (( $(echo "$MEMORY_USAGE > 80" | bc -l) )); then |
| log "⚠️ High memory usage: ${MEMORY_USAGE}%" |
| fi |
| |
| # Disk usage |
| DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') |
| if [ $DISK_USAGE -gt 80 ]; then |
| log "⚠️ High disk usage: ${DISK_USAGE}%" |
| fi |
| } |
| |
| # Check connectivity |
| check_connectivity() { |
| # Database |
| if PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -c "SELECT 1;" > /dev/null 2>&1; then |
| log "✅ Database connection is OK" |
| else |
| log "❌ Database connection failed" |
| fi |
| |
| # Redis |
| if redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD ping > /dev/null 2>&1; then |
| log "✅ Redis connection is OK" |
| else |
| log "❌ Redis connection failed" |
| fi |
| |
| # WebSocket |
| if curl -f http://localhost:8765/health > /dev/null 2>&1; then |
| log "✅ WebSocket server is responding" |
| else |
| log "❌ WebSocket server is not responding" |
| fi |
| } |
| |
| log "🔍 Starting system monitoring..." |
| |
| # Check services |
| check_service "atom-workflow-api" |
| check_service "atom-websocket-server" |
| check_service "atom-scheduler" |
| |
| # Check resources |
| check_resources |
| |
| # Check connectivity |
| check_connectivity |
| |
| log "✅ Monitoring completed" |
| """ |
| |
| monitoring_script_file = self.deployment_path / "scripts" / "monitor.sh" |
| with open(monitoring_script_file, 'w') as f: |
| f.write(monitoring_script.strip()) |
| os.chmod(monitoring_script_file, 0o755) |
| print(f" ✅ Created: {monitoring_script_file}") |
| |
| |
| backup_script = """#!/bin/bash |
| # Backup Script for Atom Workflow Automation |
| |
| BACKUP_PATH="/opt/atom/production/backups" |
| DB_BACKUP_PATH="$BACKUP_PATH/database" |
| CONFIG_BACKUP_PATH="$BACKUP_PATH/config" |
| LOG_FILE="/opt/atom/production/logs/backup.log" |
| |
| log() { |
| echo "$1" |
| echo "$(date): $1" >> $LOG_FILE |
| } |
| |
| # Create backup directories |
| mkdir -p $DB_BACKUP_PATH |
| mkdir -p $CONFIG_BACKUP_PATH |
| |
| log "📦 Starting backup process..." |
| |
| # Database backup |
| log "🗄️ Creating database backup..." |
| DB_NAME="atom_production" |
| DB_USER="atom_user" |
| DB_PASSWORD="CHANGE_THIS_PASSWORD" |
| TIMESTAMP=$(date +%Y%m%d_%H%M%S) |
| |
| PGPASSWORD=$DB_PASSWORD pg_dump -h localhost -U $DB_USER -d $DB_NAME | gzip > "$DB_BACKUP_PATH/db_backup_$TIMESTAMP.sql.gz" |
| |
| # Configuration backup |
| log "⚙️ Creating configuration backup..." |
| tar -czf "$CONFIG_BACKUP_PATH/config_backup_$TIMESTAMP.tar.gz" /opt/atom/production/config/ |
| |
| # Application backup |
| log "📱 Creating application backup..." |
| tar -czf "$BACKUP_PATH/app_backup_$TIMESTAMP.tar.gz" /opt/atom/production/ --exclude=/opt/atom/production/logs --exclude=/opt/atom/production/backups --exclude=/opt/atom/production/temp |
| |
| # Cleanup old backups (keep last 30 days) |
| log "🧹 Cleaning up old backups..." |
| find $BACKUP_PATH -name "*.gz" -mtime +30 -delete |
| |
| log "✅ Backup completed successfully" |
| log "📊 Backup size: $(du -sh $BACKUP_PATH | cut -f1)" |
| """ |
| |
| backup_script_file = self.deployment_path / "scripts" / "backup.sh" |
| with open(backup_script_file, 'w') as f: |
| f.write(backup_script.strip()) |
| os.chmod(backup_script_file, 0o755) |
| print(f" ✅ Created: {backup_script_file}") |
| |
| def _setup_health_checks(self): |
| """Setup health check endpoints""" |
| print("\n🏥 Setting Up Health Checks...") |
| |
| health_check_server = """ |
| #!/usr/bin/env python3 |
| """ |
| Health Check Server for Atom Workflow Automation |
| """ |
| |
| import os |
| import sys |
| import json |
| import asyncio |
| import aiohttp |
| try: |
| import psycopg2 |
| PSYCOPG2_AVAILABLE = True |
| except ImportError: |
| PSYCOPG2_AVAILABLE = False |
| |
| try: |
| import redis |
| REDIS_AVAILABLE = True |
| except ImportError: |
| REDIS_AVAILABLE = False |
| |
| from datetime import datetime |
| from pathlib import Path |
| |
| # Add deployment path to Python path |
| sys.path.append('/opt/atom/production') |
| |
| class HealthCheckServer: |
| def __init__(self): |
| self.port = 8080 |
| self.db_url = os.getenv('DATABASE_URL', '') |
| self.redis_url = os.getenv('REDIS_URL', '') |
| |
| async def health_check(self, request): |
| """Main health check endpoint""" |
| status = { |
| "status": "healthy", |
| "timestamp": datetime.now().isoformat(), |
| "version": "1.0.0", |
| "checks": {} |
| } |
| |
| overall_healthy = True |
| |
| # Database health check |
| if PSYCOPG2_AVAILABLE and self.db_url: |
| try: |
| conn = psycopg2.connect(self.db_url) |
| cursor = conn.cursor() |
| cursor.execute("SELECT 1") |
| cursor.close() |
| conn.close() |
| status["checks"]["database"] = {"status": "healthy", "message": "Database connection successful"} |
| except Exception as e: |
| status["checks"]["database"] = {"status": "unhealthy", "message": str(e)} |
| overall_healthy = False |
| else: |
| status["checks"]["database"] = {"status": "unknown", "message": "psycopg2 not installed or database URL missing"} |
| |
| # Redis health check |
| if REDIS_AVAILABLE and self.redis_url: |
| try: |
| r = redis.from_url(self.redis_url) |
| r.ping() |
| status["checks"]["redis"] = {"status": "healthy", "message": "Redis connection successful"} |
| except Exception as e: |
| status["checks"]["redis"] = {"status": "unhealthy", "message": str(e)} |
| overall_healthy = False |
| else: |
| status["checks"]["redis"] = {"status": "unknown", "message": "redis-py not installed or Redis URL missing"} |
| |
| # WebSocket server health check |
| try: |
| async with aiohttp.ClientSession() as session: |
| async with session.get('http://localhost:8765/health', timeout=5) as response: |
| if response.status == 200: |
| status["checks"]["websocket"] = {"status": "healthy", "message": "WebSocket server responding"} |
| else: |
| raise Exception(f"WebSocket server returned status {response.status}") |
| except Exception as e: |
| status["checks"]["websocket"] = {"status": "unhealthy", "message": str(e)} |
| overall_healthy = False |
| |
| # API server health check |
| try: |
| async with aiohttp.ClientSession() as session: |
| async with session.get('http://localhost:8000/health', timeout=5) as response: |
| if response.status == 200: |
| status["checks"]["api"] = {"status": "healthy", "message": "API server responding"} |
| else: |
| raise Exception(f"API server returned status {response.status}") |
| except Exception as e: |
| status["checks"]["api"] = {"status": "unhealthy", "message": str(e)} |
| overall_healthy = False |
| |
| # System resources check |
| try: |
| import psutil |
| |
| cpu_percent = psutil.cpu_percent(interval=1) |
| memory = psutil.virtual_memory() |
| disk = psutil.disk_usage('/') |
| |
| resources = { |
| "cpu_percent": cpu_percent, |
| "memory_percent": memory.percent, |
| "disk_percent": (disk.used / disk.total) * 100 |
| } |
| |
| # Check if resources are within acceptable limits |
| if cpu_percent < 80 and memory.percent < 80 and resources["disk_percent"] < 80: |
| status["checks"]["resources"] = {"status": "healthy", "data": resources} |
| else: |
| status["checks"]["resources"] = {"status": "warning", "data": resources} |
| |
| except Exception as e: |
| status["checks"]["resources"] = {"status": "unhealthy", "message": str(e)} |
| overall_healthy = False |
| |
| # Set overall status |
| if not overall_healthy: |
| status["status"] = "unhealthy" |
| |
| # Return appropriate HTTP status |
| http_status = 200 if overall_healthy else 503 |
| |
| return web.json_response(status, status=http_status) |
| |
| async def ready_check(self, request): |
| """Readiness check endpoint""" |
| return web.json_response({ |
| "status": "ready", |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| async def live_check(self, request): |
| """Liveness check endpoint""" |
| return web.json_response({ |
| "status": "alive", |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| async def start_server(self): |
| """Start the health check server""" |
| app = web.Application() |
| |
| app.router.add_get('/health', self.health_check) |
| app.router.add_get('/ready', self.ready_check) |
| app.router.add_get('/live', self.live_check) |
| |
| runner = web.AppRunner(app) |
| await runner.setup() |
| site = web.TCPSite(runner, 'localhost', self.port) |
| await site.start() |
| print(f"🏥 Health check server started on port {self.port}") |
| |
| if __name__ == '__main__': |
| health_server = HealthCheckServer() |
| asyncio.run(health_server.start_server()) |
| """ |
| |
| health_check_file = self.deployment_path / "health_check_server.py" |
| with open(health_check_file, 'w') as f: |
| f.write(health_check_server.strip()) |
| os.chmod(health_check_file, 0o755) |
| print(f" ✅ Created: {health_check_file}") |
| |
| def create_cron_jobs(self): |
| """Create cron jobs for maintenance tasks""" |
| print("\n⏰ Creating Cron Jobs...") |
| |
| crontab_content = """ |
| # Cron Jobs for Atom Workflow Automation |
| # Edit with: crontab -e -u atom |
| |
| # Backup every day at 2 AM |
| 0 2 * * * /opt/atom/production/scripts/backup.sh >> /opt/atom/production/logs/backup.log 2>&1 |
| |
| # Monitoring every 5 minutes |
| */5 * * * * /opt/atom/production/scripts/monitor.sh >> /opt/atom/production/logs/monitoring.log 2>&1 |
| |
| # Log rotation every day at 3 AM |
| 0 3 * * * /usr/sbin/logrotate /opt/atom/production/config/logrotate.conf |
| |
| # Database maintenance every Sunday at 4 AM |
| 0 4 * * 0 psql -h localhost -U atom_user -d atom_production -c "VACUUM ANALYZE;" >> /opt/atom/production/logs/maintenance.log 2>&1 |
| |
| # Clean up temp files every hour |
| 0 * * * * find /opt/atom/production/temp -type f -mtime +1 -delete |
| """ |
| |
| crontab_file = self.config_path / "crontab.txt" |
| with open(crontab_file, 'w') as f: |
| f.write(crontab_content.strip()) |
| print(f" ✅ Created: {crontab_file}") |
| |
| |
| logrotate_config = """ |
| /opt/atom/production/logs/*.log { |
| daily |
| missingok |
| rotate 30 |
| compress |
| delaycompress |
| notifempty |
| create 644 atom atom |
| postrotate |
| systemctl reload atom-workflow-api || true |
| systemctl reload atom-websocket-server || true |
| endscript |
| } |
| |
| /var/log/postgresql/*.log { |
| weekly |
| missingok |
| rotate 8 |
| compress |
| delaycompress |
| notifempty |
| create 644 postgres postgres |
| postrotate |
| systemctl reload postgresql || true |
| endscript |
| } |
| |
| /var/log/redis/redis-server.log { |
| weekly |
| missingok |
| rotate 8 |
| compress |
| delaycompress |
| notifempty |
| create 644 redis redis |
| postrotate |
| systemctl reload redis || true |
| endscript |
| } |
| """ |
| |
| logrotate_file = self.config_path / "logrotate.conf" |
| with open(logrotate_file, 'w') as f: |
| f.write(logrotate_config.strip()) |
| print(f" ✅ Created: {logrotate_file}") |
|
|
|
|
| def main(): |
| """Main deployment setup""" |
| print("🚀 PRODUCTION DEPLOYMENT SETUP") |
| print("=" * 80) |
| print("Setting up production environment for Atom Workflow Automation") |
| print("=" * 80) |
| |
| try: |
| |
| if os.geteuid() != 0: |
| print("❌ This script must be run as root (use sudo)") |
| return {"success": False, "error": "Root privileges required"} |
| |
| |
| deployment_manager = ProductionDeploymentManager() |
| |
| |
| result = deployment_manager.setup_production_environment() |
| |
| if result.get("success"): |
| print("\n" + "=" * 80) |
| print("🎉 PRODUCTION SETUP COMPLETED SUCCESSFULLY!") |
| print("=" * 80) |
| print("\n📋 Next Steps:") |
| print("1. Configure environment variables in /opt/atom/production/config/.env") |
| print("2. Run database migration: /opt/atom/production/config/migrate_database.sh") |
| print("3. Install systemd services: cp /opt/atom/production/scripts/*.service /etc/systemd/system/") |
| print("4. Reload systemd: systemctl daemon-reload") |
| print("5. Deploy application: /opt/atom/production/scripts/deploy.sh") |
| print("6. Setup monitoring: cp /opt/atom/production/config/*.yml /etc/prometheus/") |
| print("7. Setup cron jobs: crontab -e -u atom (paste content from /opt/atom/production/config/crontab.txt)") |
| |
| print("\n🔍 Verification Commands:") |
| print(" curl http://localhost:8080/health") |
| print(" curl http://localhost:8000/health") |
| print(" curl http://localhost:8765/health") |
| |
| print("\n📊 Monitoring URLs:") |
| print(" Prometheus: http://localhost:9090") |
| print(" Grafana: http://localhost:3000") |
| |
| print("\n🔧 Management Commands:") |
| print(" Deploy: /opt/atom/production/scripts/deploy.sh") |
| print(" Monitor: /opt/atom/production/scripts/monitor.sh") |
| print(" Backup: /opt/atom/production/scripts/backup.sh") |
| else: |
| print(f"\n❌ Production setup failed: {result.get('error')}") |
| |
| return result |
| |
| except Exception as e: |
| print(f"\n❌ Setup failed with exception: {str(e)}") |
| logger.error(f"Production setup failed: {str(e)}") |
| return {"success": False, "error": str(e)} |
|
|
|
|
| if __name__ == "__main__": |
| result = main() |
| sys.exit(0 if result.get("success") else 1) |