From 9b3b7c9cb4f6471d33301b21528aaaf51ae058c6 Mon Sep 17 00:00:00 2001 From: TechNickAI Date: Fri, 5 Jun 2026 10:02:24 -0500 Subject: [PATCH 1/3] docs(app-router): document HTTPS-Secure-cookie requirement and Hermes dashboard build step Two recurring gotchas hit during fleet rollout, now documented: 1. The session cookie is Secure, so password-protected apps only work over HTTPS. Serving over a plain-HTTP Tailscale listener makes login appear to succeed (303) but every subsequent request bounces back to login because the browser never returns the cookie. Added notes to the Auth Model and Troubleshooting sections, plus the HTTPS tailnet-serve command. 2. Hermes dashboards crash-loop under --skip-build when web_dist was never built on a fresh install. Added an 'Adding a Hermes dashboard' subsection covering the one-time 'npm run build', profile/HERMES_HOME pinning, and the X-Forwarded-Prefix Caddy block, plus a matching troubleshooting entry. --- devops/app-router/README.md | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/devops/app-router/README.md b/devops/app-router/README.md index 7d0fb8c..c7a2ddd 100644 --- a/devops/app-router/README.md +++ b/devops/app-router/README.md @@ -175,6 +175,47 @@ The app is immediately live at `https:////`. upstream (Caddy on `:8080`) at `:443`; Caddy handles all per-app path routing. Only edit `~/openclaw-apps/router/tailscale-serve.json` when changing ports or funnel state. +### Adding a Hermes dashboard + +A common app is a Hermes agent's own dashboard. Two gotchas specific to Hermes: + +1. **Build the web UI first, or drop `--skip-build`.** The dashboard is served from a + pre-built `web_dist/` directory. On a fresh Hermes install that directory may not + exist yet, and `hermes dashboard --skip-build` will crash-loop with + `✗ --skip-build was passed but no web dist found`. Build it once: + + ``` + cd ~/.hermes/hermes-agent/web && npm install && npm run build + ``` + + This emits `~/.hermes/hermes-agent/hermes_cli/web_dist/`. After that, `--skip-build` + works (and is preferred under PM2 so the process starts instantly without rebuilding). + +2. **Pin the profile and `HERMES_HOME`.** A dashboard reads sessions from one SQLite DB. + For a profile agent, pass `--profile `; for the root agent (cron jobs, no + profile), omit `--profile`. Always set `HERMES_HOME` explicitly in the PM2 `env` so + PM2's rewritten `$HOME` doesn't point the dashboard at an empty DB: + + ```js + { + name: "hermes-", + script: "/Users//.hermes/hermes-agent/venv/bin/hermes", + args: "--profile dashboard --port 9120 --no-open --skip-build", + interpreter: "none", + cwd: "/Users//.hermes/hermes-agent", + env: { + PATH: "/Users//.hermes/hermes-agent/venv/bin:/opt/homebrew/bin:/usr/bin:/bin", + HERMES_HOME: "/Users//.hermes", + }, + max_restarts: 10, + min_uptime: "30s", + } + ``` + + Add the matching `forward_auth` + `strip_prefix` Caddy block with + `header_up X-Forwarded-Prefix /hermes-` so the dashboard's SPA asset URLs + resolve under the path prefix. + ## Removing an App ``` @@ -213,6 +254,16 @@ 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`. +**The session cookie is `Secure`, so password-protected apps only work over HTTPS.** +The cookie is set with the `Secure` attribute, which means browsers (and `curl`) will +refuse to send it back over plain `http://`. If you front the router with a plain-HTTP +Tailscale Serve listener (e.g. `tailscale serve --http=`), login will appear to +succeed (the POST returns a 303) but every subsequent request gets bounced back to the +login page because the cookie never returns. Always expose the router over an **HTTPS** +door — `tailscale serve --https= http://127.0.0.1:8080` for tailnet-only, or the +default `:443` HTTPS funnel. Open (no-password) apps work fine over HTTP because they +have no cookie to lose. + ## Fronting OpenClaw Webhooks (optional) External callers like AgentMail Svix webhooks and iOS Shortcuts don't always support @@ -285,6 +336,17 @@ the app emits HTML with absolute paths to `/static/...` that 404 once it lives u `/my-app/`. Either configure a base path in the app, or rewrite assets at the Caddy layer. +**Login succeeds (303) but every page bounces back to the login screen.** The session +cookie is `Secure` and you're serving over plain HTTP, so the browser never sends the +cookie back. Expose the router over HTTPS instead — `tailscale serve --https= +http://127.0.0.1:8080` (tailnet-only) or the default `:443` funnel. See the note in the +Auth Model section. + +**A Hermes dashboard app crash-loops in PM2.** Check `pm2 logs hermes-`. If you +see `✗ --skip-build was passed but no web dist found`, the dashboard UI was never built +on this machine. Build it once with `cd ~/.hermes/hermes-agent/web && npm install && +npm run build`, then `pm2 restart hermes-`. See "Adding a Hermes dashboard." + **Tailscale config gets wiped on gateway restart.** The OpenClaw gateway's Tailscale integration is enabled. Disable it in `~/.openclaw/openclaw.json`: From 718a86a57aff5a8ff7c07e9cd44e0c7455e52457 Mon Sep 17 00:00:00 2001 From: TechNickAI Date: Fri, 5 Jun 2026 10:04:42 -0500 Subject: [PATCH 2/3] style(app-router): apply prettier formatting to README Reflow long lines to satisfy the repo's prettier pre-commit hook. No content changes. --- devops/app-router/README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/devops/app-router/README.md b/devops/app-router/README.md index c7a2ddd..cde2d2c 100644 --- a/devops/app-router/README.md +++ b/devops/app-router/README.md @@ -189,7 +189,8 @@ A common app is a Hermes agent's own dashboard. Two gotchas specific to Hermes: ``` This emits `~/.hermes/hermes-agent/hermes_cli/web_dist/`. After that, `--skip-build` - works (and is preferred under PM2 so the process starts instantly without rebuilding). + works (and is preferred under PM2 so the process starts instantly without + rebuilding). 2. **Pin the profile and `HERMES_HOME`.** A dashboard reads sessions from one SQLite DB. For a profile agent, pass `--profile `; for the root agent (cron jobs, no @@ -254,9 +255,9 @@ 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`. -**The session cookie is `Secure`, so password-protected apps only work over HTTPS.** -The cookie is set with the `Secure` attribute, which means browsers (and `curl`) will -refuse to send it back over plain `http://`. If you front the router with a plain-HTTP +**The session cookie is `Secure`, so password-protected apps only work over HTTPS.** The +cookie is set with the `Secure` attribute, which means browsers (and `curl`) will refuse +to send it back over plain `http://`. If you front the router with a plain-HTTP Tailscale Serve listener (e.g. `tailscale serve --http=`), login will appear to succeed (the POST returns a 303) but every subsequent request gets bounced back to the login page because the cookie never returns. Always expose the router over an **HTTPS** @@ -338,14 +339,15 @@ layer. **Login succeeds (303) but every page bounces back to the login screen.** The session cookie is `Secure` and you're serving over plain HTTP, so the browser never sends the -cookie back. Expose the router over HTTPS instead — `tailscale serve --https= -http://127.0.0.1:8080` (tailnet-only) or the default `:443` funnel. See the note in the -Auth Model section. +cookie back. Expose the router over HTTPS instead — +`tailscale serve --https= http://127.0.0.1:8080` (tailnet-only) or the default +`:443` funnel. See the note in the Auth Model section. **A Hermes dashboard app crash-loops in PM2.** Check `pm2 logs hermes-`. If you see `✗ --skip-build was passed but no web dist found`, the dashboard UI was never built -on this machine. Build it once with `cd ~/.hermes/hermes-agent/web && npm install && -npm run build`, then `pm2 restart hermes-`. See "Adding a Hermes dashboard." +on this machine. Build it once with +`cd ~/.hermes/hermes-agent/web && npm install && npm run build`, then +`pm2 restart hermes-`. See "Adding a Hermes dashboard." **Tailscale config gets wiped on gateway restart.** The OpenClaw gateway's Tailscale integration is enabled. Disable it in `~/.openclaw/openclaw.json`: From 6fcc8c5c9eb6f9ca77cdadc1ae7830e687ed3644 Mon Sep 17 00:00:00 2001 From: TechNickAI Date: Mon, 15 Jun 2026 23:10:07 -0500 Subject: [PATCH 3/3] docs(app-router): make HTTPS serve fix persistent Codex correctly noted that the HTTPS Tailscale Serve command needs --bg so the route persists outside the foreground shell. Add --bg in both the Auth Model note and matching troubleshooting entry. --- devops/app-router/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/devops/app-router/README.md b/devops/app-router/README.md index cde2d2c..b5b4464 100644 --- a/devops/app-router/README.md +++ b/devops/app-router/README.md @@ -261,9 +261,9 @@ to send it back over plain `http://`. If you front the router with a plain-HTTP Tailscale Serve listener (e.g. `tailscale serve --http=`), login will appear to succeed (the POST returns a 303) but every subsequent request gets bounced back to the login page because the cookie never returns. Always expose the router over an **HTTPS** -door — `tailscale serve --https= http://127.0.0.1:8080` for tailnet-only, or the -default `:443` HTTPS funnel. Open (no-password) apps work fine over HTTP because they -have no cookie to lose. +door — `tailscale serve --bg --https= http://127.0.0.1:8080` for tailnet-only, or +the default `:443` HTTPS funnel. Open (no-password) apps work fine over HTTP because +they have no cookie to lose. ## Fronting OpenClaw Webhooks (optional) @@ -340,8 +340,8 @@ layer. **Login succeeds (303) but every page bounces back to the login screen.** The session cookie is `Secure` and you're serving over plain HTTP, so the browser never sends the cookie back. Expose the router over HTTPS instead — -`tailscale serve --https= http://127.0.0.1:8080` (tailnet-only) or the default -`:443` funnel. See the note in the Auth Model section. +`tailscale serve --bg --https= http://127.0.0.1:8080` (tailnet-only) or the +default `:443` funnel. See the note in the Auth Model section. **A Hermes dashboard app crash-loops in PM2.** Check `pm2 logs hermes-`. If you see `✗ --skip-build was passed but no web dist found`, the dashboard UI was never built