MOPC-App/docs/architecture/infrastructure.md

652 lines
18 KiB
Markdown

# MOPC Platform - Infrastructure
## Overview
The MOPC platform is self-hosted on a Linux VPS at **monaco-opc.com** with the following architecture:
- **Nginx** (host-level) - Reverse proxy with SSL termination
- **Docker Compose** (MOPC stack) - Next.js + PostgreSQL
- **MinIO** (separate stack) - S3-compatible file storage
- **Poste.io** (separate stack) - Self-hosted email server
**Key Configurations:**
- Max file size: 500MB (for video uploads)
- SSL via Certbot (Let's Encrypt)
## Architecture Diagram
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ INTERNET │
└─────────────────────────────────┬────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ LINUX VPS │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ NGINX (Host Level) │ │
│ │ │ │
│ │ - SSL termination via Certbot │ │
│ │ - Reverse proxy to Docker services │ │
│ │ - Rate limiting │ │
│ │ - Security headers │ │
│ │ │ │
│ │ Ports: 80 (HTTP → HTTPS redirect), 443 (HTTPS) │ │
│ └─────────────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ MOPC Stack │ │ MinIO Stack │ │ Poste.io Stack │ │
│ │ (Docker Compose)│ │ (Docker Compose)│ │ (Docker Compose)│ │
│ │ │ │ │ │ │ │
│ │ ┌────────────┐ │ │ ┌────────────┐ │ │ ┌────────────┐ │ │
│ │ │ Next.js │ │ │ │ MinIO │ │ │ │ Poste.io │ │ │
│ │ │ :3000 │ │ │ │ :9000 │ │ │ │ :25,587 │ │ │
│ │ └────────────┘ │ │ │ :9001 │ │ │ └────────────┘ │ │
│ │ │ │ └────────────┘ │ │ │ │
│ │ ┌────────────┐ │ │ │ │ │ │
│ │ │ PostgreSQL │ │ │ │ │ │ │
│ │ │ :5432 │ │ │ │ │ │ │
│ │ └────────────┘ │ │ │ │ │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
│ Data Volumes: │
│ /data/mopc/postgres /data/minio /data/poste │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
```
## Docker Compose Configuration
### MOPC Stack
```yaml
# docker/docker-compose.yml
version: '3.8'
services:
app:
build:
context: ..
dockerfile: docker/Dockerfile
container_name: mopc-app
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://mopc:${DB_PASSWORD}@postgres:5432/mopc
- NEXTAUTH_URL=${NEXTAUTH_URL}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- MINIO_ENDPOINT=${MINIO_ENDPOINT}
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- MINIO_BUCKET=${MINIO_BUCKET}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- EMAIL_FROM=${EMAIL_FROM}
depends_on:
postgres:
condition: service_healthy
networks:
- mopc-network
postgres:
image: postgres:16-alpine
container_name: mopc-postgres
restart: unless-stopped
environment:
- POSTGRES_USER=mopc
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=mopc
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mopc"]
interval: 10s
timeout: 5s
retries: 5
networks:
- mopc-network
volumes:
postgres_data:
driver: local
driver_opts:
type: none
o: bind
device: /data/mopc/postgres
networks:
mopc-network:
driver: bridge
```
### Development Stack
The development stack includes PostgreSQL, MinIO, and the Next.js app running in Docker containers.
```yaml
# docker/docker-compose.dev.yml
services:
postgres:
image: postgres:16-alpine
container_name: mopc-postgres-dev
ports:
- "5432:5432"
environment:
- POSTGRES_USER=mopc
- POSTGRES_PASSWORD=devpassword
- POSTGRES_DB=mopc
volumes:
- postgres_dev_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mopc"]
interval: 5s
timeout: 5s
retries: 5
minio:
image: minio/minio
container_name: mopc-minio-dev
ports:
- "9000:9000"
- "9001:9001"
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin
volumes:
- minio_dev_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
# MinIO client to create default bucket on startup
createbuckets:
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set myminio http://minio:9000 minioadmin minioadmin;
mc mb --ignore-existing myminio/mopc-files;
mc anonymous set download myminio/mopc-files;
echo 'Bucket created successfully';
"
# Next.js application
app:
build:
context: ..
dockerfile: docker/Dockerfile.dev
container_name: mopc-app-dev
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://mopc:devpassword@postgres:5432/mopc
- NEXTAUTH_URL=http://localhost:3000
- NEXTAUTH_SECRET=dev-secret-key-for-local-development-only
- MINIO_ENDPOINT=http://minio:9000
- MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin
- MINIO_BUCKET=mopc-files
- NODE_ENV=development
volumes:
- ../src:/app/src
- ../public:/app/public
- ../prisma:/app/prisma
depends_on:
postgres:
condition: service_healthy
minio:
condition: service_healthy
volumes:
postgres_dev_data:
minio_dev_data:
```
### Quick Start (Development)
```bash
# 1. Start all services (PostgreSQL, MinIO, Next.js)
docker compose -f docker/docker-compose.dev.yml up --build -d
# 2. Push database schema
docker exec mopc-app-dev npx prisma db push
# 3. Seed test data
docker exec mopc-app-dev npx tsx prisma/seed.ts
# 4. Open http://localhost:3000
# Login with: admin@monaco-opc.com (magic link)
```
### Development URLs
| Service | URL | Credentials |
|---------|-----|-------------|
| Next.js App | http://localhost:3000 | See seed data |
| MinIO Console | http://localhost:9001 | minioadmin / minioadmin |
| PostgreSQL | localhost:5432 | mopc / devpassword |
### Test Accounts (after seeding)
| Role | Email |
|------|-------|
| Super Admin | admin@monaco-opc.com |
| Jury Member | jury1@example.com |
| Jury Member | jury2@example.com |
| Jury Member | jury3@example.com |
## Dockerfile
```dockerfile
# docker/Dockerfile
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Generate Prisma client
RUN npx prisma generate
# Build Next.js
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/prisma ./prisma
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
```
## Nginx Configuration
```nginx
# /etc/nginx/sites-available/mopc-platform
# Rate limiting zone
limit_req_zone $binary_remote_addr zone=mopc_limit:10m rate=10r/s;
# MOPC Platform
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name monaco-opc.com;
# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/monaco-opc.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/monaco-opc.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# 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 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';" always;
# File upload size (500MB for videos)
client_max_body_size 500M;
# Rate limiting
limit_req zone=mopc_limit burst=20 nodelay;
# Logging
access_log /var/log/nginx/mopc-access.log;
error_log /var/log/nginx/mopc-error.log;
# Next.js application
location / {
proxy_pass http://127.0.0.1:3000;
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;
proxy_cache_bypass $http_upgrade;
# Timeouts for large file uploads
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# Static files caching
location /_next/static {
proxy_pass http://127.0.0.1:3000;
proxy_cache_valid 200 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Health check endpoint
location /api/health {
proxy_pass http://127.0.0.1:3000;
access_log off;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name monaco-opc.com;
return 301 https://$host$request_uri;
}
```
## SSL Setup with Certbot
```bash
# Install Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d monaco-opc.com
# Auto-renewal is configured automatically
# Test renewal
sudo certbot renew --dry-run
```
## Environment Variables
### Production (.env)
```env
# Application
NODE_ENV=production
NEXTAUTH_URL=https://monaco-opc.com
NEXTAUTH_SECRET=generate-a-secure-random-string-here
# Database
DB_PASSWORD=your-secure-database-password
DATABASE_URL=postgresql://mopc:${DB_PASSWORD}@postgres:5432/mopc
# MinIO (external stack)
MINIO_ENDPOINT=http://localhost:9000
MINIO_ACCESS_KEY=your-minio-access-key
MINIO_SECRET_KEY=your-minio-secret-key
MINIO_BUCKET=mopc-files
# Email (Poste.io)
SMTP_HOST=localhost
SMTP_PORT=587
SMTP_USER=noreply@monaco-opc.com
SMTP_PASS=your-smtp-password
EMAIL_FROM=MOPC Platform <noreply@monaco-opc.com>
```
### Generate Secrets
```bash
# Generate NEXTAUTH_SECRET
openssl rand -base64 32
# Generate DB_PASSWORD
openssl rand -base64 24
```
## Deployment Commands
### Initial Deployment
```bash
# 1. Clone repository
git clone https://github.com/your-org/mopc-platform.git /opt/mopc
cd /opt/mopc
# 2. Create environment file
cp .env.example .env
nano .env # Edit with production values
# 3. Create data directories
sudo mkdir -p /data/mopc/postgres
sudo chown -R 1000:1000 /data/mopc
# 4. Start the stack
cd docker
docker compose up -d
# 5. Run database migrations
docker compose exec app npx prisma migrate deploy
# 6. Seed initial data (optional)
docker compose exec app npx prisma db seed
# 7. Enable Nginx site
sudo ln -s /etc/nginx/sites-available/mopc-platform /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# 8. Set up SSL
sudo certbot --nginx -d monaco-opc.com
```
### Updates
```bash
cd /opt/mopc
# 1. Pull latest code
git pull origin main
# 2. Rebuild and restart
cd docker
docker compose build app
docker compose up -d app
# 3. Run any new migrations
docker compose exec app npx prisma migrate deploy
```
### Rollback
```bash
# Revert to previous image
docker compose down
git checkout HEAD~1
docker compose build app
docker compose up -d
# Or restore from specific tag
git checkout v1.0.0
docker compose build app
docker compose up -d
```
## Backup Strategy
### Database Backups
```bash
# Create backup script
cat > /opt/mopc/scripts/backup-db.sh << 'EOF'
#!/bin/bash
BACKUP_DIR=/data/backups/mopc
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE=$BACKUP_DIR/mopc_$DATE.sql.gz
mkdir -p $BACKUP_DIR
docker exec mopc-postgres pg_dump -U mopc mopc | gzip > $BACKUP_FILE
# Keep last 30 days
find $BACKUP_DIR -name "mopc_*.sql.gz" -mtime +30 -delete
echo "Backup completed: $BACKUP_FILE"
EOF
chmod +x /opt/mopc/scripts/backup-db.sh
# Add to crontab (daily at 2 AM)
echo "0 2 * * * /opt/mopc/scripts/backup-db.sh >> /var/log/mopc-backup.log 2>&1" | sudo tee -a /etc/cron.d/mopc-backup
```
### Restore Database
```bash
# Restore from backup
gunzip < /data/backups/mopc/mopc_20260115_020000.sql.gz | docker exec -i mopc-postgres psql -U mopc mopc
```
## Monitoring
### Health Check Endpoint
```typescript
// src/app/api/health/route.ts
import { prisma } from '@/lib/prisma'
export async function GET() {
try {
// Check database connection
await prisma.$queryRaw`SELECT 1`
return Response.json({
status: 'healthy',
timestamp: new Date().toISOString(),
})
} catch (error) {
return Response.json(
{
status: 'unhealthy',
error: 'Database connection failed',
},
{ status: 503 }
)
}
}
```
### Log Viewing
```bash
# Application logs
docker compose logs -f app
# Nginx access logs
tail -f /var/log/nginx/mopc-access.log
# Nginx error logs
tail -f /var/log/nginx/mopc-error.log
# PostgreSQL logs
docker compose logs -f postgres
```
### Resource Monitoring
```bash
# Docker stats
docker stats mopc-app mopc-postgres
# System resources
htop
```
## Security Checklist
- [ ] SSL certificate active and auto-renewing
- [ ] Database password is strong and unique
- [ ] NEXTAUTH_SECRET is randomly generated
- [ ] MinIO credentials are secure
- [ ] SMTP credentials are secure
- [ ] Firewall allows only ports 80, 443, 22
- [ ] Docker daemon not exposed to network
- [ ] Regular backups configured
- [ ] Log rotation configured
- [ ] Security headers enabled in Nginx
## Troubleshooting
### Application Won't Start
```bash
# Check logs
docker compose logs app
# Check if database is ready
docker compose exec postgres pg_isready -U mopc
# Restart stack
docker compose restart
```
### Database Connection Issues
```bash
# Test connection from app container
docker compose exec app sh
nc -zv postgres 5432
# Check PostgreSQL logs
docker compose logs postgres
```
### SSL Certificate Issues
```bash
# Test certificate
sudo certbot certificates
# Force renewal
sudo certbot renew --force-renewal
# Check Nginx configuration
sudo nginx -t
```
## Related Documentation
- [Database Design](./database.md) - Schema and migrations
- [API Design](./api.md) - tRPC endpoints