From cf564f2886cc3b245dd2a7b4804e3f1cd3878bbd Mon Sep 17 00:00:00 2001 From: zwitschi Date: Sun, 17 May 2026 19:08:26 +0200 Subject: [PATCH] feat: Update CI/CD workflow to trigger separate backend and dashboard deployments; enhance deployment documentation with Nginx proxy details and OAuth callback URLs --- .gitea/workflows/ci-cd.yml | 41 ++++++--- DEPLOYMENT.md | 166 ++++++++++++++++++++++------------- README.md | 11 +++ admin-dashboard/.env.example | 5 +- docs/07_deployment_view.md | 62 ++++++++----- 5 files changed, 188 insertions(+), 97 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index ca548fd..85894d6 100644 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -66,23 +66,44 @@ jobs: - dashboard-checks if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} steps: - - name: Trigger Coolify deploy hook + - name: Trigger backend deploy env: - COOLIFY_DEPLOY_HOOK_URL: ${{ secrets.COOLIFY_DEPLOY_HOOK_URL }} - COOLIFY_DEPLOY_TOKEN: ${{ secrets.COOLIFY_DEPLOY_TOKEN }} + HOOK_URL: ${{ secrets.COOLIFY_DEPLOY_HOOK_URL_BOT }} + HOOK_TOKEN: ${{ secrets.COOLIFY_DEPLOY_TOKEN_BOT }} run: | - if [ -z "$COOLIFY_DEPLOY_HOOK_URL" ]; then - echo "Missing COOLIFY_DEPLOY_HOOK_URL secret" + if [ -z "$HOOK_URL" ]; then + echo "Missing COOLIFY_DEPLOY_HOOK_URL_BOT" exit 1 fi - if [ -n "$COOLIFY_DEPLOY_TOKEN" ]; then + if [ -n "$HOOK_TOKEN" ]; then curl --fail --show-error --silent \ -X POST \ - -H "Authorization: Bearer $COOLIFY_DEPLOY_TOKEN" \ - "$COOLIFY_DEPLOY_HOOK_URL" + -H "Authorization: Bearer $HOOK_TOKEN" \ + "$HOOK_URL" else - curl --fail --show-error --silent -X POST "$COOLIFY_DEPLOY_HOOK_URL" + curl --fail --show-error --silent -X POST "$HOOK_URL" fi - echo "Coolify deploy triggered" + echo "Backend deploy triggered" + + - name: Trigger dashboard deploy + env: + HOOK_URL: ${{ secrets.COOLIFY_DEPLOY_HOOK_URL_DASHBOARD }} + HOOK_TOKEN: ${{ secrets.COOLIFY_DEPLOY_TOKEN_DASHBOARD }} + run: | + if [ -z "$HOOK_URL" ]; then + echo "Missing COOLIFY_DEPLOY_HOOK_URL_DASHBOARD" + exit 1 + fi + + if [ -n "$HOOK_TOKEN" ]; then + curl --fail --show-error --silent \ + -X POST \ + -H "Authorization: Bearer $HOOK_TOKEN" \ + "$HOOK_URL" + else + curl --fail --show-error --silent -X POST "$HOOK_URL" + fi + + echo "Dashboard deploy triggered" diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index d1dc374..e0e5ee9 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -9,12 +9,17 @@ This document covers production deployment for the `omo-bot` Coolify project wit Recommended domains: -- `api.yourdomain.com` -> `omo-bot-backend` -- `admin.yourdomain.com` -> `omo-bot-dashboard` +- `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.yourdomain.com` serves dashboard and reverse-proxies `/admin/*` + `/health` to backend. +- `admin.openmicodyssey.com` serves dashboard and reverse-proxies `/admin/*` + `/health` to backend. ## 1. Create Coolify Project and Resources @@ -22,13 +27,80 @@ Optional same-origin pattern: 2. Add resource `omo-bot-backend` from this repository: - Build context: repository root - Start command: `npm start` - - Port: `8787` (Admin API) if exposed + - 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: `dist` + - 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: @@ -39,14 +111,21 @@ Backend (`omo-bot-backend`) minimum: Dashboard (`omo-bot-dashboard`) minimum: -- `VITE_ADMIN_API_BASE_URL` (for split-domain deployment, set to `https://api.yourdomain.com`) +- `VITE_ADMIN_API_BASE_URL` (for split-domain deployment, set to `https://api.openmicodyssey.com`) - `VITE_DISCORD_CLIENT_ID` -- `VITE_DISCORD_REDIRECT_URI` (must match Discord OAuth2 redirect list) +- `VITE_DISCORD_REDIRECT_URI` (recommended: `https://admin.openmicodyssey.com/oauth/discord/callback`) OAuth alignment: -- Discord Developer Portal redirect URI must match dashboard redirect domain. -- Config DB key `OAUTH_BRIDGE_REDIRECT_URI` must match the same URI. +- 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 Deployment Hooks and Tokens @@ -71,68 +150,33 @@ In Gitea repository settings, add Actions secrets: - `COOLIFY_DEPLOY_HOOK_URL_DASHBOARD` - `COOLIFY_DEPLOY_TOKEN_DASHBOARD` (optional) -Current workflow in `.gitea/workflows/ci-cd.yml` uses a single pair: +Current workflow in `.gitea/workflows/ci-cd.yml` triggers backend and dashboard deploys separately using these four secrets. -- `COOLIFY_DEPLOY_HOOK_URL` -- `COOLIFY_DEPLOY_TOKEN` +## 5. Domain and DNS Checklist -If you use separate resources with separate hooks, update workflow deploy step to call both hooks. +1. Create DNS records to Nginx edge host (or directly to Coolify if no edge proxy): -## 5. Example Deploy Step for Two Coolify Resources +- `A` record for root/subdomain targets +- `CNAME` where appropriate -```yaml -deploy-coolify: - name: Deploy to Coolify - runs-on: ubuntu-latest - needs: - - bot-checks - - dashboard-checks - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - steps: - - name: Trigger backend deploy - env: - HOOK_URL: ${{ secrets.COOLIFY_DEPLOY_HOOK_URL_BOT }} - HOOK_TOKEN: ${{ secrets.COOLIFY_DEPLOY_TOKEN_BOT }} - run: | - if [ -z "$HOOK_URL" ]; then - echo "Missing COOLIFY_DEPLOY_HOOK_URL_BOT" - exit 1 - fi - if [ -n "$HOOK_TOKEN" ]; then - curl --fail --show-error --silent -X POST -H "Authorization: Bearer $HOOK_TOKEN" "$HOOK_URL" - else - curl --fail --show-error --silent -X POST "$HOOK_URL" - fi +2. Verify certificates issued on Nginx or Coolify, depending on TLS termination point. +3. Verify Nginx upstreams match deployed ports: - - name: Trigger dashboard deploy - env: - HOOK_URL: ${{ secrets.COOLIFY_DEPLOY_HOOK_URL_DASHBOARD }} - HOOK_TOKEN: ${{ secrets.COOLIFY_DEPLOY_TOKEN_DASHBOARD }} - run: | - if [ -z "$HOOK_URL" ]; then - echo "Missing COOLIFY_DEPLOY_HOOK_URL_DASHBOARD" - exit 1 - fi - if [ -n "$HOOK_TOKEN" ]; then - curl --fail --show-error --silent -X POST -H "Authorization: Bearer $HOOK_TOKEN" "$HOOK_URL" - else - curl --fail --show-error --silent -X POST "$HOOK_URL" - fi -``` +- backend -> `8787` +- dashboard -> `80` -## 6. Domain and DNS Checklist +4. Verify OAuth callback route resolves through dashboard hosting: -1. Create DNS records to Coolify host: - - `A` record for root/subdomain targets - - `CNAME` where appropriate -2. Verify certificates issued for both domains. -3. Verify dashboard can call API at configured `VITE_ADMIN_API_BASE_URL`. -4. Verify API health endpoint over TLS: - - `GET https://api.yourdomain.com/health` +- `https://admin.openmicodyssey.com/oauth/discord/callback` -## 7. Post-Deploy Verification +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. Validate Admin API auth behavior with and without bearer token. +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. diff --git a/README.md b/README.md index 82a6088..d5cb843 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ This project uses the arc42 template. Chapters are in `docs/`. 3. Copy these values for runtime config (`bot_settings`): - `DISCORD_TOKEN` from Bot tab (Reset Token if needed) - `DISCORD_CLIENT_ID` from OAuth2 > General + - `OAUTH_BRIDGE_REDIRECT_URI` as exact callback URL, recommended: `https://admin.openmicodyssey.com/oauth/discord/callback` 4. In `Bot` > `Privileged Gateway Intents`, enable only: - `Server Members Intent` (required by current code) 5. Keep these disabled unless code changes require them: @@ -171,6 +172,16 @@ npm run dev Dashboard environment template: `admin-dashboard/.env.example`. +Recommended production OAuth callback URL: + +- `https://admin.openmicodyssey.com/oauth/discord/callback` + +This same URL must match: + +- Discord OAuth2 redirect whitelist +- `VITE_DISCORD_REDIRECT_URI` +- `OAUTH_BRIDGE_REDIRECT_URI` + ### Validation Commands ```bash diff --git a/admin-dashboard/.env.example b/admin-dashboard/.env.example index af22a96..ec60b57 100644 --- a/admin-dashboard/.env.example +++ b/admin-dashboard/.env.example @@ -1,4 +1,5 @@ VITE_ADMIN_API_BASE_URL=http://localhost:8787 VITE_DISCORD_CLIENT_ID= -# Must match Discord OAuth2 redirect URI whitelist. -VITE_DISCORD_REDIRECT_URI=http://localhost:5173 +# Must exactly match Discord OAuth2 redirect URI whitelist and OAUTH_BRIDGE_REDIRECT_URI. +# Recommended production route: https://admin.openmicodyssey.com/oauth/discord/callback +VITE_DISCORD_REDIRECT_URI=http://localhost:5173/oauth/discord/callback diff --git a/docs/07_deployment_view.md b/docs/07_deployment_view.md index bdc9c86..8f3b903 100644 --- a/docs/07_deployment_view.md +++ b/docs/07_deployment_view.md @@ -3,26 +3,37 @@ ## Infrastructure Overview ```txt -┌──────────────┐ HTTPS/WSS ┌───────────────────────────────────┐ -│ Discord APIs │◄───────────────────►│ Coolify deployment │ -└──────────────┘ │ ┌───────────────────────────────┐ │ - │ │ Backend container │ │ - │ │ - Discord gateway runtime │ │ - │ │ - Admin API (Express) │ │ - │ │ - Dailies pollers │ │ - │ └──────────────┬────────────────┘ │ - └─────────────────│──────────────────┘ - │ - ▼ - ┌───────────────────┐ - │ PostgreSQL │ - │ mileage + config │ - └───────────────────┘ +┌──────────────┐ HTTPS/WSS ┌──────────────────────────────┐ +│ Discord APIs │◄───────────────────►│ Coolify backend resource │ +└──────────────┘ │ port 8787 │ + │ - Discord gateway runtime │ + │ - Admin API (Express) │ + │ - Dailies pollers │ + └──────────────┬───────────────┘ + │ + ▼ + ┌───────────────────┐ + │ PostgreSQL │ + │ mileage + config │ + └───────────────────┘ -┌──────────────────────────┐ HTTPS ┌─────────────────────────────┐ -│ Admin Dashboard (SPA) │◄───────────►│ Admin API + OAuth bridge │ -│ React/Vite static deploy │ │ │ -└──────────────────────────┘ └─────────────────────────────┘ +Internet + │ + ▼ +┌──────────────────────────┐ +│ Nginx edge reverse proxy │ +│ - TLS termination │ +│ - Host/path routing │ +└─────────────┬────────────┘ + │ + ┌────────┴───────────┐ + ▼ ▼ +┌───────────────┐ ┌──────────────────────────┐ +│ Coolify │ │ Coolify dashboard │ +│ backend │ │ resource │ +│ port 8787 │ │ port 80 │ +└───────────────┘ │ React/Vite static deploy │ + └──────────────────────────┘ ``` ## CI/CD Pipeline @@ -32,14 +43,15 @@ Source file: `.gitea/workflows/ci-cd.yml` 1. Bot job: install dependencies, run lint, build, and test. 2. Dashboard job: install dependencies in `admin-dashboard`, run lint/build. 3. Deploy job: on `main`, trigger Coolify webhook (optionally authenticated with token). +4. In two-resource production setup, deploy webhooks can independently trigger backend and dashboard resources. ## Environment Matrix -| Environment | Runtime | Database | Notes | -| ----------------- | --------------------------------- | -------------------------------------- | ------------------------------------------- | -| Local development | Node.js process via `npm run dev` | Local/Postgres dev instance | Fast iteration, manual command registration | -| CI | Ephemeral runner | Test/dev DB or mocked integration path | Quality gates before deployment | -| Production | Coolify-managed container(s) | Managed PostgreSQL | Webhook-triggered deployment from Gitea | +| Environment | Runtime | Database | Notes | +| ----------------- | ------------------------------------------------ | -------------------------------------- | ---------------------------------------------------------------------------- | +| Local development | Node.js process via `npm run dev` | Local/Postgres dev instance | Fast iteration, manual command registration | +| CI | Ephemeral runner | Test/dev DB or mocked integration path | Quality gates before deployment | +| Production | Nginx edge + Coolify backend/dashboard resources | Managed PostgreSQL | Host-based routing, TLS termination, webhook-triggered deployment from Gitea | ## Deployment Risks and Mitigations @@ -47,3 +59,5 @@ Source file: `.gitea/workflows/ci-cd.yml` Mitigation: dashboard re-auth flow and short-lived session design. - Risk: DB connectivity interruptions. Mitigation: startup validation + retry behavior on mileage writes. +- Risk: edge proxy misroutes dashboard/API traffic. + Mitigation: verify upstream hostnames/ports and health checks after deploy.