# MOPC Platform - Server Deployment Guide

Deployment guide for the MOPC platform on a Linux VPS with Docker.

**Domain**: `portal.monaco-opc.com`
**App Port**: 7600 (behind Nginx reverse proxy)
**CI/CD**: Gitea Actions (Ubuntu runner) builds and pushes Docker images

## CI/CD Pipeline

The app is built automatically by a Gitea runner on every push to `main`:

1. Gitea Actions workflow builds the Docker image on Ubuntu
2. Image is pushed to the Gitea container registry
3. On the server, `docker compose up -d` refreshes the image and restarts the app

### Gitea Setup

Configure the following in your Gitea repository settings:

**Repository Variables** (Settings > Actions > Variables):

| Variable | Value |
|----------|-------|
| `REGISTRY_URL` | Your Gitea registry URL (e.g. `gitea.example.com/your-org`) |

**Repository Secrets** (Settings > Actions > Secrets):

| Secret | Value |
|--------|-------|
| `REGISTRY_USER` | Gitea username with registry access |
| `REGISTRY_PASSWORD` | Gitea access token or password |

The workflow file is at `.gitea/workflows/build.yml`.

## Prerequisites

- Linux VPS (Ubuntu 22.04+ recommended)
- Docker Engine 24+ with Compose v2
- Nginx installed on the host
- Certbot for SSL certificates

### Install Docker (if needed)

```bash
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in
```

### Install Nginx & Certbot (if needed)

```bash
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx
```

## First-Time Deployment

### 1. Clone the repository

```bash
git clone <your-repo-url> /opt/mopc
cd /opt/mopc
```

### 2. Configure environment variables

```bash
cp docker/.env.production docker/.env
nano docker/.env
```

Fill in all `CHANGE_ME` values. Generate secrets with:

```bash
openssl rand -base64 32
```

Required variables:

| Variable | Description |
|----------|-------------|
| `REGISTRY_URL` | Gitea registry URL (e.g. `gitea.example.com/your-org`) |
| `DB_PASSWORD` | PostgreSQL password |
| `NEXTAUTH_SECRET` | Auth session secret (openssl rand) |
| `NEXTAUTH_URL` | `https://portal.monaco-opc.com` |
| `MINIO_ENDPOINT` | MinIO internal URL (e.g. `http://localhost:9000`) |
| `MINIO_ACCESS_KEY` | MinIO access key |
| `MINIO_SECRET_KEY` | MinIO secret key |
| `MINIO_BUCKET` | MinIO bucket name (`mopc-files`) |
| `SMTP_HOST` | SMTP server host |
| `SMTP_PORT` | SMTP port (587) |
| `SMTP_USER` | SMTP username |
| `SMTP_PASS` | SMTP password |
| `EMAIL_FROM` | Sender address |

### 3. Run the deploy script

```bash
chmod +x scripts/deploy.sh scripts/seed.sh scripts/update.sh
./scripts/deploy.sh
```

This will:
- Log in to the container registry
- Pull the latest app image
- Start PostgreSQL + the app
- Run database migrations automatically on startup
- Wait for the health check

### 4. Seed the database (one time only)

```bash
./scripts/seed.sh
```

This seeds:
- Super admin user (`matt.ciaccio@gmail.com`)
- System settings
- Program & Round 1 configuration
- Evaluation form
- All candidature data from CSV

### 5. Set up Nginx

```bash
sudo ln -s /opt/mopc/docker/nginx/mopc-platform.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```

### 6. Set up SSL

```bash
sudo certbot --nginx -d portal.monaco-opc.com
```

Auto-renewal is configured by default. Test with:

```bash
sudo certbot renew --dry-run
```

### 7. Verify

```bash
curl https://portal.monaco-opc.com/api/health
```

Expected response:

```json
{"status":"healthy","timestamp":"...","services":{"database":"connected"}}
```

## Updating the Platform

After Gitea CI builds a new image (push to `main`):

```bash
cd /opt/mopc
./scripts/update.sh
```

This pulls the latest image from the registry, restarts only the app container (PostgreSQL stays running), runs migrations via the entrypoint, and waits for the health check.

Manual equivalent:

```bash
cd /opt/mopc/docker
docker compose up -d --pull always --force-recreate app
```

`prisma migrate deploy` runs automatically in the container entrypoint before the app starts.

## Manual Operations

### View logs

```bash
cd /opt/mopc/docker
docker compose logs -f app       # App logs
docker compose logs -f postgres   # Database logs
```

### Run migrations manually

```bash
cd /opt/mopc/docker
docker compose exec app npx prisma migrate deploy
```

### Open a shell in the app container

```bash
cd /opt/mopc/docker
docker compose exec app sh
```

### Restart services

```bash
cd /opt/mopc/docker
docker compose restart app       # App only
docker compose restart           # All services
```

### Stop everything

```bash
cd /opt/mopc/docker
docker compose down              # Stop containers (data preserved)
docker compose down -v           # Stop AND delete volumes (data lost!)
```

## Database Backups

### Create a backup

```bash
docker exec mopc-postgres pg_dump -U mopc mopc | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
```

### Restore a backup

```bash
gunzip < backup_20260130_020000.sql.gz | docker exec -i mopc-postgres psql -U mopc mopc
```

### Set up daily backups (cron)

```bash
sudo mkdir -p /data/backups/mopc

cat > /opt/mopc/scripts/backup-db.sh << 'SCRIPT'
#!/bin/bash
BACKUP_DIR=/data/backups/mopc
DATE=$(date +%Y%m%d_%H%M%S)
docker exec mopc-postgres pg_dump -U mopc mopc | gzip > $BACKUP_DIR/mopc_$DATE.sql.gz
find $BACKUP_DIR -name "mopc_*.sql.gz" -mtime +30 -delete
SCRIPT

chmod +x /opt/mopc/scripts/backup-db.sh
echo "0 2 * * * /opt/mopc/scripts/backup-db.sh >> /var/log/mopc-backup.log 2>&1" | sudo tee /etc/cron.d/mopc-backup
```

## Architecture

```
Gitea CI (Ubuntu runner)
  |
  v (docker push)
Container Registry
  |
  v (docker pull)
Linux VPS
  |
  v
Nginx (host, port 443) -- SSL termination
  |
  v
mopc-app (Docker, port 7600) -- Next.js standalone
  |
  v
mopc-postgres (Docker, port 5432) -- PostgreSQL 16

External services (separate Docker stacks):
  - MinIO (port 9000) -- S3-compatible file storage
  - Poste.io (port 587) -- SMTP email
```

## Troubleshooting

### App won't start

```bash
cd /opt/mopc/docker
docker compose logs app
docker compose exec postgres pg_isready -U mopc
```

### Can't pull image

```bash
# Re-authenticate with registry
docker login <your-registry-url>

# Check image exists
docker pull <your-registry-url>/mopc-app:latest
```

### Migration fails

```bash
# Check migration status
docker compose exec app npx prisma migrate status

# Reset (DESTROYS DATA):
docker compose exec app npx prisma migrate reset
```

### SSL certificate issues

```bash
sudo certbot certificates
sudo certbot renew --force-renewal
```

### Port conflict

The app runs on port 7600. If something else uses it:

```bash
sudo ss -tlnp | grep 7600
```

## Security Checklist

- [ ] SSL certificate active and auto-renewing
- [ ] `docker/.env` has strong, unique passwords
- [ ] `NEXTAUTH_SECRET` is randomly generated
- [ ] Gitea registry credentials secured
- [ ] Firewall allows only ports 80, 443, 22
- [ ] Docker daemon not exposed to network
- [ ] Daily backups configured
- [ ] Nginx security headers active
