450 lines
10 KiB
Markdown
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
|