diff --git a/deployment/.env.example b/deployment/.env.example new file mode 100644 index 0000000..c86cbf8 --- /dev/null +++ b/deployment/.env.example @@ -0,0 +1,12 @@ +# Database Configuration +DB_USER=agenda +DB_PASSWORD=your-secure-database-password-here +DB_NAME=agenda_tasks + +# Application Configuration +SECRET_KEY=your-secret-key-min-32-characters-here +DEBUG=false + +# Domain Configuration (for SSL) +DOMAIN=your-domain.com +EMAIL=admin@your-domain.com diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 0000000..c0792d8 --- /dev/null +++ b/deployment/README.md @@ -0,0 +1,319 @@ +# Agenda Tasks - Server Deployment Guide + +This guide explains how to deploy the Agenda Tasks backend to a Linode server. + +## Server Requirements + +**Recommended: Linode 2GB ($12/month)** +- 1 CPU core +- 2 GB RAM +- 50 GB SSD storage +- Ubuntu 22.04 LTS + +This configuration is sufficient for a small to medium user base. + +## Prerequisites + +1. A Linode server with Ubuntu 22.04 LTS +2. A domain name pointing to your server's IP address +3. SSH access to the server + +## Quick Start + +### 1. Connect to Your Server + +```bash +ssh root@your-server-ip +``` + +### 2. Install Docker + +```bash +# Update system +apt update && apt upgrade -y + +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh + +# Install Docker Compose +apt install docker-compose-plugin -y + +# Start Docker +systemctl enable docker +systemctl start docker +``` + +### 3. Clone the Repository + +```bash +# Create app directory +mkdir -p /opt/agenda-tasks +cd /opt/agenda-tasks + +# Clone repository (or copy files) +git clone https://your-repo-url.git . +``` + +### 4. Configure Environment + +```bash +cd deployment + +# Copy environment template +cp .env.example .env + +# Edit configuration +nano .env +``` + +Configure the following variables: + +```bash +# Database Configuration +DB_USER=agenda +DB_PASSWORD= +DB_NAME=agenda_tasks + +# Application Configuration +SECRET_KEY= +DEBUG=false + +# Domain Configuration +DOMAIN=your-domain.com +EMAIL=admin@your-domain.com +``` + +**Generate Secure Values:** + +```bash +# Generate DB_PASSWORD (32 characters) +openssl rand -base64 32 + +# Generate SECRET_KEY (32 characters) +openssl rand -hex 32 +``` + +### 5. Deploy + +```bash +cd /opt/agenda-tasks/deployment/scripts +./deploy.sh +``` + +### 6. Set Up SSL + +After DNS propagation (may take up to 48 hours): + +```bash +./setup-ssl.sh +``` + +## Directory Structure + +``` +deployment/ +├── docker/ +│ ├── Dockerfile.backend # Backend container +│ └── docker-compose.yml # Service orchestration +├── nginx/ +│ ├── nginx.conf # Main nginx config +│ └── conf.d/ +│ └── default.conf # Server block config +├── scripts/ +│ ├── deploy.sh # Deployment script +│ ├── backup-db.sh # Database backup +│ └── setup-ssl.sh # SSL certificate setup +├── backups/ # Database backups (created automatically) +├── .env.example # Environment template +└── README.md # This file +``` + +## Services + +| Service | Port | Description | +|---------|------|-------------| +| PostgreSQL | 5432 (internal) | Database | +| Backend | 8000 (internal) | FastAPI application | +| Nginx | 80, 443 | Reverse proxy with SSL | +| Certbot | - | SSL certificate management | + +## API Endpoints + +After deployment, your API will be available at: + +``` +https://your-domain.com/ +``` + +### Authentication Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | /auth/register | Register new user | +| POST | /auth/login | Login and get tokens | +| POST | /auth/refresh | Refresh access token | +| GET | /auth/me | Get current user info | + +### Task Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | /tasks?date=YYYY-MM-DD | Get tasks for date | +| POST | /tasks | Create new task | +| GET | /tasks/{id} | Get task by ID | +| PUT | /tasks/{id} | Update task | +| DELETE | /tasks/{id} | Delete task | +| PATCH | /tasks/{id}/toggle | Toggle task status | +| POST | /tasks/{id}/reschedule | Reschedule task | + +### API Documentation + +Interactive API documentation is available at: +- Swagger UI: `https://your-domain.com/docs` +- ReDoc: `https://your-domain.com/redoc` + +## Maintenance + +### View Logs + +```bash +cd /opt/agenda-tasks/deployment/docker + +# All services +docker compose logs -f + +# Specific service +docker compose logs -f backend +docker compose logs -f nginx +docker compose logs -f db +``` + +### Database Backup + +```bash +cd /opt/agenda-tasks/deployment/scripts +./backup-db.sh +``` + +Backups are stored in `/opt/agenda-tasks/deployment/backups/` and automatically cleaned up after 7 days. + +### Restore Database + +```bash +cd /opt/agenda-tasks/deployment/docker + +# Stop backend +docker compose stop backend + +# Restore from backup +gunzip < ../backups/agenda_tasks_YYYYMMDD_HHMMSS.sql.gz | \ + docker compose exec -T db psql -U agenda agenda_tasks + +# Start backend +docker compose start backend +``` + +### Update Application + +```bash +cd /opt/agenda-tasks + +# Pull latest code +git pull + +# Redeploy +cd deployment/scripts +./deploy.sh +``` + +### Restart Services + +```bash +cd /opt/agenda-tasks/deployment/docker + +# Restart all +docker compose restart + +# Restart specific service +docker compose restart backend +``` + +## Security Checklist + +- [ ] Strong database password (32+ characters) +- [ ] Strong secret key (32+ characters) +- [ ] SSL certificate installed +- [ ] Firewall configured (ports 80, 443 only) +- [ ] Regular backups enabled +- [ ] DEBUG=false in production + +### Configure Firewall + +```bash +# Allow SSH +ufw allow ssh + +# Allow HTTP/HTTPS +ufw allow 80/tcp +ufw allow 443/tcp + +# Enable firewall +ufw enable +``` + +## Flutter App Configuration + +When building the Flutter app for production, specify the API URL: + +```bash +# Android APK +flutter build apk --dart-define=API_URL=https://your-domain.com + +# iOS +flutter build ios --dart-define=API_URL=https://your-domain.com +``` + +## Troubleshooting + +### Backend Not Starting + +```bash +# Check logs +docker compose logs backend + +# Common issues: +# - Database connection failed: Check DB_PASSWORD +# - Import errors: Rebuild with --no-cache +docker compose build --no-cache backend +``` + +### SSL Certificate Failed + +```bash +# Check DNS +dig your-domain.com + +# DNS should point to your server IP +# Wait for propagation if recently changed +``` + +### Database Connection Issues + +```bash +# Check database is running +docker compose ps db + +# Check database logs +docker compose logs db + +# Test connection +docker compose exec db psql -U agenda -d agenda_tasks -c "SELECT 1" +``` + +## Support + +For issues or questions: +1. Check the logs first +2. Review this documentation +3. Contact the development team diff --git a/deployment/docker/Dockerfile.backend b/deployment/docker/Dockerfile.backend new file mode 100644 index 0000000..397f50c --- /dev/null +++ b/deployment/docker/Dockerfile.backend @@ -0,0 +1,32 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY backend/requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt gunicorn + +# Copy application code +COPY backend/app ./app + +# Create non-root user +RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app +USER appuser + +# Expose port +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/docs')" || exit 1 + +# Run with gunicorn +CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"] diff --git a/deployment/docker/docker-compose.yml b/deployment/docker/docker-compose.yml new file mode 100644 index 0000000..7a6354b --- /dev/null +++ b/deployment/docker/docker-compose.yml @@ -0,0 +1,72 @@ +version: '3.8' + +services: + db: + image: postgres:16-alpine + container_name: agenda_db + restart: unless-stopped + environment: + POSTGRES_USER: ${DB_USER:-agenda} + POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required} + POSTGRES_DB: ${DB_NAME:-agenda_tasks} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - backend_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-agenda} -d ${DB_NAME:-agenda_tasks}"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: + context: ../.. + dockerfile: deployment/docker/Dockerfile.backend + container_name: agenda_backend + restart: unless-stopped + environment: + DATABASE_URL: postgresql://${DB_USER:-agenda}:${DB_PASSWORD}@db:5432/${DB_NAME:-agenda_tasks} + SECRET_KEY: ${SECRET_KEY:?SECRET_KEY is required} + DEBUG: ${DEBUG:-false} + depends_on: + db: + condition: service_healthy + networks: + - backend_network + expose: + - "8000" + + nginx: + image: nginx:alpine + container_name: agenda_nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ../nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ../nginx/conf.d:/etc/nginx/conf.d:ro + - certbot_data:/etc/letsencrypt:ro + - certbot_www:/var/www/certbot:ro + depends_on: + - backend + networks: + - backend_network + + certbot: + image: certbot/certbot + container_name: agenda_certbot + volumes: + - certbot_data:/etc/letsencrypt + - certbot_www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + +volumes: + postgres_data: + certbot_data: + certbot_www: + +networks: + backend_network: + driver: bridge diff --git a/deployment/nginx/conf.d/default.conf b/deployment/nginx/conf.d/default.conf new file mode 100644 index 0000000..b19f468 --- /dev/null +++ b/deployment/nginx/conf.d/default.conf @@ -0,0 +1,84 @@ +# HTTP redirect to HTTPS +server { + listen 80; + listen [::]:80; + server_name _; + + # Let's Encrypt challenge location + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # Redirect all other traffic to HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS server +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name _; + + # SSL certificates (will be created by certbot) + ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.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; + + # HSTS + add_header Strict-Transport-Security "max-age=63072000" always; + + # 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; + + # Auth endpoints with rate limiting + location ~ ^/auth/(login|register)$ { + limit_req zone=auth_limit burst=10 nodelay; + + 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 ""; + } + + # API endpoints with rate limiting + location / { + limit_req zone=api_limit burst=20 nodelay; + + 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 ""; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Health check endpoint (no rate limiting) + location /health { + proxy_pass http://backend/docs; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } +} diff --git a/deployment/nginx/nginx.conf b/deployment/nginx/nginx.conf new file mode 100644 index 0000000..066a688 --- /dev/null +++ b/deployment/nginx/nginx.conf @@ -0,0 +1,47 @@ +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; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + 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/rss+xml application/atom+xml image/svg+xml; + + # Rate limiting zones + limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m; + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; + + # Upstream backend + upstream backend { + server backend:8000; + keepalive 32; + } + + # Include additional configs + include /etc/nginx/conf.d/*.conf; +} diff --git a/deployment/scripts/backup-db.sh b/deployment/scripts/backup-db.sh new file mode 100755 index 0000000..6a52529 --- /dev/null +++ b/deployment/scripts/backup-db.sh @@ -0,0 +1,58 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOYMENT_DIR="$(dirname "$SCRIPT_DIR")" +BACKUP_DIR="$DEPLOYMENT_DIR/backups" +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") + +# Load environment variables +if [ -f "$DEPLOYMENT_DIR/.env" ]; then + source "$DEPLOYMENT_DIR/.env" +fi + +# Default values +DB_USER="${DB_USER:-agenda}" +DB_NAME="${DB_NAME:-agenda_tasks}" + +# Create backup directory +mkdir -p "$BACKUP_DIR" + +echo -e "${GREEN}=== Database Backup ===${NC}" +echo -e "${YELLOW}Database: $DB_NAME${NC}" +echo -e "${YELLOW}User: $DB_USER${NC}" + +# Create backup +BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz" + +echo -e "${GREEN}Creating backup: $BACKUP_FILE${NC}" + +cd "$DEPLOYMENT_DIR/docker" + +docker compose exec -T db pg_dump -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE" + +if [ -f "$BACKUP_FILE" ]; then + BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) + echo -e "${GREEN}Backup created successfully!${NC}" + echo -e "${YELLOW}Size: $BACKUP_SIZE${NC}" +else + echo -e "${RED}Backup failed!${NC}" + exit 1 +fi + +# Clean up old backups (keep last 7 days) +echo -e "${GREEN}Cleaning up old backups...${NC}" +find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete + +# List current backups +echo -e "${GREEN}Current backups:${NC}" +ls -lh "$BACKUP_DIR"/*.sql.gz 2>/dev/null || echo "No backups found" + +echo -e "${GREEN}=== Backup complete! ===${NC}" diff --git a/deployment/scripts/deploy.sh b/deployment/scripts/deploy.sh new file mode 100755 index 0000000..b014585 --- /dev/null +++ b/deployment/scripts/deploy.sh @@ -0,0 +1,81 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOYMENT_DIR="$(dirname "$SCRIPT_DIR")" +PROJECT_DIR="$(dirname "$DEPLOYMENT_DIR")" + +echo -e "${GREEN}=== Agenda Tasks Deployment ===${NC}" + +# Check if .env file exists +if [ ! -f "$DEPLOYMENT_DIR/.env" ]; then + echo -e "${RED}Error: .env file not found!${NC}" + echo "Please copy .env.example to .env and configure it:" + echo " cp $DEPLOYMENT_DIR/.env.example $DEPLOYMENT_DIR/.env" + exit 1 +fi + +# Load environment variables +source "$DEPLOYMENT_DIR/.env" + +# Validate required variables +required_vars=("DB_PASSWORD" "SECRET_KEY" "DOMAIN" "EMAIL") +for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + echo -e "${RED}Error: $var is not set in .env${NC}" + exit 1 + fi +done + +echo -e "${YELLOW}Domain: $DOMAIN${NC}" +echo -e "${YELLOW}Email: $EMAIL${NC}" + +# Navigate to docker directory +cd "$DEPLOYMENT_DIR/docker" + +# Pull latest images +echo -e "${GREEN}Pulling latest images...${NC}" +docker compose pull + +# Build backend +echo -e "${GREEN}Building backend...${NC}" +docker compose build backend + +# Stop existing containers +echo -e "${GREEN}Stopping existing containers...${NC}" +docker compose down + +# Start services +echo -e "${GREEN}Starting services...${NC}" +docker compose up -d + +# Wait for services to be healthy +echo -e "${GREEN}Waiting for services to be healthy...${NC}" +sleep 10 + +# Check service status +echo -e "${GREEN}Checking service status...${NC}" +docker compose ps + +# Check backend health +if docker compose exec -T backend python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/docs')"; then + echo -e "${GREEN}Backend is healthy!${NC}" +else + echo -e "${RED}Backend health check failed!${NC}" + docker compose logs backend + exit 1 +fi + +echo -e "${GREEN}=== Deployment complete! ===${NC}" +echo "" +echo "Next steps:" +echo "1. Set up SSL with: ./setup-ssl.sh" +echo "2. Configure your DNS to point to this server" +echo "3. Access the API at: https://$DOMAIN" diff --git a/deployment/scripts/setup-ssl.sh b/deployment/scripts/setup-ssl.sh new file mode 100755 index 0000000..e4e9383 --- /dev/null +++ b/deployment/scripts/setup-ssl.sh @@ -0,0 +1,174 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOYMENT_DIR="$(dirname "$SCRIPT_DIR")" + +echo -e "${GREEN}=== SSL Certificate Setup ===${NC}" + +# Load environment variables +if [ ! -f "$DEPLOYMENT_DIR/.env" ]; then + echo -e "${RED}Error: .env file not found!${NC}" + exit 1 +fi + +source "$DEPLOYMENT_DIR/.env" + +# Validate required variables +if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then + echo -e "${RED}Error: DOMAIN and EMAIL must be set in .env${NC}" + exit 1 +fi + +echo -e "${YELLOW}Domain: $DOMAIN${NC}" +echo -e "${YELLOW}Email: $EMAIL${NC}" + +cd "$DEPLOYMENT_DIR/docker" + +# Create a temporary nginx config for initial certificate request +echo -e "${GREEN}Creating temporary nginx config for certificate request...${NC}" + +# Create temp config directory if it doesn't exist +mkdir -p "$DEPLOYMENT_DIR/nginx/conf.d" + +# Create temporary HTTP-only config +cat > "$DEPLOYMENT_DIR/nginx/conf.d/default.conf" << EOF +server { + listen 80; + listen [::]:80; + server_name $DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 200 'Waiting for SSL certificate...'; + add_header Content-Type text/plain; + } +} +EOF + +# Restart nginx with temporary config +echo -e "${GREEN}Restarting nginx with temporary config...${NC}" +docker compose up -d nginx + +# Wait for nginx to start +sleep 5 + +# Request certificate +echo -e "${GREEN}Requesting SSL certificate from Let's Encrypt...${NC}" + +docker compose run --rm certbot certonly \ + --webroot \ + --webroot-path=/var/www/certbot \ + --email "$EMAIL" \ + --agree-tos \ + --no-eff-email \ + -d "$DOMAIN" + +# Check if certificate was created +if docker compose run --rm certbot certificates | grep -q "$DOMAIN"; then + echo -e "${GREEN}Certificate obtained successfully!${NC}" +else + echo -e "${RED}Failed to obtain certificate!${NC}" + echo "Please check:" + echo "1. DNS is pointing to this server" + echo "2. Port 80 is accessible from the internet" + exit 1 +fi + +# Restore the full nginx config with SSL +echo -e "${GREEN}Restoring full nginx config with SSL...${NC}" + +cat > "$DEPLOYMENT_DIR/nginx/conf.d/default.conf" << EOF +# HTTP redirect to HTTPS +server { + listen 80; + listen [::]:80; + server_name $DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://\$host\$request_uri; + } +} + +# HTTPS server +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name $DOMAIN; + + ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; + + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + + 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; + + add_header Strict-Transport-Security "max-age=63072000" always; + 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; + + location ~ ^/auth/(login|register)\$ { + limit_req zone=auth_limit burst=10 nodelay; + + 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 ""; + } + + location / { + limit_req zone=api_limit burst=20 nodelay; + + 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 ""; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + location /health { + proxy_pass http://backend/docs; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } +} +EOF + +# Reload nginx +echo -e "${GREEN}Reloading nginx with SSL config...${NC}" +docker compose exec nginx nginx -s reload + +echo -e "${GREEN}=== SSL setup complete! ===${NC}" +echo "" +echo "Your API is now available at: https://$DOMAIN" +echo "" +echo "Certificate will auto-renew via the certbot container."