# MVNexus Deployment Guide

## Prerequisites

- Docker 24+ and Docker Compose v2
- MySQL 8.4+
- Redis 7+
- Node 20+ (build assets in CI or locally before image build)
- PHP 8.3+ (if deploying without Docker)

## Production build checklist

1. Copy `docs/PRODUCTION_ENV_TEMPLATE.md` values into `.env`
2. Set `APP_ENV=production`, `APP_DEBUG=false`
3. Run `composer install --no-dev --optimize-autoloader`
4. Run `npm ci && npm run build`
5. Run `php artisan migrate --force`
6. Cache framework metadata:

```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
```

## Docker deployment

```bash
cp docs/PRODUCTION_ENV_TEMPLATE.md .env
# edit secrets and hostnames

docker compose -f docker-compose.production.yml build
docker compose -f docker-compose.production.yml up -d

docker compose -f docker-compose.production.yml exec app php artisan migrate --force
docker compose -f docker-compose.production.yml exec app php artisan config:cache
```

### Services

| Service | Role |
|---------|------|
| `app` | PHP-FPM application |
| `nginx` | Web server + websocket proxy to Reverb |
| `mysql` | Primary database |
| `redis` | Cache, sessions, queues |
| `queue` | Queue workers |
| `scheduler` | `schedule:work` |
| `reverb` | WebSocket server |

## Health checks

| Endpoint | Purpose |
|----------|---------|
| `GET /up` | Laravel default liveness |
| `GET /health/app` | App metadata |
| `GET /health/database` | DB connectivity |
| `GET /health/queue` | Queue backend |
| `GET /health/reverb` | Broadcast configuration |

## Reverse proxy

- Terminate TLS at nginx/ingress
- Proxy `/app` to Reverb for websockets
- Set trusted proxies (`TRUSTED_PROXIES` / load balancer IPs)

## SPA authentication (Sanctum cookies + CORS)

The React SPA uses **stateful Sanctum sessions** (httpOnly cookies), not API bearer tokens in the browser. Configure these in production `.env`:

| Variable | Purpose |
|----------|---------|
| `CORS_ALLOWED_ORIGINS` | Comma-separated **full** frontend URLs with scheme (e.g. `https://helpdesk.example.com`). No `*` when using credentials. |
| `SANCTUM_STATEFUL_DOMAINS` | Comma-separated **hostnames only** (no scheme/port unless non-default), e.g. `helpdesk.example.com` |
| `SESSION_DRIVER` | Use `redis` in production (same Redis as cache/queue) |
| `SESSION_DOMAIN` | Cookie domain, often `.example.com` for subdomains |
| `SESSION_SECURE_COOKIE` | `true` behind HTTPS |
| `SESSION_HTTP_ONLY` | `true` |
| `SESSION_SAME_SITE` | `lax` or `strict` |
| `SESSION_LIFETIME` | Minutes (default `120`) |

**Mailgun (auth email):** `MAILGUN_DOMAIN`, `MAILGUN_SECRET`, `MAILGUN_ENDPOINT` — required for OTP, magic links, and invite emails.

**OTP / invite tuning:** `OTP_ALLOWED_EMAIL_DOMAIN`, `OTP_*` rate limits, `MAGIC_LINK_EXPIRES_MINUTES`, `USER_INVITE_EXPIRES_DAYS` (see `.env.example`).

**Checklist:**
- Frontend `VITE_*` API base URL matches a host listed in `CORS_ALLOWED_ORIGINS`
- `APP_URL` matches the API origin users hit for `/sanctum/csrf-cookie`
- `SANCTUM_STATEFUL_DOMAINS` includes every hostname users use in the browser bar
- Do **not** configure Microsoft Entra / Azure OAuth for current releases (`sso_identities` is legacy, unused)

## Post-deploy verification

```bash
curl -fsS https://your-host/health/app
curl -fsS https://your-host/up
```

Run smoke tests from `tests/Feature/API` and `tests/Feature/Identity` (OTP/magic link). Manually verify: request OTP → email received → verify → `GET /api/v1/auth/me` returns user.
