182 lines
6.2 KiB
Markdown
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)
|