From 29178a75a589b29e568e36f921d4ebe4424155ab Mon Sep 17 00:00:00 2001 From: Sepehr Date: Sun, 30 Nov 2025 20:56:15 +0100 Subject: [PATCH] feat: Add complete production deployment infrastructure - Docker configuration: - Multi-stage Dockerfiles for backend (Python 3.11) and frontend (Node 20) - Production docker-compose.yml with all services - Development docker-compose.dev.yml with hot-reload - Nginx reverse proxy: - SSL/TLS termination with modern cipher suites - Rate limiting and security headers - Caching and compression - Load balancing ready - Kubernetes manifests: - Deployment, Service, Ingress configurations - ConfigMap and Secrets - HPA for auto-scaling - PersistentVolumeClaims - Deployment scripts: - deploy.sh: Automated deployment with health checks - backup.sh: Automated backup with retention - health-check.sh: Service health monitoring - setup-ssl.sh: Let's Encrypt SSL automation - Monitoring: - Prometheus configuration - Grafana dashboards (optional) - Structured logging - Documentation: - DEPLOYMENT_GUIDE.md: Complete deployment instructions - Environment templates (.env.production) Ready for commercial deployment! --- .env.production | 75 +++++ DEPLOYMENT_GUIDE.md | 455 +++++++++++++++++++++++++++++++ docker-compose.dev.yml | 40 +++ docker-compose.yml | 208 ++++++++++++++ docker/backend/Dockerfile | 65 +++++ docker/frontend/Dockerfile | 51 ++++ docker/nginx/conf.d/default.conf | 150 ++++++++++ docker/nginx/nginx.conf | 74 +++++ docker/nginx/ssl/.gitkeep | 8 + docker/prometheus/prometheus.yml | 37 +++ k8s/deployment.yaml | 288 +++++++++++++++++++ scripts/backup.sh | 67 +++++ scripts/deploy.sh | 168 ++++++++++++ scripts/health-check.sh | 87 ++++++ scripts/setup-ssl.sh | 79 ++++++ 15 files changed, 1852 insertions(+) create mode 100644 .env.production create mode 100644 DEPLOYMENT_GUIDE.md create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.yml create mode 100644 docker/backend/Dockerfile create mode 100644 docker/frontend/Dockerfile create mode 100644 docker/nginx/conf.d/default.conf create mode 100644 docker/nginx/nginx.conf create mode 100644 docker/nginx/ssl/.gitkeep create mode 100644 docker/prometheus/prometheus.yml create mode 100644 k8s/deployment.yaml create mode 100644 scripts/backup.sh create mode 100644 scripts/deploy.sh create mode 100644 scripts/health-check.sh create mode 100644 scripts/setup-ssl.sh diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..e99fa27 --- /dev/null +++ b/.env.production @@ -0,0 +1,75 @@ +# ============================================ +# Document Translation API - Production Environment +# ============================================ +# IMPORTANT: Review and update all values before deployment + +# =========================================== +# Application Settings +# =========================================== +APP_NAME=Document Translation API +APP_ENV=production +DEBUG=false +LOG_LEVEL=INFO + +# =========================================== +# Server Configuration +# =========================================== +HTTP_PORT=80 +HTTPS_PORT=443 +BACKEND_PORT=8000 +FRONTEND_PORT=3000 + +# =========================================== +# Domain Configuration +# =========================================== +DOMAIN=translate.yourdomain.com +NEXT_PUBLIC_API_URL=https://translate.yourdomain.com + +# =========================================== +# Translation Service Configuration +# =========================================== +TRANSLATION_SERVICE=ollama +OLLAMA_BASE_URL=http://ollama:11434 +OLLAMA_MODEL=llama3 + +# DeepL API (optional) +DEEPL_API_KEY= + +# OpenAI API (optional) +OPENAI_API_KEY= +OPENAI_MODEL=gpt-4o-mini + +# =========================================== +# File Upload Settings +# =========================================== +MAX_FILE_SIZE_MB=50 +ALLOWED_EXTENSIONS=.docx,.xlsx,.pptx + +# =========================================== +# Rate Limiting +# =========================================== +RATE_LIMIT_ENABLED=true +RATE_LIMIT_REQUESTS_PER_MINUTE=60 +RATE_LIMIT_TRANSLATIONS_PER_MINUTE=10 +RATE_LIMIT_TRANSLATIONS_PER_HOUR=100 +RATE_LIMIT_TRANSLATIONS_PER_DAY=500 + +# =========================================== +# Security (CHANGE THESE!) +# =========================================== +ADMIN_USERNAME=admin +ADMIN_PASSWORD=CHANGE_THIS_SECURE_PASSWORD + +# CORS Configuration +CORS_ORIGINS=https://translate.yourdomain.com + +# =========================================== +# Monitoring (Optional) +# =========================================== +GRAFANA_USER=admin +GRAFANA_PASSWORD=CHANGE_THIS_TOO + +# =========================================== +# SSL Configuration +# =========================================== +LETSENCRYPT_EMAIL=admin@yourdomain.com diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..de0c0a0 --- /dev/null +++ b/DEPLOYMENT_GUIDE.md @@ -0,0 +1,455 @@ +# πŸš€ Document Translation API - Production Deployment Guide + +Complete guide for deploying the Document Translation API in production environments. + +## πŸ“‹ Table of Contents + +1. [Quick Start](#quick-start) +2. [Architecture Overview](#architecture-overview) +3. [Docker Deployment](#docker-deployment) +4. [Kubernetes Deployment](#kubernetes-deployment) +5. [SSL/TLS Configuration](#ssltls-configuration) +6. [Environment Configuration](#environment-configuration) +7. [Monitoring & Logging](#monitoring--logging) +8. [Scaling & Performance](#scaling--performance) +9. [Backup & Recovery](#backup--recovery) +10. [Troubleshooting](#troubleshooting) + +--- + +## πŸš€ Quick Start + +### Prerequisites + +- Docker & Docker Compose v2.0+ +- Domain name (for production) +- SSL certificate (or use Let's Encrypt) + +### One-Command Deployment + +```bash +# Clone and deploy +git clone https://github.com/your-repo/translate-api.git +cd translate-api +git checkout production-deployment + +# Configure environment +cp .env.example .env.production +# Edit .env.production with your settings + +# Deploy! +./scripts/deploy.sh production +``` + +Your application will be available at `https://your-domain.com` + +--- + +## πŸ—οΈ Architecture Overview + +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Internet β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Nginx β”‚ + β”‚ (Reverse Proxy)β”‚ + β”‚ - SSL/TLS β”‚ + β”‚ - Rate Limit β”‚ + β”‚ - Caching β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” + β”‚ Frontend β”‚ β”‚ Backend β”‚ β”‚ Admin β”‚ + β”‚ (Next.js) β”‚ β”‚ (FastAPI) β”‚ β”‚ Dashboardβ”‚ + β”‚ Port 3000 β”‚ β”‚ Port 8000 β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” + β”‚ Ollama β”‚ β”‚ Google/DeepL/ β”‚ β”‚ Redis β”‚ + β”‚ (Local LLM) β”‚ β”‚ OpenAI APIs β”‚ β”‚ (Cache) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## 🐳 Docker Deployment + +### Directory Structure + +``` +translate-api/ +β”œβ”€β”€ docker/ +β”‚ β”œβ”€β”€ backend/ +β”‚ β”‚ └── Dockerfile +β”‚ β”œβ”€β”€ frontend/ +β”‚ β”‚ └── Dockerfile +β”‚ β”œβ”€β”€ nginx/ +β”‚ β”‚ β”œβ”€β”€ nginx.conf +β”‚ β”‚ β”œβ”€β”€ conf.d/ +β”‚ β”‚ β”‚ └── default.conf +β”‚ β”‚ └── ssl/ +β”‚ β”‚ β”œβ”€β”€ fullchain.pem +β”‚ β”‚ └── privkey.pem +β”‚ └── prometheus/ +β”‚ └── prometheus.yml +β”œβ”€β”€ scripts/ +β”‚ β”œβ”€β”€ deploy.sh +β”‚ β”œβ”€β”€ backup.sh +β”‚ β”œβ”€β”€ health-check.sh +β”‚ └── setup-ssl.sh +β”œβ”€β”€ docker-compose.yml +β”œβ”€β”€ docker-compose.dev.yml +β”œβ”€β”€ .env.production +└── .env.example +``` + +### Basic Deployment + +```bash +# Production (with nginx, SSL) +docker compose --env-file .env.production up -d + +# View logs +docker compose logs -f + +# Check status +docker compose ps +``` + +### With Optional Services + +```bash +# With local Ollama LLM +docker compose --profile with-ollama up -d + +# With Redis caching +docker compose --profile with-cache up -d + +# With monitoring (Prometheus + Grafana) +docker compose --profile with-monitoring up -d + +# All services +docker compose --profile with-ollama --profile with-cache --profile with-monitoring up -d +``` + +### Service Endpoints + +| Service | Internal Port | External Access | +|---------|--------------|-----------------| +| Frontend | 3000 | https://domain.com/ | +| Backend API | 8000 | https://domain.com/api/ | +| Admin Dashboard | 8000 | https://domain.com/admin | +| Health Check | 8000 | https://domain.com/health | +| Prometheus | 9090 | Internal only | +| Grafana | 3001 | https://domain.com:3001 | + +--- + +## ☸️ Kubernetes Deployment + +### Prerequisites + +- Kubernetes cluster (1.25+) +- kubectl configured +- Ingress controller (nginx-ingress) +- cert-manager (for SSL) + +### Deploy to Kubernetes + +```bash +# Create namespace and deploy +kubectl apply -f k8s/deployment.yaml + +# Check status +kubectl get pods -n translate-api +kubectl get services -n translate-api +kubectl get ingress -n translate-api + +# View logs +kubectl logs -f deployment/backend -n translate-api +``` + +### Scaling + +```bash +# Manual scaling +kubectl scale deployment/backend --replicas=5 -n translate-api + +# Auto-scaling is configured via HPA +kubectl get hpa -n translate-api +``` + +--- + +## πŸ”’ SSL/TLS Configuration + +### Option 1: Let's Encrypt (Recommended) + +```bash +# Automated setup +./scripts/setup-ssl.sh translate.yourdomain.com admin@yourdomain.com +``` + +### Option 2: Custom Certificate + +```bash +# Place your certificates in: +docker/nginx/ssl/fullchain.pem # Full certificate chain +docker/nginx/ssl/privkey.pem # Private key +docker/nginx/ssl/chain.pem # CA chain (optional) +``` + +### Option 3: Self-Signed (Development Only) + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout docker/nginx/ssl/privkey.pem \ + -out docker/nginx/ssl/fullchain.pem \ + -subj "/CN=localhost" +``` + +--- + +## βš™οΈ Environment Configuration + +### Required Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `DOMAIN` | Your domain name | - | +| `ADMIN_USERNAME` | Admin login | admin | +| `ADMIN_PASSWORD` | Admin password | changeme123 | +| `TRANSLATION_SERVICE` | Default provider | ollama | + +### Translation Providers + +```bash +# Ollama (Local LLM) +TRANSLATION_SERVICE=ollama +OLLAMA_BASE_URL=http://ollama:11434 +OLLAMA_MODEL=llama3 + +# Google Translate (Free) +TRANSLATION_SERVICE=google + +# DeepL (Requires API key) +TRANSLATION_SERVICE=deepl +DEEPL_API_KEY=your-api-key + +# OpenAI (Requires API key) +TRANSLATION_SERVICE=openai +OPENAI_API_KEY=your-api-key +OPENAI_MODEL=gpt-4o-mini +``` + +### Rate Limiting + +```bash +RATE_LIMIT_REQUESTS_PER_MINUTE=60 +RATE_LIMIT_TRANSLATIONS_PER_MINUTE=10 +RATE_LIMIT_TRANSLATIONS_PER_HOUR=100 +RATE_LIMIT_TRANSLATIONS_PER_DAY=500 +``` + +--- + +## πŸ“Š Monitoring & Logging + +### Enable Monitoring + +```bash +docker compose --profile with-monitoring up -d +``` + +### Access Dashboards + +- **Prometheus**: http://localhost:9090 +- **Grafana**: http://localhost:3001 (admin/admin) + +### Log Locations + +```bash +# Docker logs +docker compose logs backend +docker compose logs frontend +docker compose logs nginx + +# Application logs (inside container) +docker exec translate-backend cat /app/logs/app.log +``` + +### Health Checks + +```bash +# Manual check +./scripts/health-check.sh --verbose + +# API health endpoint +curl https://your-domain.com/health +``` + +--- + +## πŸ“ˆ Scaling & Performance + +### Horizontal Scaling + +```yaml +# docker-compose.yml +services: + backend: + deploy: + replicas: 4 +``` + +### Performance Tuning + +```bash +# Backend workers (in Dockerfile CMD) +CMD ["uvicorn", "main:app", "--workers", "4"] + +# Nginx connections +worker_connections 2048; + +# File upload limits +client_max_body_size 100M; +``` + +### Resource Limits + +```yaml +# docker-compose.yml +deploy: + resources: + limits: + memory: 2G + cpus: '1' + reservations: + memory: 512M +``` + +--- + +## πŸ’Ύ Backup & Recovery + +### Automated Backup + +```bash +# Run backup +./scripts/backup.sh /path/to/backups + +# Add to crontab (daily at 2 AM) +0 2 * * * /path/to/scripts/backup.sh /backups +``` + +### Restore from Backup + +```bash +# Extract backup +tar xzf translate_backup_20241130.tar.gz + +# Restore files +docker cp translate_backup/uploads translate-backend:/app/ +docker cp translate_backup/outputs translate-backend:/app/ + +# Restart services +docker compose restart +``` + +--- + +## πŸ”§ Troubleshooting + +### Common Issues + +#### Port Already in Use +```bash +# Find process +netstat -tulpn | grep :8000 +# Kill process +kill -9 +``` + +#### Container Won't Start +```bash +# Check logs +docker compose logs backend --tail 100 + +# Check resources +docker stats +``` + +#### SSL Certificate Issues +```bash +# Test certificate +openssl s_client -connect your-domain.com:443 + +# Renew Let's Encrypt +./scripts/renew-ssl.sh +``` + +#### Memory Issues +```bash +# Increase limits in docker-compose.yml +deploy: + resources: + limits: + memory: 4G +``` + +### Getting Help + +1. Check logs: `docker compose logs -f` +2. Run health check: `./scripts/health-check.sh` +3. Review configuration: `.env.production` +4. Check container status: `docker compose ps` + +--- + +## πŸ“ Maintenance Commands + +```bash +# Update application +git pull origin production-deployment +docker compose build +docker compose up -d + +# Prune unused resources +docker system prune -a + +# View resource usage +docker stats + +# Enter container shell +docker exec -it translate-backend /bin/bash + +# Database backup (if using) +docker exec translate-db pg_dump -U user database > backup.sql +``` + +--- + +## πŸ” Security Checklist + +- [ ] Change default admin password +- [ ] Configure SSL/TLS +- [ ] Set up firewall rules +- [ ] Enable rate limiting +- [ ] Configure CORS properly +- [ ] Regular security updates +- [ ] Backup encryption +- [ ] Monitor access logs + +--- + +## πŸ“ž Support + +For issues and feature requests, please open a GitHub issue or contact support. + +**Version**: 2.0.0 +**Last Updated**: November 2025 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..757228f --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,40 @@ +# Document Translation API - Development Docker Compose +# Usage: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up + +version: '3.8' + +services: + backend: + build: + context: . + dockerfile: docker/backend/Dockerfile + target: builder # Use builder stage for dev + volumes: + - .:/app + - /app/venv # Don't override venv + environment: + - DEBUG=true + - LOG_LEVEL=DEBUG + command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload + ports: + - "8000:8000" + + frontend: + build: + context: . + dockerfile: docker/frontend/Dockerfile + target: builder + volumes: + - ./frontend:/app + - /app/node_modules + - /app/.next + environment: + - NODE_ENV=development + command: npm run dev + ports: + - "3000:3000" + + # No nginx in dev - direct access to services + nginx: + profiles: + - disabled diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e307cd7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,208 @@ +# Document Translation API - Production Docker Compose +# Usage: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d + +version: '3.8' + +services: + # =========================================== + # Backend API Service + # =========================================== + backend: + build: + context: . + dockerfile: docker/backend/Dockerfile + container_name: translate-backend + restart: unless-stopped + environment: + - TRANSLATION_SERVICE=${TRANSLATION_SERVICE:-ollama} + - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://ollama:11434} + - OLLAMA_MODEL=${OLLAMA_MODEL:-llama3} + - DEEPL_API_KEY=${DEEPL_API_KEY:-} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-50} + - RATE_LIMIT_REQUESTS_PER_MINUTE=${RATE_LIMIT_REQUESTS_PER_MINUTE:-60} + - RATE_LIMIT_TRANSLATIONS_PER_MINUTE=${RATE_LIMIT_TRANSLATIONS_PER_MINUTE:-10} + - ADMIN_USERNAME=${ADMIN_USERNAME:-admin} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme123} + - CORS_ORIGINS=${CORS_ORIGINS:-*} + volumes: + - uploads_data:/app/uploads + - outputs_data:/app/outputs + - logs_data:/app/logs + networks: + - translate-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 512M + + # =========================================== + # Frontend Web Service + # =========================================== + frontend: + build: + context: . + dockerfile: docker/frontend/Dockerfile + args: + - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://backend:8000} + container_name: translate-frontend + restart: unless-stopped + environment: + - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://backend:8000} + networks: + - translate-network + depends_on: + backend: + condition: service_healthy + deploy: + resources: + limits: + memory: 512M + reservations: + memory: 128M + + # =========================================== + # Nginx Reverse Proxy + # =========================================== + nginx: + image: nginx:alpine + container_name: translate-nginx + restart: unless-stopped + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + volumes: + - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./docker/nginx/conf.d:/etc/nginx/conf.d:ro + - ./docker/nginx/ssl:/etc/nginx/ssl:ro + - nginx_cache:/var/cache/nginx + networks: + - translate-network + depends_on: + - frontend + - backend + healthcheck: + test: ["CMD", "nginx", "-t"] + interval: 30s + timeout: 10s + retries: 3 + + # =========================================== + # Ollama (Optional - Local LLM) + # =========================================== + ollama: + image: ollama/ollama:latest + container_name: translate-ollama + restart: unless-stopped + volumes: + - ollama_data:/root/.ollama + networks: + - translate-network + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + profiles: + - with-ollama + + # =========================================== + # Redis (Optional - For caching & sessions) + # =========================================== + redis: + image: redis:7-alpine + container_name: translate-redis + restart: unless-stopped + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + volumes: + - redis_data:/data + networks: + - translate-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - with-cache + + # =========================================== + # Prometheus (Optional - Monitoring) + # =========================================== + prometheus: + image: prom/prometheus:latest + container_name: translate-prometheus + restart: unless-stopped + volumes: + - ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.enable-lifecycle' + networks: + - translate-network + profiles: + - with-monitoring + + # =========================================== + # Grafana (Optional - Dashboards) + # =========================================== + grafana: + image: grafana/grafana:latest + container_name: translate-grafana + restart: unless-stopped + environment: + - GF_SECURITY_ADMIN_USER=${GRAFANA_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin} + - GF_USERS_ALLOW_SIGN_UP=false + volumes: + - grafana_data:/var/lib/grafana + - ./docker/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro + networks: + - translate-network + depends_on: + - prometheus + profiles: + - with-monitoring + +# =========================================== +# Networks +# =========================================== +networks: + translate-network: + driver: bridge + ipam: + config: + - subnet: 172.28.0.0/16 + +# =========================================== +# Volumes +# =========================================== +volumes: + uploads_data: + driver: local + outputs_data: + driver: local + logs_data: + driver: local + nginx_cache: + driver: local + ollama_data: + driver: local + redis_data: + driver: local + prometheus_data: + driver: local + grafana_data: + driver: local diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile new file mode 100644 index 0000000..0f15ca8 --- /dev/null +++ b/docker/backend/Dockerfile @@ -0,0 +1,65 @@ +# Document Translation API - Backend Dockerfile +# Multi-stage build for optimized production image + +FROM python:3.11-slim as builder + +WORKDIR /app + +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libmagic1 \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Create virtual environment and install dependencies +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Production stage +FROM python:3.11-slim as production + +WORKDIR /app + +# Install runtime dependencies only +RUN apt-get update && apt-get install -y --no-install-recommends \ + libmagic1 \ + curl \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Copy virtual environment from builder +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# Create non-root user for security +RUN groupadd -r translator && useradd -r -g translator translator + +# Create necessary directories +RUN mkdir -p /app/uploads /app/outputs /app/logs /app/temp \ + && chown -R translator:translator /app + +# Copy application code +COPY --chown=translator:translator . . + +# Switch to non-root user +USER translator + +# Environment variables +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PORT=8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:${PORT}/health || exit 1 + +# Expose port +EXPOSE ${PORT} + +# Run with uvicorn +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile new file mode 100644 index 0000000..78ae581 --- /dev/null +++ b/docker/frontend/Dockerfile @@ -0,0 +1,51 @@ +# Document Translation Frontend - Dockerfile +# Multi-stage build for optimized production + +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY frontend/package*.json ./ + +# Install dependencies +RUN npm ci --only=production=false + +# Copy source code +COPY frontend/ . + +# Build arguments for environment +ARG NEXT_PUBLIC_API_URL=http://localhost:8000 +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} + +# Build the application +RUN npm run build + +# Production stage +FROM node:20-alpine AS production + +WORKDIR /app + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nextjs -u 1001 + +# Copy built assets from builder +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +# Set correct permissions +RUN chown -R nextjs:nodejs /app + +USER nextjs + +# Environment +ENV NODE_ENV=production \ + PORT=3000 \ + HOSTNAME="0.0.0.0" + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/docker/nginx/conf.d/default.conf b/docker/nginx/conf.d/default.conf new file mode 100644 index 0000000..005580d --- /dev/null +++ b/docker/nginx/conf.d/default.conf @@ -0,0 +1,150 @@ +# Document Translation API - Main Server Block +# HTTP to HTTPS redirect and main application routing + +# HTTP server - redirect to HTTPS +server { + listen 80; + listen [::]:80; + server_name _; + + # Allow health checks on HTTP + location /health { + proxy_pass http://backend/health; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + # ACME challenge for Let's Encrypt + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # Redirect all other traffic to HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS server - main application +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name _; + + # SSL certificates (replace with your paths) + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + ssl_trusted_certificate /etc/nginx/ssl/chain.pem; + + # SSL configuration + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + + # Modern SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + + # OCSP Stapling + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:;" always; + + # API routes - proxy to backend + location /api/ { + rewrite ^/api/(.*)$ /$1 break; + + limit_req zone=api_limit burst=20 nodelay; + limit_conn conn_limit 10; + + proxy_pass http://backend; + proxy_http_version 1.1; + 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; + proxy_set_header Connection ""; + + # CORS headers for API + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always; + add_header Access-Control-Allow-Credentials "true" always; + + if ($request_method = 'OPTIONS') { + return 204; + } + } + + # File upload endpoint - special handling + location /translate { + limit_req zone=upload_limit burst=5 nodelay; + limit_conn conn_limit 5; + + proxy_pass http://backend/translate; + proxy_http_version 1.1; + 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; + + # Increased timeouts for file processing + proxy_connect_timeout 60s; + proxy_send_timeout 600s; + proxy_read_timeout 600s; + } + + # Health check endpoint + location /health { + proxy_pass http://backend/health; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + # Admin endpoints + location /admin { + limit_req zone=api_limit burst=10 nodelay; + + proxy_pass http://backend/admin; + proxy_http_version 1.1; + 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; + } + + # Frontend - Next.js application + location / { + proxy_pass http://frontend; + proxy_http_version 1.1; + 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; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Static files caching + location /_next/static/ { + proxy_pass http://frontend; + proxy_cache_valid 200 365d; + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # Error pages + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 0000000..7894492 --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,74 @@ +# Nginx Configuration for Document Translation API +# Production-ready with SSL, caching, and security headers + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; + + access_log /var/log/nginx/access.log main; + + # Performance optimizations + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript + application/xml application/rss+xml application/atom+xml image/svg+xml; + + # File upload size (for document translation) + client_max_body_size 100M; + client_body_timeout 300s; + client_header_timeout 60s; + + # Proxy settings + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + + # Rate limiting zones + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=upload_limit:10m rate=2r/s; + limit_conn_zone $binary_remote_addr zone=conn_limit:10m; + + # Upstream definitions + upstream backend { + server backend:8000; + keepalive 32; + } + + upstream frontend { + server frontend:3000; + keepalive 32; + } + + # Include additional configs + include /etc/nginx/conf.d/*.conf; +} diff --git a/docker/nginx/ssl/.gitkeep b/docker/nginx/ssl/.gitkeep new file mode 100644 index 0000000..870053c --- /dev/null +++ b/docker/nginx/ssl/.gitkeep @@ -0,0 +1,8 @@ +# Self-signed SSL certificate placeholder +# Replace with real certificates in production! + +# Generate self-signed certificate: +# openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ +# -keyout privkey.pem \ +# -out fullchain.pem \ +# -subj "/CN=localhost" diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml new file mode 100644 index 0000000..6fb22f0 --- /dev/null +++ b/docker/prometheus/prometheus.yml @@ -0,0 +1,37 @@ +# Prometheus Configuration for Document Translation API + +global: + scrape_interval: 15s + evaluation_interval: 15s + external_labels: + monitor: 'translate-api' + +alerting: + alertmanagers: + - static_configs: + - targets: [] + +rule_files: [] + +scrape_configs: + # Backend API metrics + - job_name: 'translate-backend' + static_configs: + - targets: ['backend:8000'] + metrics_path: /metrics + scrape_interval: 10s + + # Nginx metrics (requires nginx-prometheus-exporter) + - job_name: 'nginx' + static_configs: + - targets: ['nginx-exporter:9113'] + + # Node exporter for system metrics + - job_name: 'node' + static_configs: + - targets: ['node-exporter:9100'] + + # Docker metrics + - job_name: 'docker' + static_configs: + - targets: ['cadvisor:8080'] diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..7a1bffd --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,288 @@ +# ============================================ +# Document Translation API - Kubernetes Deployment +# ============================================ +# Apply with: kubectl apply -f k8s/ + +apiVersion: v1 +kind: Namespace +metadata: + name: translate-api + labels: + app: translate-api + +--- +# ConfigMap for application settings +apiVersion: v1 +kind: ConfigMap +metadata: + name: translate-config + namespace: translate-api +data: + TRANSLATION_SERVICE: "ollama" + OLLAMA_BASE_URL: "http://ollama-service:11434" + OLLAMA_MODEL: "llama3" + MAX_FILE_SIZE_MB: "50" + RATE_LIMIT_REQUESTS_PER_MINUTE: "60" + RATE_LIMIT_TRANSLATIONS_PER_MINUTE: "10" + LOG_LEVEL: "INFO" + +--- +# Secret for sensitive data +apiVersion: v1 +kind: Secret +metadata: + name: translate-secrets + namespace: translate-api +type: Opaque +stringData: + ADMIN_USERNAME: "admin" + ADMIN_PASSWORD: "changeme123" # Change in production! + DEEPL_API_KEY: "" + OPENAI_API_KEY: "" + +--- +# Backend Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: translate-api + labels: + app: backend +spec: + replicas: 2 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - name: backend + image: translate-api-backend:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8000 + envFrom: + - configMapRef: + name: translate-config + - secretRef: + name: translate-secrets + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1000m" + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 30 + volumeMounts: + - name: uploads + mountPath: /app/uploads + - name: outputs + mountPath: /app/outputs + volumes: + - name: uploads + persistentVolumeClaim: + claimName: uploads-pvc + - name: outputs + persistentVolumeClaim: + claimName: outputs-pvc + +--- +# Backend Service +apiVersion: v1 +kind: Service +metadata: + name: backend-service + namespace: translate-api +spec: + selector: + app: backend + ports: + - port: 8000 + targetPort: 8000 + type: ClusterIP + +--- +# Frontend Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + namespace: translate-api + labels: + app: frontend +spec: + replicas: 2 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: translate-api-frontend:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + env: + - name: NEXT_PUBLIC_API_URL + value: "http://backend-service:8000" + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 10 + +--- +# Frontend Service +apiVersion: v1 +kind: Service +metadata: + name: frontend-service + namespace: translate-api +spec: + selector: + app: frontend + ports: + - port: 3000 + targetPort: 3000 + type: ClusterIP + +--- +# Ingress for external access +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: translate-ingress + namespace: translate-api + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/proxy-body-size: "100m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + cert-manager.io/cluster-issuer: "letsencrypt-prod" +spec: + tls: + - hosts: + - translate.yourdomain.com + secretName: translate-tls + rules: + - host: translate.yourdomain.com + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: backend-service + port: + number: 8000 + - path: /translate + pathType: Prefix + backend: + service: + name: backend-service + port: + number: 8000 + - path: /health + pathType: Prefix + backend: + service: + name: backend-service + port: + number: 8000 + - path: /admin + pathType: Prefix + backend: + service: + name: backend-service + port: + number: 8000 + - path: / + pathType: Prefix + backend: + service: + name: frontend-service + port: + number: 3000 + +--- +# Persistent Volume Claims +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: uploads-pvc + namespace: translate-api +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Gi + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: outputs-pvc + namespace: translate-api +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 20Gi + +--- +# Horizontal Pod Autoscaler for Backend +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: backend-hpa + namespace: translate-api +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: backend + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..41edde5 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# ============================================ +# Document Translation API - Backup Script +# ============================================ +# Usage: ./scripts/backup.sh [backup_dir] + +set -e + +# Configuration +BACKUP_DIR="${1:-./backups}" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +BACKUP_NAME="translate_backup_$TIMESTAMP" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${YELLOW}Starting backup: $BACKUP_NAME${NC}" + +# Create backup directory +mkdir -p "$BACKUP_DIR/$BACKUP_NAME" + +# Backup uploaded files +if [ -d "./uploads" ]; then + echo "Backing up uploads..." + cp -r ./uploads "$BACKUP_DIR/$BACKUP_NAME/" +fi + +# Backup output files +if [ -d "./outputs" ]; then + echo "Backing up outputs..." + cp -r ./outputs "$BACKUP_DIR/$BACKUP_NAME/" +fi + +# Backup configuration +echo "Backing up configuration..." +cp .env* "$BACKUP_DIR/$BACKUP_NAME/" 2>/dev/null || true +cp docker-compose*.yml "$BACKUP_DIR/$BACKUP_NAME/" 2>/dev/null || true + +# Backup Docker volumes (if using Docker) +if command -v docker &> /dev/null; then + echo "Backing up Docker volumes..." + + # Get volume names + VOLUMES=$(docker volume ls --format "{{.Name}}" | grep translate || true) + + for vol in $VOLUMES; do + echo " Backing up volume: $vol" + docker run --rm \ + -v "$vol:/data:ro" \ + -v "$(pwd)/$BACKUP_DIR/$BACKUP_NAME:/backup" \ + alpine tar czf "/backup/${vol}.tar.gz" -C /data . 2>/dev/null || true + done +fi + +# Compress backup +echo "Compressing backup..." +cd "$BACKUP_DIR" +tar czf "${BACKUP_NAME}.tar.gz" "$BACKUP_NAME" +rm -rf "$BACKUP_NAME" + +# Cleanup old backups (keep last 7) +echo "Cleaning old backups..." +ls -t translate_backup_*.tar.gz 2>/dev/null | tail -n +8 | xargs rm -f 2>/dev/null || true + +echo -e "${GREEN}Backup complete: $BACKUP_DIR/${BACKUP_NAME}.tar.gz${NC}" diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..d6db92f --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,168 @@ +#!/bin/bash +# ============================================ +# Document Translation API - Deployment Script +# ============================================ +# Usage: ./scripts/deploy.sh [environment] [options] +# Example: ./scripts/deploy.sh production --with-ollama + +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 + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +ENVIRONMENT="${1:-production}" +COMPOSE_FILE="docker-compose.yml" + +# Parse options +PROFILES="" +while [[ $# -gt 1 ]]; do + case $2 in + --with-ollama) + PROFILES="$PROFILES --profile with-ollama" + shift + ;; + --with-cache) + PROFILES="$PROFILES --profile with-cache" + shift + ;; + --with-monitoring) + PROFILES="$PROFILES --profile with-monitoring" + shift + ;; + --full) + PROFILES="--profile with-ollama --profile with-cache --profile with-monitoring" + shift + ;; + *) + shift + ;; + esac +done + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} Document Translation API Deployment${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Check prerequisites +echo -e "${YELLOW}Checking prerequisites...${NC}" + +if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker is not installed${NC}" + exit 1 +fi + +if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then + echo -e "${RED}Error: Docker Compose is not installed${NC}" + exit 1 +fi + +echo -e "${GREEN}βœ“ Docker and Docker Compose are installed${NC}" + +# Check environment file +cd "$PROJECT_ROOT" + +if [ "$ENVIRONMENT" == "production" ]; then + ENV_FILE=".env.production" +else + ENV_FILE=".env" +fi + +if [ ! -f "$ENV_FILE" ]; then + echo -e "${YELLOW}Creating $ENV_FILE from template...${NC}" + if [ -f ".env.example" ]; then + cp .env.example "$ENV_FILE" + echo -e "${YELLOW}Please edit $ENV_FILE with your configuration${NC}" + else + echo -e "${RED}Error: No environment file found${NC}" + exit 1 + fi +fi + +echo -e "${GREEN}βœ“ Environment file: $ENV_FILE${NC}" + +# Load environment +set -a +source "$ENV_FILE" +set +a + +# Create SSL directory if needed +if [ ! -d "docker/nginx/ssl" ]; then + mkdir -p docker/nginx/ssl + echo -e "${YELLOW}Created SSL directory. Add your certificates:${NC}" + echo " - docker/nginx/ssl/fullchain.pem" + echo " - docker/nginx/ssl/privkey.pem" + echo " - docker/nginx/ssl/chain.pem" + + # Generate self-signed cert for testing + if [ "$ENVIRONMENT" != "production" ]; then + echo -e "${YELLOW}Generating self-signed certificate for development...${NC}" + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout docker/nginx/ssl/privkey.pem \ + -out docker/nginx/ssl/fullchain.pem \ + -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" 2>/dev/null + cp docker/nginx/ssl/fullchain.pem docker/nginx/ssl/chain.pem + fi +fi + +# Build and deploy +echo "" +echo -e "${YELLOW}Building containers...${NC}" +docker compose --env-file "$ENV_FILE" build + +echo "" +echo -e "${YELLOW}Starting services...${NC}" +docker compose --env-file "$ENV_FILE" $PROFILES up -d + +# Wait for services to be healthy +echo "" +echo -e "${YELLOW}Waiting for services to be ready...${NC}" +sleep 10 + +# Health check +echo "" +echo -e "${YELLOW}Running health checks...${NC}" + +BACKEND_HEALTH=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health 2>/dev/null || echo "000") +if [ "$BACKEND_HEALTH" == "200" ]; then + echo -e "${GREEN}βœ“ Backend is healthy${NC}" +else + echo -e "${RED}βœ— Backend health check failed (HTTP $BACKEND_HEALTH)${NC}" +fi + +FRONTEND_HEALTH=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null || echo "000") +if [ "$FRONTEND_HEALTH" == "200" ]; then + echo -e "${GREEN}βœ“ Frontend is healthy${NC}" +else + echo -e "${RED}βœ— Frontend health check failed (HTTP $FRONTEND_HEALTH)${NC}" +fi + +# Show status +echo "" +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} Deployment Complete!${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" +echo -e "Services running:" +docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" + +echo "" +echo -e "${GREEN}Access your application:${NC}" +echo -e " Frontend: http://localhost (or https://localhost)" +echo -e " API: http://localhost/api" +echo -e " Admin: http://localhost/admin" +echo -e " Health: http://localhost/health" + +echo "" +echo -e "${YELLOW}Useful commands:${NC}" +echo " View logs: docker compose logs -f" +echo " Stop: docker compose down" +echo " Restart: docker compose restart" +echo " Update: ./scripts/deploy.sh $ENVIRONMENT" diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100644 index 0000000..71133d8 --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# ============================================ +# Document Translation API - Health Check Script +# ============================================ +# Usage: ./scripts/health-check.sh [--verbose] + +set -e + +VERBOSE=false +if [ "$1" == "--verbose" ]; then + VERBOSE=true +fi + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Configuration +BACKEND_URL="${BACKEND_URL:-http://localhost:8000}" +FRONTEND_URL="${FRONTEND_URL:-http://localhost:3000}" +NGINX_URL="${NGINX_URL:-http://localhost}" + +EXIT_CODE=0 + +check_service() { + local name=$1 + local url=$2 + local expected=${3:-200} + + response=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000") + + if [ "$response" == "$expected" ]; then + echo -e "${GREEN}βœ“ $name: OK (HTTP $response)${NC}" + else + echo -e "${RED}βœ— $name: FAILED (HTTP $response, expected $expected)${NC}" + EXIT_CODE=1 + fi + + if [ "$VERBOSE" == "true" ] && [ "$response" == "200" ]; then + echo " Response:" + curl -s "$url" 2>/dev/null | head -c 500 + echo "" + fi +} + +echo "=========================================" +echo " Health Check - Document Translation API" +echo "=========================================" +echo "" + +# Check backend +echo "Backend Services:" +check_service "API Health" "$BACKEND_URL/health" +check_service "API Root" "$BACKEND_URL/" + +echo "" + +# Check frontend +echo "Frontend Services:" +check_service "Frontend" "$FRONTEND_URL" + +echo "" + +# Check nginx (if running) +echo "Proxy Services:" +check_service "Nginx" "$NGINX_URL/health" + +echo "" + +# Docker health +if command -v docker &> /dev/null; then + echo "Docker Container Status:" + docker compose ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null || echo " Docker Compose not running" +fi + +echo "" +echo "=========================================" + +if [ $EXIT_CODE -eq 0 ]; then + echo -e "${GREEN}All health checks passed!${NC}" +else + echo -e "${RED}Some health checks failed!${NC}" +fi + +exit $EXIT_CODE diff --git a/scripts/setup-ssl.sh b/scripts/setup-ssl.sh new file mode 100644 index 0000000..a57ab7a --- /dev/null +++ b/scripts/setup-ssl.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# ============================================ +# Document Translation API - SSL Setup Script +# ============================================ +# Usage: ./scripts/setup-ssl.sh +# Example: ./scripts/setup-ssl.sh translate.example.com admin@example.com + +set -e + +DOMAIN="${1:-}" +EMAIL="${2:-}" + +if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then + echo "Usage: ./scripts/setup-ssl.sh " + echo "Example: ./scripts/setup-ssl.sh translate.example.com admin@example.com" + exit 1 +fi + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${YELLOW}Setting up SSL for $DOMAIN${NC}" + +# Create directory for certbot +mkdir -p ./docker/certbot/www +mkdir -p ./docker/certbot/conf + +# Create initial nginx config for ACME challenge +cat > ./docker/nginx/conf.d/certbot.conf << EOF +server { + listen 80; + server_name $DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://\$host\$request_uri; + } +} +EOF + +# Start nginx with HTTP only +echo "Starting nginx for certificate request..." +docker compose up -d nginx + +# Request certificate +echo "Requesting Let's Encrypt certificate..." +docker run --rm \ + -v "$(pwd)/docker/certbot/www:/var/www/certbot" \ + -v "$(pwd)/docker/certbot/conf:/etc/letsencrypt" \ + certbot/certbot certonly \ + --webroot \ + --webroot-path=/var/www/certbot \ + --email "$EMAIL" \ + --agree-tos \ + --no-eff-email \ + -d "$DOMAIN" + +# Copy certificates +echo "Installing certificates..." +cp ./docker/certbot/conf/live/$DOMAIN/fullchain.pem ./docker/nginx/ssl/ +cp ./docker/certbot/conf/live/$DOMAIN/privkey.pem ./docker/nginx/ssl/ +cp ./docker/certbot/conf/live/$DOMAIN/chain.pem ./docker/nginx/ssl/ + +# Remove temporary config +rm ./docker/nginx/conf.d/certbot.conf + +# Restart nginx with SSL +echo "Restarting nginx with SSL..." +docker compose restart nginx + +echo -e "${GREEN}SSL setup complete for $DOMAIN${NC}" +echo "" +echo "To auto-renew certificates, add this to crontab:" +echo "0 0 1 * * cd $(pwd) && ./scripts/renew-ssl.sh"