450 lines
10 KiB
Markdown

# Pangolin Tunnel Deployment
## Overview
Pangolin is a self-hosted tunnel service (alternative to Cloudflare Tunnel) that provides public HTTPS access to your Changemaker Lite instance without port forwarding or firewall configuration.
**Benefits:**
- No port forwarding needed
- SSL/TLS handled by tunnel provider
- Static public URLs
- Self-hosted tunnel server (privacy/control)
- Free/open source
**Architecture:**
```
Internet → Pangolin Tunnel (pangolin.bnkserve.org) → Newt Container → Nginx → Services
```
**Changemaker Integration:**
- Pangolin server: https://api.bnkserve.org/v1
- Tunnel endpoint: https://pangolin.bnkserve.org
- Newt container: Tunnel connector (fosrl/newt image)
- Admin GUI: PangolinPage.tsx setup wizard
---
## Setup Workflow
### 1. Prerequisites
**Required:**
- Pangolin API key (obtain from Pangolin admin)
- Docker Compose running
- Nginx container accessible from Newt
**Environment Variables:**
```bash
PANGOLIN_API_URL=https://api.bnkserve.org/v1
PANGOLIN_API_KEY=<your-api-key>
PANGOLIN_ORG_ID=<set-after-org-creation>
PANGOLIN_SITE_ID=<set-after-site-creation>
PANGOLIN_ENDPOINT=https://pangolin.bnkserve.org
PANGOLIN_NEWT_ID=<set-after-resource-creation>
PANGOLIN_NEWT_SECRET=<set-after-resource-creation>
```
---
### 2. Setup via Admin GUI
**Easiest method:** Use `/app/pangolin` page in admin GUI.
**Steps:**
1. Navigate to http://localhost:3000/app/pangolin
2. Enter `PANGOLIN_API_KEY` (click "Test Connection")
3. Create Organization (or select existing)
4. Create Site (linked to org)
5. Create Endpoint (tunnel URL)
6. Create Resource (Newt connector credentials)
7. Copy `NEWT_ID` and `NEWT_SECRET` to `.env`
8. Restart Newt container: `docker compose restart newt`
---
### 3. Manual Setup (CLI)
**Organization:**
```bash
curl -X POST https://api.bnkserve.org/v1/orgs \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My Organization",
"description": "Changemaker Lite Production"
}'
# Returns:
# {"id":"org_abc123","name":"My Organization",...}
export PANGOLIN_ORG_ID=org_abc123
```
**Site:**
```bash
curl -X POST https://api.bnkserve.org/v1/sites \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"org_id": "'"$PANGOLIN_ORG_ID"'",
"name": "Production Site",
"description": "Main deployment"
}'
# Returns:
# {"id":"site_xyz789","name":"Production Site",...}
export PANGOLIN_SITE_ID=site_xyz789
```
**Endpoint:**
```bash
curl -X POST https://api.bnkserve.org/v1/endpoints \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "'"$PANGOLIN_SITE_ID"'",
"subdomain": "changemaker",
"domain": "pangolin.bnkserve.org"
}'
# Returns:
# {"id":"endpoint_def456","url":"https://changemaker.pangolin.bnkserve.org",...}
export PANGOLIN_ENDPOINT=https://changemaker.pangolin.bnkserve.org
```
**Resource (Newt Connector):**
```bash
curl -X POST https://api.bnkserve.org/v1/resources \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"endpoint_id": "<endpoint-id>",
"target": "http://nginx:80",
"name": "Changemaker Services"
}'
# Returns:
# {"id":"newt_abc123","secret":"secret_xyz789",...}
export PANGOLIN_NEWT_ID=newt_abc123
export PANGOLIN_NEWT_SECRET=secret_xyz789
```
**Update .env:**
```bash
cat >> .env <<EOF
PANGOLIN_ORG_ID=$PANGOLIN_ORG_ID
PANGOLIN_SITE_ID=$PANGOLIN_SITE_ID
PANGOLIN_ENDPOINT=$PANGOLIN_ENDPOINT
PANGOLIN_NEWT_ID=$PANGOLIN_NEWT_ID
PANGOLIN_NEWT_SECRET=$PANGOLIN_NEWT_SECRET
EOF
```
---
### 4. Start Newt Container
```bash
# Restart to pick up new env vars
docker compose up -d --force-recreate newt
# Check logs
docker compose logs -f newt
# Should see:
# [INFO] Connected to Pangolin tunnel
# [INFO] Tunnel active: https://changemaker.pangolin.bnkserve.org
```
---
### 5. DNS Configuration
**Option A: Direct Access** (use tunnel URL):
- https://changemaker.pangolin.bnkserve.org
**Option B: Custom Domain** (CNAME to tunnel):
```
app.cmlite.org. CNAME changemaker.pangolin.bnkserve.org.
api.cmlite.org. CNAME changemaker.pangolin.bnkserve.org.
```
---
## Newt Container Configuration
**docker-compose.yml:**
```yaml
newt:
image: fosrl/newt
container_name: newt-changemaker
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT}
- NEWT_ID=${PANGOLIN_NEWT_ID}
- NEWT_SECRET=${PANGOLIN_NEWT_SECRET}
depends_on:
- nginx
networks:
- changemaker-lite
```
**Key Features:**
- **Restart policy**: `unless-stopped` (auto-reconnects)
- **Nginx dependency**: Ensures Nginx running before Newt starts
- **No ports exposed**: All traffic via tunnel
**Target**: Newt connects to `http://nginx:80` (Docker internal network).
---
## Tunnel Lifecycle
### Start Tunnel
```bash
docker compose up -d newt
```
### Stop Tunnel
```bash
docker compose stop newt
```
### Check Status
```bash
# Container status
docker compose ps newt
# Logs
docker compose logs --tail=50 newt
# Test tunnel
curl https://changemaker.pangolin.bnkserve.org/api/health
```
### Restart (after .env changes)
```bash
docker compose up -d --force-recreate newt
```
---
## Exit Nodes & Resource Routing
**Resource**: Defines how tunnel routes traffic to your services.
**Target URL**: `http://nginx:80` (Nginx handles subdomain routing internally).
**Example Flow:**
1. User visits `https://changemaker.pangolin.bnkserve.org/api/health`
2. Pangolin tunnel receives HTTPS request
3. Tunnel forwards to Newt container
4. Newt proxies to `http://nginx:80/api/health`
5. Nginx routes to `changemaker-v2-api:4000/api/health`
6. Response flows back through tunnel
**Multiple Resources:** Create separate resources for different backends (advanced).
---
## Troubleshooting
### Tunnel Not Connecting
**Symptoms:** Newt logs show connection errors
**Diagnosis:**
```bash
docker compose logs newt
# Common errors:
# - "Authentication failed" (wrong NEWT_ID/SECRET)
# - "Endpoint not found" (wrong PANGOLIN_ENDPOINT)
# - "Connection refused" (Nginx not running)
```
**Solutions:**
```bash
# Verify credentials
docker compose exec newt printenv | grep PANGOLIN
# Test Nginx from Newt container
docker compose exec newt wget -O- http://nginx:80/api/health
# Restart Newt
docker compose restart newt
```
---
### Tunnel Connected But Site Unreachable
**Symptoms:** Newt connected, but HTTPS requests timeout/fail
**Diagnosis:**
```bash
# Test tunnel endpoint
curl -I https://changemaker.pangolin.bnkserve.org
# Check Nginx logs
docker compose logs nginx | tail -50
# Verify resource target
curl -X GET https://api.bnkserve.org/v1/resources/<resource-id> \
-H "Authorization: Bearer $PANGOLIN_API_KEY"
```
**Common Causes:**
- Resource target points to wrong service
- Nginx not listening on port 80
- Firewall blocking Nginx → backend communication
**Solution:**
```bash
# Verify Nginx config
docker compose exec nginx nginx -t
# Check Nginx listening
docker compose exec nginx netstat -tulpn | grep :80
# Test backend from Nginx
docker compose exec nginx curl http://changemaker-v2-api:4000/api/health
```
---
### SSL Certificate Errors
**Symptoms:** Browser shows "Certificate invalid" warning
**Cause:** Tunnel endpoint SSL certificate not trusted (rare).
**Solution:** Contact Pangolin support — tunnel provider manages SSL certificates.
---
### Frequent Disconnects
**Symptoms:** Newt reconnects every few minutes
**Diagnosis:**
```bash
# Check for network issues
docker compose logs newt | grep -i disconnect
# Monitor connection
watch -n5 'docker compose logs --tail=1 newt'
```
**Possible Causes:**
- Network instability
- Container restarts (check `docker compose ps`)
- Resource limits (check `docker stats newt-changemaker`)
**Solution:**
```bash
# Increase restart backoff (if needed)
# Edit docker-compose.yml:
newt:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
```
---
## Migration from Cloudflare Tunnel
**Retired Scripts** (in `scripts/legacy/`):
- `start-production.sh`
- `config.sh`
- `tunnel-config.sh`
**Migration Steps:**
1. Stop Cloudflare tunnel: `cloudflared service uninstall`
2. Remove Cloudflare credentials: `rm ~/.cloudflared/*.json`
3. Setup Pangolin tunnel (see above)
4. Update DNS: Change CNAME from `cloudflared.com` to `pangolin.bnkserve.org`
5. Test new tunnel: `curl https://changemaker.pangolin.bnkserve.org/api/health`
6. Remove old scripts: `rm scripts/legacy/*`
**Why Pangolin?**
- Self-hosted (privacy/control)
- No Cloudflare dependency
- Free/open source
- API-driven management
---
## Advanced Configuration
### Custom Tunnel Domain
**Requirement:** Own domain with DNS control.
**Steps:**
1. Create endpoint with custom domain
2. Add DNS record: `tunnel.cmlite.org CNAME pangolin.bnkserve.org.`
3. Update `PANGOLIN_ENDPOINT=https://tunnel.cmlite.org`
4. Restart Newt
### Multiple Sites
**Use case:** Staging + production on same tunnel.
**Setup:**
```bash
# Create second site
curl -X POST https://api.bnkserve.org/v1/sites \
-d '{"org_id":"...","name":"Staging"}'
# Create endpoint for staging
curl -X POST https://api.bnkserve.org/v1/endpoints \
-d '{"site_id":"...","subdomain":"staging-changemaker"}'
# Create resource pointing to staging Nginx
curl -X POST https://api.bnkserve.org/v1/resources \
-d '{"endpoint_id":"...","target":"http://nginx-staging:80"}'
```
---
## Monitoring
### Health Checks
**Tunnel status:**
```bash
# Container health
docker compose ps newt
# Connection logs
docker compose logs --tail=50 newt | grep -i connected
# Test public endpoint
curl -I https://changemaker.pangolin.bnkserve.org
```
**Prometheus metrics** (if enabled):
```bash
# API uptime through tunnel
curl https://changemaker.pangolin.bnkserve.org/api/metrics | grep cm_api_uptime
```
---
## Related Documentation
- **[Docker Compose](docker-compose.md)** — Newt container configuration
- **[Nginx Configuration](nginx.md)** — Reverse proxy setup
- **[SSL/TLS](ssl-tls.md)** — Certificate management (handled by tunnel)
- **[Environment Variables](environment-variables.md)** — Pangolin env vars