annator-command-center / scripts /production /deploy_production_with_oauth.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 6)
383cb38 verified
Raw
History Blame Contribute Delete
18.9 kB
#!/usr/bin/env python3
"""
Comprehensive Production Deployment with OAuth Completion
This script handles the complete production deployment of the Atom AI Assistant
including OAuth service completion, SSL/TLS configuration, monitoring setup,
and production validation.
Usage:
python deploy_production_with_oauth.py
"""
from datetime import datetime
import json
import os
import secrets
import subprocess
import sys
import time
from typing import Any, Dict, List, Tuple
import requests
class ProductionDeploymentWithOAuth:
"""Complete production deployment with OAuth service completion"""
def __init__(self):
self.base_url = "http://localhost:5058"
self.deployment_log = []
self.start_time = datetime.now()
self.remaining_services = ["outlook", "teams", "github"]
def log_step(self, step_name: str, status: str, message: str = ""):
"""Log deployment step with timestamp"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = {
"timestamp": timestamp,
"step": step_name,
"status": status,
"message": message
}
self.deployment_log.append(log_entry)
status_icon = "✅" if status == "success" else "❌" if status == "failed" else "⚠️"
print(f"{status_icon} [{timestamp}] {step_name}: {message}")
def validate_current_oauth_status(self) -> Dict[str, Any]:
"""Validate current OAuth system status"""
self.log_step("oauth_status_validation", "running", "Validating current OAuth system status")
# First, check environment variables
missing_credentials = self._check_oauth_credentials()
if missing_credentials:
self.log_step(
"oauth_status_validation",
"warning",
f"Missing OAuth credentials: {', '.join(missing_credentials)}"
)
return {
"success": False,
"error": "Missing OAuth credentials",
"missing_credentials": missing_credentials
}
try:
response = requests.get(
f"{self.base_url}/api/auth/oauth-status?user_id=production_deploy",
timeout=10
)
if response.status_code == 200:
data = response.json()
connected_services = data.get("connected_services", 0)
total_services = data.get("total_services", 0)
self.log_step(
"oauth_status_validation",
"success",
f"Current OAuth status: {connected_services}/{total_services} services connected"
)
return {
"success": True,
"connected_services": connected_services,
"total_services": total_services,
"success_rate": connected_services / total_services if total_services > 0 else 0,
"data": data
}
else:
self.log_step(
"oauth_status_validation",
"failed",
f"OAuth status endpoint returned HTTP {response.status_code}"
)
return {"success": False, "error": f"HTTP {response.status_code}"}
except Exception as e:
self.log_step(
"oauth_status_validation",
"failed",
f"OAuth status validation failed: {str(e)}"
)
return {"success": False, "error": str(e)}
def _check_oauth_credentials(self) -> List[str]:
"""Check for missing OAuth credentials in environment"""
required_credentials = {
"OUTLOOK_CLIENT_ID": "Microsoft Outlook",
"OUTLOOK_CLIENT_SECRET": "Microsoft Outlook",
"TEAMS_CLIENT_ID": "Microsoft Teams",
"TEAMS_CLIENT_SECRET": "Microsoft Teams",
"GITHUB_CLIENT_ID": "GitHub",
"GITHUB_CLIENT_SECRET": "GitHub"
}
missing = []
for env_var, service in required_credentials.items():
if not os.getenv(env_var):
missing.append(f"{env_var} ({service})")
return missing
def configure_remaining_oauth_services(self) -> bool:
"""Configure remaining OAuth services with placeholder credentials"""
self.log_step(
"oauth_service_completion",
"running",
f"Configuring remaining OAuth services: {', '.join(self.remaining_services)}"
)
# Create configuration template for remaining services
# Now reads from environment variables instead of TODO placeholders
production_domain = os.getenv("PRODUCTION_DOMAIN", "your-production-domain.com")
oauth_config = {
"outlook": {
"client_id": os.getenv("OUTLOOK_CLIENT_ID", ""),
"client_secret": os.getenv("OUTLOOK_CLIENT_SECRET", ""),
"redirect_uri": f"https://{production_domain}/api/auth/outlook/oauth2callback",
"scopes": ["https://graph.microsoft.com/Mail.Read", "https://graph.microsoft.com/Calendars.Read"],
"configured": bool(os.getenv("OUTLOOK_CLIENT_ID") and os.getenv("OUTLOOK_CLIENT_SECRET"))
},
"teams": {
"client_id": os.getenv("TEAMS_CLIENT_ID", ""),
"client_secret": os.getenv("TEAMS_CLIENT_SECRET", ""),
"redirect_uri": f"https://{production_domain}/api/auth/teams/oauth2callback",
"scopes": ["https://graph.microsoft.com/Team.ReadBasic.All"],
"configured": bool(os.getenv("TEAMS_CLIENT_ID") and os.getenv("TEAMS_CLIENT_SECRET"))
},
"github": {
"client_id": os.getenv("GITHUB_CLIENT_ID", ""),
"client_secret": os.getenv("GITHUB_CLIENT_SECRET", ""),
"redirect_uri": f"https://{production_domain}/api/auth/github/oauth2callback",
"scopes": ["repo", "user", "read:org"],
"configured": bool(os.getenv("GITHUB_CLIENT_ID") and os.getenv("GITHUB_CLIENT_SECRET"))
}
}
# Save OAuth configuration template
config_file = "oauth_remaining_services_config.json"
with open(config_file, "w") as f:
json.dump(oauth_config, f, indent=2)
self.log_step(
"oauth_service_completion",
"success",
f"OAuth configuration template created: {config_file}"
)
# Create setup instructions
instructions = self._generate_oauth_setup_instructions()
instructions_file = "OAUTH_SERVICE_SETUP_INSTRUCTIONS.md"
with open(instructions_file, "w") as f:
f.write(instructions)
self.log_step(
"oauth_service_completion",
"info",
f"Setup instructions created: {instructions_file}"
)
return True
def _generate_oauth_setup_instructions(self) -> str:
"""Generate OAuth service setup instructions"""
return f"""# OAuth Service Setup Instructions
## Remaining Services to Configure
### 1. Microsoft Outlook/Teams
**Steps:**
1. Go to [Azure Portal](https://portal.azure.com)
2. Navigate to Azure Active Directory > App registrations
3. Create a new application registration
4. Configure redirect URIs:
- `https://your-production-domain.com/api/auth/outlook/oauth2callback`
- `https://your-production-domain.com/api/auth/teams/oauth2callback`
5. Add required API permissions:
- Microsoft Graph > Mail.Read
- Microsoft Graph > Calendars.Read
- Microsoft Graph > Team.ReadBasic.All
6. Copy Client ID and Client Secret to environment variables
### 2. GitHub
**Steps:**
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Create a new OAuth App
3. Configure:
- Application name: Atom AI Assistant
- Homepage URL: https://your-production-domain.com
- Authorization callback URL: `https://your-production-domain.com/api/auth/github/oauth2callback`
4. Copy Client ID and Client Secret to environment variables
## Environment Variables to Set
```bash
# Microsoft Outlook/Teams
OUTLOOK_CLIENT_ID=your_microsoft_client_id
OUTLOOK_CLIENT_SECRET=your_microsoft_client_secret
TEAMS_CLIENT_ID=your_teams_client_id
TEAMS_CLIENT_SECRET=your_teams_client_secret
# GitHub
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
# Production Domain
PRODUCTION_DOMAIN=your-production-domain.com
```
## Verification Steps
1. Update the environment variables above
2. Restart the backend server
3. Run OAuth validation: `python test_oauth_validation.py`
4. Verify all 10 services show as connected
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
def setup_production_environment(self) -> bool:
"""Setup production environment configuration"""
self.log_step("production_environment", "running", "Setting up production environment")
try:
# Generate production environment template
env_template = self._generate_production_env_template()
env_file = ".env.production.template"
with open(env_file, "w") as f:
f.write(env_template)
self.log_step(
"production_environment",
"success",
f"Production environment template created: {env_file}"
)
# Create production deployment configuration
deployment_config = self._generate_deployment_config()
config_file = "production_deployment_config.json"
with open(config_file, "w") as f:
json.dump(deployment_config, f, indent=2)
self.log_step(
"production_environment",
"success",
f"Deployment configuration created: {config_file}"
)
return True
except Exception as e:
self.log_step(
"production_environment",
"failed",
f"Production environment setup failed: {str(e)}"
)
return False
def _generate_production_env_template(self) -> str:
"""Generate production environment template"""
return f"""# Production Environment Configuration
# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
# Application Settings
FLASK_ENV=production
DEBUG=False
SECRET_KEY={secrets.token_urlsafe(32)}
# Server Configuration
HOST=0.0.0.0
PORT=5058
PRODUCTION_DOMAIN=your-production-domain.com
# Database Configuration
DATABASE_URL=postgresql://username:password@localhost/atom_production
# or for SQLite:
# DATABASE_URL=sqlite:///./data/atom_production.db
# Security Configuration
ATOM_OAUTH_ENCRYPTION_KEY={secrets.token_urlsafe(32)}
CSRF_ENABLED=True
SESSION_SECURE=True
# OAuth Configuration - Update with real credentials
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
SLACK_CLIENT_ID=your_slack_client_id
SLACK_CLIENT_SECRET=your_slack_client_secret
TRELLO_API_KEY=your_trello_api_key
TRELLO_API_SECRET=your_trello_api_secret
ASANA_CLIENT_ID=your_asana_client_id
ASANA_CLIENT_SECRET=your_asana_client_secret
NOTION_CLIENT_ID=your_notion_client_id
NOTION_CLIENT_SECRET=your_notion_client_secret
DROPBOX_CLIENT_ID=your_dropbox_client_id
DROPBOX_CLIENT_SECRET=your_dropbox_client_secret
# Remaining OAuth Services - Configure with real credentials
# Microsoft Outlook (Calendar & Email integration)
OUTLOOK_CLIENT_ID=
OUTLOOK_CLIENT_SECRET=
# Microsoft Teams (Chat & Collaboration integration)
TEAMS_CLIENT_ID=
TEAMS_CLIENT_SECRET=
# GitHub (Repository & Issue integration)
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# AI Provider Configuration
OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
DEEPSEEK_API_KEY=your_deepseek_api_key
GOOGLE_AI_API_KEY=your_google_ai_api_key
# Monitoring & Analytics
ENABLE_METRICS=True
LOG_LEVEL=INFO
HEALTH_CHECK_INTERVAL=30
# SSL/TLS Configuration (for production)
SSL_CERT_PATH=/path/to/ssl/certificate.crt
SSL_KEY_PATH=/path/to/ssl/private.key
# Rate Limiting
RATE_LIMIT_REQUESTS=1000
RATE_LIMIT_WINDOW=3600
"""
def _generate_deployment_config(self) -> Dict[str, Any]:
"""Generate deployment configuration"""
return {
"deployment_id": f"atom_production_{self.start_time.strftime('%Y%m%d_%H%M%S')}",
"timestamp": self.start_time.isoformat(),
"components": {
"backend": {
"status": "ready",
"port": 5058,
"health_endpoint": "/healthz",
"dependencies": ["database", "oauth_services"]
},
"database": {
"status": "configured",
"type": "sqlite", # or "postgresql"
"path": "./data/atom_production.db"
},
"oauth_services": {
"status": "partial",
"connected": 7,
"total": 10,
"remaining": self.remaining_services
},
"security": {
"status": "implemented",
"features": ["csrf_protection", "token_encryption", "secure_sessions"]
},
"monitoring": {
"status": "configured",
"endpoints": ["/healthz", "/api/services/status", "/api/auth/oauth-status"]
}
},
"deployment_steps": [
"environment_configuration",
"oauth_service_completion",
"ssl_tls_setup",
"monitoring_setup",
"backup_configuration",
"final_validation"
],
"requirements": {
"ssl_certificate": "required",
"domain_configuration": "required",
"environment_variables": "required",
"database_backup": "recommended"
}
}
def setup_ssl_tls_configuration(self) -> bool:
"""Setup SSL/TLS configuration for production"""
self.log_step("ssl_tls_setup", "running", "Setting up SSL/TLS configuration")
try:
# Create SSL/TLS setup instructions
ssl_instructions = self._generate_ssl_setup_instructions()
ssl_file = "SSL_TLS_SETUP_GUIDE.md"
with open(ssl_file, "w") as f:
f.write(ssl_instructions)
self.log_step(
"ssl_tls_setup",
"success",
f"SSL/TLS setup guide created: {ssl_file}"
)
# Create nginx configuration template
nginx_config = self._generate_nginx_config()
nginx_file = "nginx_production.conf"
with open(nginx_file, "w") as f:
f.write(nginx_config)
self.log_step(
"ssl_tls_setup",
"success",
f"NGINX configuration template created: {nginx_file}"
)
return True
except Exception as e:
self.log_step(
"ssl_tls_setup",
"failed",
f"SSL/TLS setup failed: {str(e)}"
)
return False
def _generate_ssl_setup_instructions(self) -> str:
"""Generate SSL/TLS setup instructions"""
return f"""# SSL/TLS Setup Guide for Production
## Options for SSL/TLS Certificate
### 1. Let's Encrypt (Free)
```bash
# Install certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx
# Get certificate
sudo certbot --nginx -d your-production-domain.com
# Auto-renewal
sudo crontab -e
# Add: 0 12 * * * /usr/bin/certbot renew --quiet
```
### 2. Commercial Certificate
1. Purchase SSL certificate from provider (DigiCert, Comodo, etc.)
2. Generate CSR and private key
3. Submit CSR to certificate authority
4. Install issued certificate
### 3. Self-Signed (Development Only)
```bash
# Generate self-signed certificate (NOT for production)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```
## NGINX Configuration
See `nginx_production.conf` for complete configuration template.
## Environment Variables
```bash
SSL_CERT_PATH=/etc/ssl/certs/your-domain.crt
SSL_KEY_PATH=/etc/ssl/private/your-domain.key
```
## Verification
```bash
# Test SSL configuration
openssl s_client -connect your-production-domain.com:443
# Check certificate validity
openssl x509 -in /path/to/certificate.crt -text -noout
```
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
def _generate_nginx_config(self) -> str:
"""Generate NGINX configuration template"""
return f"""# NGINX Production Configuration for Atom AI Assistant
# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
server {{
listen 80;
server_name your-production-domain.com;
return 301 https://$server_name$request_uri;
}}
server {{
listen 443 ssl http2;
server_name your-production-domain.com;
# SSL Configuration
ssl_certificate /etc/ssl/certs/your-domain.crt;
ssl_certificate_key /etc/ssl/private/your-domain.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# Proxy to Flask application
location / {{
proxy_pass http://localhost:5058;
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 support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}}
# Static files
location /static/ {{
alias /path/to/your/static/files/;
expires 1y;
add_header Cache-Control "public, immutable";
}}
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {{
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:5058;
}}
# Health checks
location /healthz {{
access_log off;
proxy_pass http://localhost:5058;
}}
# OAuth callbacks - no rate limiting
location /api/auth