Skip to content

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:

  1. rp.monxas.casa — Bootstrap endpoint (install scripts + enrollment API)
  2. 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-server running 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:

  1. Add tunnel ingress rules in ~/.cloudflared/config.yml on 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
  1. Restart tunnel: systemctl restart cloudflared

  2. 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 snippet rp.caddy — ese fichero no existe en /etc/caddy/snippets/ (solo hay wp-pulse-*). El rol ansible trae rp-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:

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):

curl -fsSL https://rp.monxas.casa/install | sh -s -- --token=eyJhbGc...

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:

ss -tlnp | grep 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:

[server]
url = "https://rp-server.monxas.ts.net"  # MagicDNS, not rp.monxas.casa

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:

  1. 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"
  1. DNS: Change rp.monxas.casa CNAME to point elsewhere or delete record.

  2. 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/health in Healthchecks.io

Last updated: 2026-05-25 (F7-4)