Remote-Pulse Public Routing Setup¶
Status: Operational (F7-4)
Owner: Ramón Kamibayashi
Related: ADR-0008 §4 Stack server / Bootstrap public route, ADR-0008 §10 Bootstrap one-liner
Overview¶
Remote-Pulse requires two public HTTPS routes for bootstrap and web dashboard:
rp.monxas.casa— Bootstrap endpoint (install scripts + enrollment API)dash.rp.monxas.casa— Web dashboard (F5, PocketID SSO)
Both routes terminate TLS at Caddy LXC HA pair (270/271) and proxy to LXC 280 rp-server.
Security Model¶
- Bootstrap
rp.monxas.casa: Intentionally public for one-command installs. Only/v1/enroll,/v1/agent/reauth,/health, and static install scripts exposed. Everything else returns 401. Enrollment protected by JWT validation server-side. - Dashboard
dash.rp.monxas.casa: PocketID forward_auth required (added in F5). - Data plane: All agent↔server traffic post-enrollment flows via Tailscale (NAT-traversal, identity injection). Public routes are bootstrap-only.
Prerequisites¶
- Caddy LXC HA pair (270/271) operational with keepalived VIP
.250 - Cloudflare DNS API access (CF_API_TOKEN in SOPS)
- TLS certs
/etc/caddy/certs/monxas.casa.{pem,key}on LXCs - LXC 280
rp-serverrunning FastAPI on port 8080
1. Cloudflare DNS Records¶
Add two CNAME records in Cloudflare dashboard or via API:
# Via Cloudflare dashboard
# Zone: monxas.casa
# Type: CNAME
# Name: rp
# Target: caddy-vip.monxas.casa (resolves to 192.168.0.250 via local DNS / Pi-hole)
# Proxy: Enabled (orange cloud)
# TTL: Auto
# Name: dash.rp
# Target: caddy-vip.monxas.casa
# Proxy: Enabled
# TTL: Auto
Alternative via CLI (using cf-cli or terraform):
# Install cf-cli if not present: brew install cloudflare/cloudflare/cf-cli
cf dns create monxas.casa rp CNAME caddy-vip.monxas.casa --proxy
cf dns create monxas.casa dash.rp CNAME caddy-vip.monxas.casa --proxy
Validation:
dig rp.monxas.casa +short
# Should return Cloudflare proxy IPs (104.x.x.x or 172.x.x.x), not your home IP
dig dash.rp.monxas.casa +short
# Same
2. Cloudflare Tunnel (Optional — Not Required for Default Setup)¶
Default setup uses direct DNS → VIP → Caddy LXC. No CF Tunnel needed for bootstrap routes (enrollment flow is public HTTPS, not tunneled).
If you prefer CF Tunnel ingress:
- Add tunnel ingress rules in
~/.cloudflared/config.ymlon tunnel host (VM 208):
ingress:
- hostname: rp.monxas.casa
service: https://192.168.0.250:443
originRequest:
noTLSVerify: true
originServerName: rp.monxas.casa
- hostname: dash.rp.monxas.casa
service: https://192.168.0.250:443
originRequest:
noTLSVerify: true
originServerName: dash.rp.monxas.casa
# ... existing rules
- service: http_status:404
-
Restart tunnel:
systemctl restart cloudflared -
Verify:
cloudflared tunnel info <tunnel-id>
3. Deploy Caddy Snippet to LXC 270/271¶
⚠️ Estado real (2026-07-12): el routing de rp está hoy inline en el bloque
*.monxas.casa { … }del Caddyfile principal de LXC 270/271 (→192.168.0.196:8080), no en un snippetrp.caddy— ese fichero no existe en/etc/caddy/snippets/(solo haywp-pulse-*). El rol ansible traerp-dashboard.caddy. Los comandos de abajo describen el flujo ansible previsto; verifica el estado desplegado antes de aplicar.
The snippet /etc/caddy/snippets/rp.caddy is deployed via Ansible or manually:
Via Ansible (Recommended)¶
cd ~/docs-repo
ansible-playbook -i ansible/inventory.yml ansible/playbook.yml \
--tags rp-caddy,rp-install-assets \
--limit caddy_lxc
Manual Deployment¶
# Sync snippet to both LXCs
scp ansible/roles/caddy_lxc/files/snippets/rp.caddy [email protected]:/etc/caddy/snippets/
scp ansible/roles/caddy_lxc/files/snippets/rp.caddy [email protected]:/etc/caddy/snippets/
# Import snippet in Caddyfile (if not already present)
ssh [email protected] "grep -q 'import snippets/rp.caddy' /etc/caddy/Caddyfile || echo 'import snippets/rp.caddy' >> /etc/caddy/Caddyfile"
ssh [email protected] "grep -q 'import snippets/rp.caddy' /etc/caddy/Caddyfile || echo 'import snippets/rp.caddy' >> /etc/caddy/Caddyfile"
# Reload Caddy
ssh [email protected] systemctl reload caddy
ssh [email protected] systemctl reload caddy
Validation:
# Check Caddy logs for errors
ssh [email protected] journalctl -u caddy -n 50
ssh [email protected] journalctl -u caddy -n 50
4. Sync Bootstrap Install Scripts¶
Install scripts are served from /var/lib/caddy/rp-install/ on each Caddy LXC.
cd ~/docs-repo
ansible-playbook -i ansible/inventory.yml ansible/playbook.yml \
--tags rp-install-assets \
--limit caddy_lxc
This syncs:
- install.sh → /var/lib/caddy/rp-install/install.sh
- install.ps1 → /var/lib/caddy/rp-install/install.ps1
- install.sha256 (generated on target)
Re-sync cron: Daily at 03:15 to pick up updates from monxas-remote-pulse repo.
5. Verify TLS Termination¶
Test HTTPS endpoints from external network (not LAN, to validate full path):
# From your phone hotspot or VPS outside home network:
curl -fsSL https://rp.monxas.casa/health
# Expected: {"status":"ok"} or similar from rp-server
curl -fsSL https://rp.monxas.casa/install?show=1 | head -20
# Expected: bash shebang + Remote-Pulse installer header
curl -fsSL https://rp.monxas.casa/install.sha256
# Expected: SHA256 hashes of install.sh and install.ps1
TLS validation:
openssl s_client -connect rp.monxas.casa:443 -servername rp.monxas.casa < /dev/null | grep 'Verify return code'
# Expected: Verify return code: 0 (ok)
6. Test Enrollment Flow (End-to-End)¶
Generate enrollment token (requires LXC 280 rp-server running):
ssh [email protected] # LXC 280 rp-server
cd /opt/remote-pulse
source venv/bin/activate
rp admin enroll --group test --ttl 24h --max-uses 1
# Outputs JWT token: eyJhbGc...
Bootstrap test host (from a VM or container you can wipe):
Expected flow:
1. Script downloads and runs
2. Tailscale installed (if not present)
3. Agent enrolled via POST https://rp.monxas.casa/v1/enroll
4. Tailscale auth-key returned, tailscale up executed
5. Agent systemd service started
6. Host appears in dashboard (rp dash or curl https://rp-server.tailnet:8080/v1/hosts)
7. Troubleshooting¶
Symptom: curl install script returns 404¶
Cause: Install scripts not synced to Caddy LXC.
Fix:
ansible-playbook -i ansible/inventory.yml ansible/playbook.yml --tags rp-install-assets --limit caddy_lxc -vv
Check /var/lib/caddy/rp-install/ exists on LXC 270/271 and contains install.sh.
Symptom: POST /v1/enroll returns 502 Bad Gateway¶
Cause: LXC 280 rp-server not listening on port 8080.
Fix:
ssh [email protected]
systemctl status remote-pulse
journalctl -u remote-pulse -n 100
Verify FastAPI is bound to 0.0.0.0:8080:
Symptom: TLS cert invalid / self-signed warning¶
Cause: CF origin cert not deployed to Caddy LXC.
Fix:
# Verify certs exist
ssh [email protected] ls -lh /etc/caddy/certs/monxas.casa.{pem,key}
# If missing, deploy from SOPS-encrypted source
cd ~/docs-repo
ansible-playbook -i ansible/inventory.yml ansible/playbook.yml --tags caddy_lxc,tls_certs --limit caddy_lxc
Symptom: Enrollment works but agent can't reach server after install¶
Cause: Agent trying to use public route for data-plane (should be Tailscale).
Fix: Check agent config /etc/rp/agent.conf:
Enrollment should have written this correctly. If not, regenerate enrollment token and re-enroll.
Symptom: Rate-limit errors during testing¶
Cause: Caddy rate-limit (10 req/min/IP on /v1/enroll).
Temporary bypass for testing:
ssh [email protected]
# Comment out rate_limit block in /etc/caddy/snippets/rp.caddy
systemctl reload caddy
Restore after testing.
8. Rollback Procedure¶
If routing breaks production:
- Remove import from Caddyfile:
ssh [email protected] "sed -i '/import snippets\/rp.caddy/d' /etc/caddy/Caddyfile && systemctl reload caddy"
ssh [email protected] "sed -i '/import snippets\/rp.caddy/d' /etc/caddy/Caddyfile && systemctl reload caddy"
-
DNS: Change rp.monxas.casa CNAME to point elsewhere or delete record.
-
Investigate breakage in staging LXC before re-applying.
Next Steps¶
- F5: Add PocketID forward_auth to
dash.rp.monxas.casa - F7: Complete Windows native installer (replace .ps1 stub)
- Monitoring: Add uptime check for
rp.monxas.casa/healthin Healthchecks.io
Last updated: 2026-05-25 (F7-4)