fix(installer): dedicate Tailscale listener port

This commit is contained in:
Bailey Dixon
2026-08-30 19:54:51 -04:00
parent ce63030bcf
commit 460d6cd198
4 changed files with 33 additions and 14 deletions
+8 -6
View File
@@ -734,8 +734,9 @@ cat > "$TS_SHIM_PATH" <<SHIM
#
# Usage:
# hermes-relay-tailscale status
# hermes-relay-tailscale enable [--dashboard-listener-port 443] [--dashboard-target-port 9119] [--api-port 8642] [--no-api]
# hermes-relay-tailscale disable [--dashboard-listener-port 443] [--api-port 8642] [--no-api]
# hermes-relay-tailscale enable [--dashboard-listener-port 10443] [--dashboard-target-port 9119] [--api-port 8642] [--no-api]
# hermes-relay-tailscale disable [--dashboard-listener-port 10443] [--dashboard-target-port 9119] [--api-port 8642] [--no-api]
# hermes-relay-tailscale enable --dashboard-listener-port 443 # advanced override; only when :443 is free
# hermes-relay-tailscale enable --port 8767 # explicit legacy/direct Relay
# hermes-relay-tailscale disable --port 8767 # explicit legacy/direct Relay
#
@@ -843,7 +844,7 @@ fi
# ── 7/7 Offer (don't force) Tailscale serve enablement ───────────────────
# ADR 25 — first-class Tailscale helper. The relay stays loopback-bound on
# :8767 while the recommended Tailscale ingress listens on HTTPS :443 and
# :8767 while the recommended Tailscale ingress listens on HTTPS :10443 and
# proxies local Dashboard :9119; Relay rides Dashboard's same-origin plugin
# transport. The optional Hermes API server may also be published on :8642
# for headless compatibility.
@@ -879,7 +880,7 @@ if command -v tailscale >/dev/null 2>&1; then
else
printf "\n"
printf " ${C_DIM}%s${C_RESET} %s\n" "$SYM_INFO" "Tailscale is installed. Publish Dashboard + optional API over your tailnet?"
printf " ${C_DIM}%s${C_RESET} %s\n" "$SYM_INFO" "Runs HTTPS ${C_BOLD}:443${C_RESET} → local Dashboard ${C_BOLD}:9119${C_RESET} (including Relay path), plus ${C_BOLD}:8642${C_RESET} for optional API fallback."
printf " ${C_DIM}%s${C_RESET} %s\n" "$SYM_INFO" "Runs dedicated HTTPS ${C_BOLD}:10443${C_RESET} → local Dashboard ${C_BOLD}:9119${C_RESET} (including Relay path), plus ${C_BOLD}:8642${C_RESET} for optional API fallback."
printf " ${C_DIM}%s${C_RESET} %s\n" "$SYM_INFO" "Loopback services stay private; Tailscale handles TLS + identity."
printf "\n ${C_BOLD}Enable now?${C_RESET} ${C_DIM}[y/N]${C_RESET} "
read -r ts_reply </dev/tty || ts_reply=""
@@ -891,7 +892,7 @@ if command -v tailscale >/dev/null 2>&1; then
if [ -n "$ts_do" ]; then
if "$VENV_PY" -m plugin.relay.tailscale_cli enable >/dev/null 2>&1; then
ok "tailscale serve enabled on HTTPS :443 for local Dashboard :9119, plus optional API :8642"
ok "tailscale serve enabled on HTTPS :10443 for local Dashboard :9119, plus optional API :8642"
info " Check: ${C_BOLD}hermes-relay-tailscale status${C_RESET}"
info " Revoke: ${C_BOLD}hermes-relay-tailscale disable${C_RESET}"
else
@@ -967,7 +968,8 @@ printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-pair --mode auto --prefer tails
printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-status${C_RESET} ${C_DIM}# show live phone state${C_RESET}\n" "$SYM_INFO"
printf "\n"
printf " ${C_BOLD}${C_CYAN}Remote access${C_RESET}\n"
printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-relay-tailscale enable${C_RESET} ${C_DIM}# HTTPS :443 → Dashboard :9119 + optional API :8642${C_RESET}\n" "$SYM_INFO"
printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-relay-tailscale enable${C_RESET} ${C_DIM}# HTTPS :10443 → Dashboard :9119 + optional API :8642${C_RESET}\n" "$SYM_INFO"
printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-relay-tailscale enable --dashboard-listener-port 443${C_RESET} ${C_DIM}# advanced override; only if :443 is free${C_RESET}\n" "$SYM_INFO"
printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-relay-tailscale enable --port 8767${C_RESET} ${C_DIM}# legacy/direct Relay only${C_RESET}\n" "$SYM_INFO"
printf " ${C_DIM}%s${C_RESET} ${C_BOLD}hermes-relay-tailscale status${C_RESET} ${C_DIM}# show served Tailscale ports${C_RESET}\n" "$SYM_INFO"
printf "\n"
@@ -23,16 +23,26 @@ class InstallTailscaleContractTests(unittest.TestCase):
self.script,
)
self.assertIn(
"HTTPS :443 for local Dashboard :9119, plus optional API :8642",
"HTTPS :10443 for local Dashboard :9119, plus optional API :8642",
self.script,
)
def test_installed_shim_marks_direct_relay_as_explicit_legacy(self) -> None:
self.assertIn(
"hermes-relay-tailscale enable [--dashboard-listener-port 443] "
"hermes-relay-tailscale enable [--dashboard-listener-port 10443] "
"[--dashboard-target-port 9119] [--api-port 8642] [--no-api]",
self.script,
)
self.assertIn(
"hermes-relay-tailscale disable [--dashboard-listener-port 10443] "
"[--dashboard-target-port 9119] [--api-port 8642] [--no-api]",
self.script,
)
self.assertIn(
"hermes-relay-tailscale enable --dashboard-listener-port 443 "
"# advanced override; only when :443 is free",
self.script,
)
self.assertIn(
"hermes-relay-tailscale enable --port 8767 # explicit legacy/direct Relay",
self.script,
@@ -48,7 +58,12 @@ class InstallTailscaleContractTests(unittest.TestCase):
)[1].split(
'${C_BOLD}${C_CYAN}Self-setup / troubleshoot${C_RESET}', 1
)[0]
self.assertIn("HTTPS :443 → Dashboard :9119 + optional API :8642", remote_access)
self.assertIn("HTTPS :10443 → Dashboard :9119 + optional API :8642", remote_access)
self.assertIn(
"enable --dashboard-listener-port 443${C_RESET} "
"${C_DIM}# advanced override; only if :443 is free",
remote_access,
)
self.assertIn("legacy/direct Relay only", remote_access)
self.assertNotIn("publish relay :8767", remote_access.lower())
self.assertNotIn("--https=9119", remote_access)
+2 -1
View File
@@ -40,8 +40,9 @@ class PairCliSurfaceTests(unittest.TestCase):
).read_text(encoding="utf-8")
self.assertIn("--dashboard-url", skill)
self.assertIn("Tailscale Serve normally exposes HTTPS `443`", skill)
self.assertIn("Tailscale Serve normally exposes dedicated HTTPS `10443`", skill)
self.assertIn("local Dashboard on `9119`", skill)
self.assertIn("Listener `443` is an advanced explicit override", skill)
self.assertIn("LAN/raw-tailnet Dashboard candidates on\n `9119`", skill)
self.assertIn("--legacy-direct-relay", skill)
self.assertIn("does not imply or expose public port `8767`", skill)
+5 -4
View File
@@ -36,7 +36,7 @@ Operators with the Hermes dashboard open can also mint the same QR from the web
1. **Hermes-Relay plugin installed into the Hermes venv.** Verify by running `python -m plugin.pair --help` — if it errors with `ModuleNotFoundError: No module named 'plugin'`, install it first: `pip install -e <path-to-hermes-relay-repo>`.
2. **Hermes API server reachable** on `API_SERVER_HOST:API_SERVER_PORT` (default `127.0.0.1:8642`). `plugin.pair` auto-reads this from `~/.hermes/config.yaml` → `~/.hermes/.env` → env vars → defaults.
3. **Relay server running** on `RELAY_HOST:RELAY_PORT` (default `0.0.0.0:8767`) if the user wants terminal/bridge channels. The Relay may stay host-internal: current Android pairing normally reaches it through the Dashboard's same-origin plugin transport. Tailscale Serve normally exposes HTTPS `443` and proxies the host-local Dashboard on `9119`; a raw LAN/tailnet route may reach `9119` directly. Without a live relay, the QR will configure chat only.
3. **Relay server running** on `RELAY_HOST:RELAY_PORT` (default `0.0.0.0:8767`) if the user wants terminal/bridge channels. The Relay may stay host-internal: current Android pairing normally reaches it through the Dashboard's same-origin plugin transport. Tailscale Serve normally exposes dedicated HTTPS `10443` and proxies the host-local Dashboard on `9119`; a raw LAN/tailnet route may reach `9119` directly. Listener `443` is an advanced explicit override only when it is free. Without a live relay, the QR will configure chat only.
4. **Host is Linux or macOS.** The relay uses a real PTY backend, which is POSIX-only. Windows hosts can generate API-only QRs but the terminal channel will not work.
## Procedure
@@ -60,9 +60,10 @@ Operators with the Hermes dashboard open can also mint the same QR from the web
A trusted configured `HERMES_DASHBOARD_PUBLIC_URL` / Dashboard public URL is
also suitable. If no exact origin is available, omit the flag; the pair
backend will construct conventional LAN/raw-tailnet Dashboard candidates on
`9119`, or advertise the HTTPS `443` listener when Tailscale Serve maps it to
local Dashboard `9119`. Never substitute the API server URL or infer public
port `8767`.
`9119`, or advertise the HTTPS `10443` listener when Tailscale Serve maps it
to local Dashboard `9119`. HTTPS `443` is used only after the operator
explicitly selects that advanced listener and confirms it is free. Never
substitute the API server URL or infer public port `8767`.
If `python` resolves to the wrong interpreter (plugin not found), use the Hermes venv explicitly: