263 lines
8.7 KiB
Markdown
263 lines
8.7 KiB
Markdown
# Deployment Guide
|
|
|
|
This document covers production deployment for the `omo-bot` Coolify project with **two separate resources**:
|
|
|
|
- `omo-bot-backend` (Node.js bot + Admin API)
|
|
- `omo-bot-dashboard` (React/Vite static dashboard from `admin-dashboard/`)
|
|
|
|
## Deployment Topology
|
|
|
|
Recommended domains:
|
|
|
|
- `api.openmicodyssey.com` -> `omo-bot-backend`
|
|
- `admin.openmicodyssey.com` -> `omo-bot-dashboard`
|
|
|
|
Recommended edge topology:
|
|
|
|
- Internet -> Nginx reverse proxy -> Coolify resources
|
|
- Nginx routes by hostname to backend/dashboard resource domains or internal ports
|
|
|
|
Optional same-origin pattern:
|
|
|
|
- `admin.openmicodyssey.com` serves dashboard and reverse-proxies `/admin/*` + `/health` to backend.
|
|
|
|
## 1. Create Coolify Project and Resources
|
|
|
|
1. In Coolify, create (or open) project `omo-bot`.
|
|
2. Add resource `omo-bot-backend` from this repository:
|
|
- Build context: repository root
|
|
- Start command: `npm start`
|
|
- Internal port: `8787` (Admin API)
|
|
3. Add resource `omo-bot-dashboard` from this repository:
|
|
- Base directory: `admin-dashboard`
|
|
- Build command: `npm run build`
|
|
- Publish directory: `/`
|
|
- Internal port: `80` (typical static web port behind Coolify)
|
|
4. Configure domains for each resource and enable TLS certificates.
|
|
|
|
## 1.1 Nginx Reverse Proxy in Front of Coolify
|
|
|
|
Use this pattern when you want one public edge server in front of Coolify-managed services.
|
|
|
|
Recommended flow:
|
|
|
|
1. Public DNS for `api.openmicodyssey.com` and `admin.openmicodyssey.com` points to Nginx host.
|
|
2. Nginx terminates TLS and forwards traffic to Coolify resource domains or private IP:port targets.
|
|
3. Coolify resources stay private or restricted to internal network where possible.
|
|
|
|
Example host-based routing:
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name api.openmicodyssey.com;
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name api.openmicodyssey.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/api.openmicodyssey.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/api.openmicodyssey.com/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://coolify-backend.internal:8787;
|
|
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 https;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name admin.openmicodyssey.com;
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name admin.openmicodyssey.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/admin.openmicodyssey.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/admin.openmicodyssey.com/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://coolify-dashboard.internal:80;
|
|
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 https;
|
|
}
|
|
}
|
|
```
|
|
|
|
If you prefer same-origin routing for dashboard + API through one public domain, proxy `location /admin/` and `location /health` to backend while keeping `/` on dashboard.
|
|
|
|
Recommended OAuth callback route:
|
|
|
|
- `https://admin.openmicodyssey.com/oauth/discord/callback`
|
|
|
|
If you use this callback path, make sure dashboard hosting serves the SPA entrypoint for that route instead of returning `404`.
|
|
|
|
## 2. Configure Runtime Variables
|
|
|
|
Backend (`omo-bot-backend`) minimum:
|
|
|
|
- `DATABASE_URL`
|
|
- `CONFIG_DB_ENABLED=true`
|
|
- Any temporary legacy vars needed for first-time `db:seed`
|
|
|
|
Dashboard (`omo-bot-dashboard`) minimum:
|
|
|
|
- `VITE_ADMIN_API_BASE_URL` (for split-domain deployment, set to `https://api.openmicodyssey.com`)
|
|
- `VITE_DISCORD_CLIENT_ID`
|
|
- `VITE_DISCORD_REDIRECT_URI` (recommended: `https://admin.openmicodyssey.com/oauth/discord/callback`)
|
|
|
|
OAuth alignment:
|
|
|
|
- Discord Developer Portal redirect URI must exactly match the dashboard callback URL.
|
|
- Config DB key `OAUTH_BRIDGE_REDIRECT_URI` must match the same callback URL.
|
|
- Dashboard env `VITE_DISCORD_REDIRECT_URI` must match the same callback URL.
|
|
|
|
Discord OAuth authorize URL shape:
|
|
|
|
```text
|
|
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fadmin.openmicodyssey.com%2Foauth%2Fdiscord%2Fcallback&scope=identify%20guilds&prompt=consent
|
|
```
|
|
|
|
## 3. Get Coolify API Token and Resource UUIDs
|
|
|
|
1. In Coolify, open `Keys & Tokens` -> `API Tokens` and create a token.
|
|
2. Copy your Coolify base URL (example: `https://coolify.yourdomain.com`).
|
|
3. Find resource UUID for:
|
|
|
|
- `omo-bot-backend`
|
|
- `omo-bot-dashboard`
|
|
|
|
Deploy API endpoint used by CI:
|
|
|
|
- `GET /api/v1/deploy?uuid=<resource_uuid>`
|
|
- Authorization: `Bearer <COOLIFY_API_TOKEN>`
|
|
|
|
### 3.1 Token Requirements (Important)
|
|
|
|
If CI fails with `Coolify API token rejected during preflight (HTTP 403)`, token exists but lacks access to API target.
|
|
|
|
Use this checklist:
|
|
|
|
1. Create token in same Coolify instance that hosts `omo-bot-backend` and `omo-bot-dashboard` resources.
|
|
2. Ensure token owner has access to project/team containing those resources.
|
|
3. Ensure token is API token (not UI session token/cookie).
|
|
4. Set `COOLIFY_BASE_URL` to Coolify root URL only (example: `https://coolify.yourdomain.com`), no extra path like `/project/...`.
|
|
5. Rotate token if unsure, then update Gitea secret `COOLIFY_API_TOKEN`.
|
|
|
|
Required permissions to grant (minimum):
|
|
|
|
- Project/resource read access for both deploy targets (`omo-bot-backend`, `omo-bot-dashboard`)
|
|
- Deploy/trigger permission on both resources
|
|
- Team/project membership that includes those resources (not observer/read-only)
|
|
|
|
If your Coolify token form shows these permissions:
|
|
|
|
- root
|
|
- write
|
|
- deploy
|
|
- read
|
|
- read:sensitive
|
|
|
|
Use this selection for CI deploy token:
|
|
|
|
- Required: read + deploy
|
|
- Optional: write (only if your instance still blocks deploy with read+deploy)
|
|
- Avoid for CI: root
|
|
- Optional and sensitive: read:sensitive (only needed when debugging APIs that must return secrets/log internals)
|
|
|
|
Tip: Start with read + deploy. If preflight still returns 403, temporarily add write to verify scope mismatch, then reduce to least-privilege that still works.
|
|
|
|
Preflight check from local shell (should NOT return `401` or `403`):
|
|
|
|
```bash
|
|
curl -i \
|
|
-H "Authorization: Bearer $COOLIFY_API_TOKEN" \
|
|
"$COOLIFY_BASE_URL/api/v1/deploy"
|
|
```
|
|
|
|
Resource trigger checks (match CI fallback logic):
|
|
|
|
```bash
|
|
# Variant 1: GET + Bearer
|
|
curl -i -G \
|
|
-H "Authorization: Bearer $COOLIFY_API_TOKEN" \
|
|
--data-urlencode "uuid=$COOLIFY_RESOURCE_UUID" \
|
|
"$COOLIFY_BASE_URL/api/v1/deploy"
|
|
|
|
# Variant 2: POST JSON + Bearer
|
|
curl -i -X POST \
|
|
-H "Authorization: Bearer $COOLIFY_API_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"uuid\":\"$COOLIFY_RESOURCE_UUID\"}" \
|
|
"$COOLIFY_BASE_URL/api/v1/deploy"
|
|
|
|
# Variant 3: GET + raw Authorization token
|
|
curl -i -G \
|
|
-H "Authorization: $COOLIFY_API_TOKEN" \
|
|
--data-urlencode "uuid=$COOLIFY_RESOURCE_UUID" \
|
|
"$COOLIFY_BASE_URL/api/v1/deploy"
|
|
```
|
|
|
|
If all variants return `403`, issue is permissions/scope in Coolify, not Gitea secret injection.
|
|
|
|
## 4. Configure Gitea Action Secrets/Variables
|
|
|
|
In Gitea repository settings, add Actions secrets:
|
|
|
|
- `COOLIFY_BASE_URL`
|
|
- `COOLIFY_API_TOKEN`
|
|
- `COOLIFY_RESOURCE_UUID_BOT`
|
|
- `COOLIFY_RESOURCE_UUID_DASHBOARD`
|
|
|
|
Current workflow in `.gitea/workflows/ci-cd.yml` triggers backend and dashboard deploys separately via the Coolify Deploy API.
|
|
|
|
## 5. Domain and DNS Checklist
|
|
|
|
1. Create DNS records to Nginx edge host (or directly to Coolify if no edge proxy):
|
|
|
|
- `A` record for root/subdomain targets
|
|
- `CNAME` where appropriate
|
|
|
|
2. Verify certificates issued on Nginx or Coolify, depending on TLS termination point.
|
|
3. Verify Nginx upstreams match deployed ports:
|
|
|
|
- backend -> `8787`
|
|
- dashboard -> `80`
|
|
|
|
4. Verify OAuth callback route resolves through dashboard hosting:
|
|
|
|
- `https://admin.openmicodyssey.com/oauth/discord/callback`
|
|
|
|
5. Verify dashboard can call API at configured `VITE_ADMIN_API_BASE_URL`.
|
|
6. Verify API health endpoint over TLS:
|
|
- `GET https://api.openmicodyssey.com/health`
|
|
|
|
## 6. Post-Deploy Verification
|
|
|
|
1. `npm run register:commands` (if bot app/guild IDs changed).
|
|
2. Confirm bot online in Discord and slash commands visible.
|
|
3. Open dashboard URL and run OAuth login flow.
|
|
4. Confirm Discord redirects back to `https://admin.openmicodyssey.com/oauth/discord/callback` without `404`.
|
|
5. Validate Admin API auth behavior with and without bearer token.
|
|
|
|
## 7. Coolify 403 Troubleshooting
|
|
|
|
When CI fails in `Validate Coolify API access` with HTTP `403`:
|
|
|
|
1. Confirm token belongs to correct Coolify instance and team/project.
|
|
2. Confirm resource UUIDs are from same instance and still valid.
|
|
3. Re-run local preflight curl from section 3.1.
|
|
4. Recreate token and update Gitea secret.
|
|
5. Re-run pipeline.
|
|
|
|
Current CI workflow already prints sanitized response snippets for deploy failures and tries auth/method fallback variants in `.gitea/workflows/ci-cd.yml`.
|