652 lines
18 KiB
Markdown
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
|