Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions devops/app-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use valid JS syntax in PM2 env example

The new setup snippet shows OPENCLAW_HOOK_TOKEN with a trailing semicolon inside an object literal, which is invalid JavaScript in ecosystem.config.js. If users copy this block as written, PM2 will fail to parse the config and Caddy won’t receive OPENCLAW_HOOK_TOKEN, so the webhook bearer-injection flow described in this section will not work.

Useful? React with 👍 / 👎.

}
```
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://<host>:4242/hooks/<hook-name>` with no `Authorization`
header — Caddy injects `Bearer <token>` before forwarding to the gateway.

**Legacy URL preservation:** If existing webhook subscriptions already point at
`https://<host>/hooks/<name>` (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
Expand Down
11 changes: 11 additions & 0 deletions devops/app-router/launchd/ai.openclaw.app-router-serve.plist
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@
<string>4242</string>
<key>APP_ROUTER_UPSTREAM</key>
<string>http://127.0.0.1:8080</string>
<!-- Optional: also expose /hooks/ on the default :443 tailscale-serve
mount for legacy webhook URLs. Uncomment to enable.
<key>APP_ROUTER_HOOKS_PATH</key>
<string>/hooks/</string>
-->
<!-- Optional: also expose / on the default :443 tailscale-serve
mount pointing at a second upstream (e.g., OpenClaw control UI on
127.0.0.1:18790). Uncomment to enable.
<key>APP_ROUTER_ROOT_UPSTREAM</key>
<string>http://127.0.0.1:18790</string>
-->
</dict>
<key>RunAtLoad</key>
<true/>
Expand Down
15 changes: 15 additions & 0 deletions devops/app-router/scripts/restore-tailscale-serve.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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://<host>/hooks/<name> 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
14 changes: 14 additions & 0 deletions devops/app-router/templates/Caddyfile.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# After editing: `caddy reload --config <this-file> --adapter caddyfile`

:8080 {
bind 127.0.0.1 ::1

# Auth service endpoints (login form, verify, logout).
handle /auth/* {
Expand All @@ -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
Expand Down
Loading