diff --git a/devops/app-router/README.md b/devops/app-router/README.md index fbde0ac..1a8a470 100644 --- a/devops/app-router/README.md +++ b/devops/app-router/README.md @@ -153,6 +153,35 @@ Slugs are validated against `^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$` — lowerca alphanumerics and hyphens, max 32 chars. Anything outside that gets a 400 from `/auth/verify` and `/auth/login`. +## Fronting OpenClaw Webhooks (optional) + +External callers like AgentMail Svix webhooks and iOS Shortcuts don't always support +custom `Authorization` headers. The OpenClaw gateway hooks server (`127.0.0.1:18789`) +requires a bearer token on every request. Caddy can inject it transparently so callers +don't need to send auth themselves. + +**Setup:** + +1. Set `OPENCLAW_HOOK_TOKEN` in the Caddy process environment. The easiest way is via + the PM2 ecosystem env block in `ecosystem.config.js`: + ```js + env: { + OPENCLAW_HOOK_TOKEN: "your-token-here"; + } + ``` +2. Uncomment the `handle /hooks/* { ... }` block in the Caddyfile. +3. Reload Caddy: + `caddy reload --config ~/openclaw-apps/router/Caddyfile --adapter caddyfile` + +Callers can then `POST https://:4242/hooks/` with no `Authorization` +header — Caddy injects `Bearer ` before forwarding to the gateway. + +**Legacy URL preservation:** If existing webhook subscriptions already point at +`https:///hooks/` (the default `:443` tailscale-serve mount), set +`APP_ROUTER_HOOKS_PATH=/hooks/` in the launchd plist's `EnvironmentVariables` block. The +restore script will apply an additional `tailscale serve --set-path` mount so both the +`:4242` and `:443` URLs reach the app router. + ## Public Access Tailscale Serve is tailnet-only. To make an app accessible from outside the tailnet diff --git a/devops/app-router/launchd/ai.openclaw.app-router-serve.plist b/devops/app-router/launchd/ai.openclaw.app-router-serve.plist index 95f1584..c254c59 100644 --- a/devops/app-router/launchd/ai.openclaw.app-router-serve.plist +++ b/devops/app-router/launchd/ai.openclaw.app-router-serve.plist @@ -31,6 +31,17 @@ 4242 APP_ROUTER_UPSTREAM http://127.0.0.1:8080 + + RunAtLoad diff --git a/devops/app-router/scripts/restore-tailscale-serve.sh b/devops/app-router/scripts/restore-tailscale-serve.sh index e5318f0..13c6b5b 100755 --- a/devops/app-router/scripts/restore-tailscale-serve.sh +++ b/devops/app-router/scripts/restore-tailscale-serve.sh @@ -54,3 +54,18 @@ fi echo "[app-router-serve] applying serve: --https=${PORT} → ${UPSTREAM}" "$TAILSCALE_BIN" serve --bg "--https=${PORT}" "$UPSTREAM" + +# Optional: legacy /hooks/ path on :443 → app-router (so existing AgentMail/iOS +# Shortcut subscriptions that POST to https:///hooks/ keep working +# without subscription changes). +if [ -n "${APP_ROUTER_HOOKS_PATH:-}" ]; then + echo "[app-router-serve] applying serve: --set-path=${APP_ROUTER_HOOKS_PATH} → ${UPSTREAM}${APP_ROUTER_HOOKS_PATH}" + "$TAILSCALE_BIN" serve --bg --set-path="${APP_ROUTER_HOOKS_PATH}" "${UPSTREAM}${APP_ROUTER_HOOKS_PATH}" +fi + +# Optional: mount a separate upstream on the default :443 / root path +# (typically the OpenClaw control UI on :18790). +if [ -n "${APP_ROUTER_ROOT_UPSTREAM:-}" ]; then + echo "[app-router-serve] applying serve: --set-path=/ → ${APP_ROUTER_ROOT_UPSTREAM}" + "$TAILSCALE_BIN" serve --bg --set-path=/ "${APP_ROUTER_ROOT_UPSTREAM}" +fi diff --git a/devops/app-router/templates/Caddyfile.example b/devops/app-router/templates/Caddyfile.example index a2815e1..a15f1d9 100644 --- a/devops/app-router/templates/Caddyfile.example +++ b/devops/app-router/templates/Caddyfile.example @@ -11,6 +11,7 @@ # After editing: `caddy reload --config --adapter caddyfile` :8080 { + bind 127.0.0.1 ::1 # Auth service endpoints (login form, verify, logout). handle /auth/* { @@ -22,6 +23,19 @@ respond "ok" 200 } + # ── OpenClaw webhook routing (optional) ─────────────────────────────── + # Front the OpenClaw gateway hooks server with bearer injection so + # external callers (AgentMail Svix, iOS Shortcuts, etc.) can POST + # without sending Authorization headers. Set OPENCLAW_HOOK_TOKEN in + # the Caddy process environment (e.g., via PM2 ecosystem env), then + # uncomment this block. + # + # handle /hooks/* { + # reverse_proxy 127.0.0.1:18789 { + # header_up Authorization "Bearer {env.OPENCLAW_HOOK_TOKEN}" + # } + # } + # ── Open app: no password, just a path → port mapping. ──────────────── handle /my-open-app/* { uri strip_prefix /my-open-app