changemaker.lite/PANGOLIN_NGINX_FIX_SUMMARY.md

182 lines
6.2 KiB
Markdown

# Pangolin Tunnel Management + Nginx Multi-Domain Support - Implementation Summary
**Date:** 2026-02-15
**Status:** ✅ COMPLETE
## Changes Implemented
### 1. Pangolin Resources Configuration (`configs/pangolin/resources.yml`)
**Added 2 new resources:**
- Excalidraw (subdomain: `draw`, container: `excalidraw-changemaker`, port: 80)
- MailHog (subdomain: `mail`, container: `mailhog-changemaker`, port: 8025)
**Fixed Mini QR resource:**
- Container name: `miniqr-changemaker``mini-qr`
- Port: `8089``8080`
**Total resources:** 14 (up from 12)
### 2. Nginx Template Updates (`nginx/conf.d/services.conf.template`)
Updated 6 server blocks to support multi-domain routing:
| Service | Old server_name | New server_name |
|---------|----------------|-----------------|
| **Gitea** | `git.${DOMAIN}` | `git.cmlite.org git.betteredmonton.org` |
| **n8n** | `n8n.${DOMAIN}` | `n8n.cmlite.org n8n.betteredmonton.org` |
| **Code Server** | `code.${DOMAIN}` | `code.cmlite.org code.betteredmonton.org` |
| **MailHog** | `mail.${DOMAIN}` | `mail.cmlite.org mail.betteredmonton.org` |
| **Mini QR** | `qr.${DOMAIN}` | `qr.cmlite.org qr.betteredmonton.org` |
| **Excalidraw** | `draw.${DOMAIN}` | `draw.cmlite.org draw.betteredmonton.org` |
**CSP Headers Updated:**
All 6 services now allow iframe embedding from both admin domains:
```nginx
add_header Content-Security-Policy "frame-ancestors 'self' app.cmlite.org app.betteredmonton.org" always;
```
### 3. Nginx Container Rebuild
- Rebuilt nginx image to pick up updated template files
- Restarted nginx container with new configuration
- Verified nginx syntax: ✅ OK
## Root Cause Analysis
### Issue 1: Missing Resources
- Excalidraw and MailHog existed in infrastructure but weren't listed in `resources.yml`
- Pangolin page reads from this YAML to display resource status
### Issue 2: X-Frame-Options Error
- Nginx template used `${DOMAIN}` variable (only substitutes ONE domain from `.env`)
- When accessing via alternate domain, nginx didn't match any server block
- Request fell back to default server with `X-Frame-Options: SAMEORIGIN`
- This blocked iframe embedding
### Issue 3: Wrong Container Name
- resources.yml referenced `miniqr-changemaker` (doesn't exist)
- Correct name from docker-compose.yml: `mini-qr`
## Solution Applied
**Pattern:** Hardcoded dual-domain `server_name` directives (same pattern as NocoDB, Listmonk, Grafana, etc.)
**Why not template variables?**
- `${DOMAIN}` substitution only supports ONE domain value
- We need BOTH domains for multi-domain production deployment
- Hardcoding is explicit and predictable
## Verification Results
### ✅ Nginx Configuration
```bash
$ docker compose exec nginx nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
### ✅ Multi-Domain Server Blocks
All 6 services verified with both domains in `server_name`:
- Mini QR: `qr.cmlite.org qr.betteredmonton.org`
- Gitea: `git.cmlite.org git.betteredmonton.org`
- n8n: `n8n.cmlite.org n8n.betteredmonton.org`
- Code Server: `code.cmlite.org code.betteredmonton.org`
- MailHog: `mail.cmlite.org mail.betteredmonton.org`
- Excalidraw: `draw.cmlite.org draw.betteredmonton.org`
### ✅ Resources Count
```bash
$ grep -c "subdomain:" configs/pangolin/resources.yml
14
```
## Testing Instructions
### 1. Test Pangolin Resources Page
```bash
# Access Pangolin page
# Navigate to http://localhost:3000/app/pangolin
# or https://app.betteredmonton.org/app/pangolin
# Verify:
# ✅ Resources table shows 14 services
# ✅ "Excalidraw" appears in list
# ✅ "MailHog" appears in list
# ✅ Mini QR container shows as "mini-qr"
```
### 2. Test Multi-Domain Access (Production)
```bash
# Test all 6 services respond on both domains
for service in qr git n8n code mail draw; do
for domain in cmlite.org betteredmonton.org; do
echo "Testing ${service}.${domain}..."
curl -I https://${service}.${domain} 2>&1 | grep HTTP | head -1
done
done
# Expected: All return HTTP/1.1 200 OK (or 302 for auth-protected)
```
### 3. Test Iframe Embedding
```bash
# Test Mini QR iframe from both domains
# 1. Navigate to https://app.betteredmonton.org/app/services/qr
# 2. Check browser console for errors
# 3. Verify Mini QR loads without "Refused to display" error
# 4. Repeat from https://app.cmlite.org/app/services/qr
# Test other services similarly
```
### 4. Verify CSP Headers
```bash
# Check CSP headers include both admin domains
curl -I https://qr.betteredmonton.org 2>&1 | grep Content-Security-Policy
# Expected:
# Content-Security-Policy: frame-ancestors 'self' app.cmlite.org app.betteredmonton.org
```
## Success Criteria
✅ Pangolin resources list shows 14 services (up from 12)
✅ Excalidraw and MailHog appear in resources table
✅ Mini QR container name corrected to `mini-qr`
✅ Mini QR iframe loads without X-Frame-Options errors
✅ All 6 iframe-embedded services work on both domains
✅ CSP headers allow embedding from both app domains
✅ Nginx config regenerates successfully without syntax errors
## Files Modified
1. **configs/pangolin/resources.yml** - Added 2 resources, fixed 1 container name/port
2. **nginx/conf.d/services.conf.template** - Updated 6 server blocks for dual-domain support
## Next Steps
1. **Pangolin Setup** (Manual)
- Login to Pangolin dashboard
- Create resources for new services (Excalidraw, MailHog)
- Set authentication to "Not Protected" for public access
- Verify tunnel connectivity
2. **Production Deployment**
- Rebuild nginx image: `docker compose build nginx`
- Restart nginx: `docker compose up -d nginx`
- Test all services on both domains
- Verify iframe embedding works
3. **Documentation Updates**
- Update CLAUDE.md with new resource count
- Update MEMORY.md with nginx rebuild requirement for template changes
- Document dual-domain pattern in troubleshooting guide
## Notes
- **Important:** Template changes require nginx image rebuild (`docker compose build nginx`)
- **Pattern:** Hardcoded domains prevent future confusion vs. variable substitution
- **Scope:** Frontend already uses `buildServiceUrl()` correctly (no code changes needed)
- **Manual Setup:** Pangolin resources.yml is documentation/reference only (manual setup per MEMORY.md)