← Commit history

Delete skills/hydrogen-port-watcher

Kyle Bergstedt ·bdf5a5d045 · ·parent 4ab679b
2 files changed −262
skills/hydrogen-port-watcher/._SKILL.mddeleted
⋯ 1 unchanged line ⋯
skills/hydrogen-port-watcher/SKILL.mddeleted−262
@@ -1,262 +0,0 @@-----name: hydrogen-port-watcher-description: >-  Dynamic port forwarding for Adom Hydrogen. A daemon inside the-  Adom-Workspace machine watches for new TCP listeners (OAuth callbacks from VS-  Code extensions like Codex, Copilot, GitLens) and notifies Hydrogen to create-  host-side TCP proxies. Covers the cases macOS/Lima port exposure can't:-  `127.0.0.1`-only listeners and exposure gaps. Makes localhost:{port}-  on the Mac reach the machine. Extensions just work — no manual setup.-  Trigger words: port forward, dynamic port, OAuth callback, Codex auth,-  extension auth, port mapping, localhost port, port watcher, port proxy.------# Hydrogen Port Watcher — Dynamic Port Forwarding--> runtime (the retired Docker runtime.--## Why the daemon is still here on the machine--When a service inside the Adom-Workspace machine binds `0.0.0.0:<port>`, Lima's-vz networking makes it reachable from the Mac at `localhost:<sameport>` — so you-might think a port watcher is unnecessary. It isn't. The daemon is **RETAINED**-because that `0.0.0.0` exposure has two gaps the watcher closes:--1. **`127.0.0.1`-only listeners.** vz does NOT mirror loopback, so a-   loopback-only listener inside the machine is never exposed on the Mac. VS Code-   extensions (Codex, Copilot, GitLens) start OAuth callback servers that-   frequently bind `127.0.0.1` — the browser redirects to-   `localhost:{port}/auth/callback?code=...` but the Mac never had that port,-   so it doesn't exist.-2. **Exposure lag / wedge.** macOS/Lima port exposure can lag or break-   even for `0.0.0.0` listeners (a real Hydrogen-logged failure mode:-   "code-server alive in the machine but the Mac can't reach 127.0.0.1:7380"). The-   watcher gives Hydrogen a reliable host-side proxy that doesn't depend on Lima's-   automatic exposure.--## The Solution--A lightweight daemon inside the machine monitors `/proc/net/tcp` for new-listening ports. When one appears (that wasn't there at boot and isn't a known-service), it notifies Hydrogen's control API. Hydrogen creates a host-side TCP proxy on-that same port, forwarding through code-server's `/proxy/{port}` to reach the-machine. The browser redirect just works — even for loopback-only callback-servers Lima wouldn't have exposed.--## Architecture--```-Adom-Workspace machine:             macOS Host:-┌─────────────────────┐            ┌──────────────────────┐-│ Codex creates server │           │ Hydrogen Control API       │-│ on 127.0.0.1:1455    │           │ (:47084)             │-│ (loopback, no expose)│           │                      │-│ port-watcher.sh      │──POST──→ │ /port-forward        │-│ detects port 1455    │  open     │ creates TCP proxy    │-│                      │           │ on localhost:1455     │-│ code-server          │           │                      │-│ /proxy/1455/ ←───────┼───────── │ proxy forwards here  │-└─────────────────────┘            └──────────────────────┘-                                          ↑-                                   Browser redirects to-                                   localhost:1455/callback-```--## What Gets Forwarded (and what doesn't)--**FORWARDED (temporary, appeared after boot):**-- OAuth callback ports from extensions (Codex, Copilot, etc.) — especially-  `127.0.0.1`-only ones Lima cannot expose-- Any new TCP listener that wasn't running when the machine started-- Auto-expires after 5 minutes (OAuth callbacks are fast)--**NOT FORWARDED (known services, stay behind /proxy/{port} or 0.0.0.0 exposure):**-- code-server (8080 inside the machine; exposed as 7380 mapping/`0.0.0.0`)-- relay WS/HTTP (8765, 8766)-- adom-vscode (8821)-- shotlog (8820)-- Any port in the KNOWN_PORTS list-- Any port that was listening at machine boot time-- Any port ≤ 1023--This keeps the local workspace machine behaving like cloud Hydrogen — known-services use `/proxy/{port}` (or macOS/Lima exposure for `0.0.0.0` binds), and-only short-lived / loopback-only OAuth ports get a dedicated host proxy.--## API Endpoints--### POST /port-forward-Create or remove a host-side TCP proxy. Called by the machine's watcher.--```json-{"port": 1455, "action": "open"}   // Create proxy-{"port": 1455, "action": "close"}  // Remove proxy-```--### GET /port-forward-List currently forwarded ports.--```json-{"ok": true, "forwarded_ports": [1455], "count": 1}-```--## The Watcher Daemon--`scripts/port-watcher.sh` runs inside the machine:--1. Polls `/proc/net/tcp` every 2 seconds-2. Diffs against previous scan-3. New port (not in KNOWN_PORTS whitelist) → POST `/port-forward` open-4. Port gone → POST `/port-forward` close-5. Port open > 5 minutes → auto-expire (likely a service, not OAuth)--No boot snapshot — the whitelist is the sole source of truth. Services-like shotlog can start at any time (days/weeks later) and will never-get a host proxy as long as their port is in the whitelist.--The watcher reaches Hydrogen at the `adom-host` gateway plus the control port-(`<adom-host>:<control>/port-forward`). vz does NOT mirror loopback, so the-machine cannot reach Hydrogen at its own `127.0.0.1`; instead the machine's-`/etc/hosts` maps `adom-host` to the gateway, and that's where Hydrogen's host-listener lives. The control port is dynamic per launch; the live control URL-(already pointing at the gateway host) is in `~/.adom/hydrogen-control-url`.--## Installation--The watcher is a bash script (`port-watcher.sh`) deployed into the machine-during setup steps. Installation uses `limactl shell hydrogen-builder` + `nsenter`-into the machine, NOT `docker cp`/`docker exec`:--```bash-# Stage the script into the Lima VM, then install it into the machine via nsenter.-limactl copy port-watcher.sh hydrogen-builder:/tmp/port-watcher.sh-limactl shell hydrogen-builder -- sudo nsenter -t <leader> -a -- bash -lc \-  'cp /tmp/port-watcher.sh /usr/local/bin/port-watcher.sh && chmod +x /usr/local/bin/port-watcher.sh'-# Launch detached inside the machine (as the adom user)-limactl shell hydrogen-builder -- sudo nsenter -t <leader> -a -S 1001 -G 1001 -- bash -lc \-  'nohup bash /usr/local/bin/port-watcher.sh >/tmp/port-watcher.log 2>&1 &'-```--The script parses `/proc/net/tcp` directly in `awk` — it filters listener rows-(state `0A`), splits the hex `local_address`, and converts the port with-`strtonum("0x" …)`. No Python is involved.--## UI: Toast + Port Mappings Dialog--**Toast notification:** When a port is forwarded, Hydrogen shows an info toast:-"Port 1455 forwarded to workspace". When removed: "Port 1455 forwarding stopped".-Powered by the `port-forward-changed` Tauri event.--**Port Mappings dialog:** Accessible from the Adom menu → Admin → "Port Mappings".-Shows exposed `0.0.0.0` ports AND dynamic watcher-created proxies. Dynamic-ports have a "Stop" button. Matches the API Explorer visual style.--## Adding New Known Services--When a new service is added to the machine that runs a persistent TCP server,-add its port to the `KNOWN_PORTS` whitelist string in `port-watcher.sh`-(`scripts/port-watcher.sh:16`):--```bash-KNOWN_PORTS="8080 8765 8766 8821 8820 36035 47084 47083 47085 1420 7380 8770 9000"-```--This prevents it from getting a dedicated host proxy. Services should bind-`0.0.0.0` (exposed by Lima) or use `/proxy/{port}` instead, consistent with web-Hydrogen.--## The forwarding layers--There are distinct forwarding mechanisms. On macOS the `/proc/net/tcp` watcher-above is the **primary** path; the manual endpoints below are the explicit-fallback.--### No automatic OAuth pre-bind on macOS--Hydrogen's macOS webview is **WKWebView**, which exposes no navigation-interception-hook (the same CDP gap that makes `/eval` fire-and-forget on macOS). So there is-no *automatic* zero-latency pre-bind of OAuth callback ports when an OAuth URL-leaves the webview — the **port watcher is the primary path**, with the manual-`POST /start-oauth-proxy` endpoint as the explicit fallback. The-`/start-oauth-proxy` + `/oauth/callback` endpoints themselves are cross-platform-and work the same on macOS.--**Control / callback endpoints** (Hydrogen control API):-- `POST /start-oauth-proxy` — manually pre-bind a callback port (on macOS this is-  the explicit way to get the pre-bind effect).-- `GET /oauth/callback` — Hydrogen's own built-in callback receiver (used by the-  Claude Code PKCE flow); do not call directly.--**Failure mode the AI may see:** if a proxy binds but code-server can't be-reached, Hydrogen returns a styled **502 Bad Gateway** page (dark `#0d1117`-background, teal `#00b8b0` "OAuth callback proxy" heading) reading roughly *"Hydrogen-tried to forward this OAuth callback to the workspace via code-server proxy-(port …/proxy/…), but it failed. The extension that started this OAuth flow may-need its callback port (…) mapped to the host."* Seeing that page in a browser-means the proxy bound but the machine-side proxy hop failed — check the Hydrogen log-for `[oauth-proxy]` lines and that code-server is alive.--### The port watcher (`/proc/net/tcp` daemon — the primary path on macOS)--The bash daemon documented above. On macOS it is the workhorse: it surfaces new-callback ports — including `127.0.0.1`-only callback servers Lima silently won't-expose — and asks Hydrogen to proxy them. Note: when an app has already registered-its port via the adom-port-hints protocol, the watcher skips it (it only owns the-fallback for third-party extensions that don't know Hydrogen exists).--### Manual--`POST /start-oauth-proxy` or `POST /port-forward` (or-`adom-cli port-forward register`) for cases the watcher misses; this is the-explicit pre-bind path on macOS.--## Port Conflicts--If the host port is already taken by another Mac app, the forwarding-fails silently. The OAuth callback will break. Mitigation: extensions-typically pick a different random port on retry. Hydrogen logs the conflict.--## How OAuth Callbacks Work End-to-End--1. Extension (Codex) starts HTTP server on `127.0.0.1:1455` inside the machine-2. port-watcher.sh detects port 1455 (not in boot snapshot, not known) —-   note Lima would NOT have exposed this loopback-only listener-3. Watcher POSTs `{"port":1455,"action":"open"}` to Hydrogen-4. Hydrogen creates TCP proxy: `localhost:1455` → code-server `/proxy/1455/`-5. Extension opens browser with `redirect_uri=http://localhost:1455/callback`-6. User authenticates, browser redirects to `localhost:1455/callback?code=...`-7. Hydrogen's proxy receives the request, forwards into the machine via code-server-8. Extension receives callback, completes auth-9. Extension closes its server, port 1455 disappears-10. Watcher POSTs `{"port":1455,"action":"close"}`, Hydrogen removes proxy--## Debugging--All debugging runs inside the machine via `limactl shell hydrogen-builder -- sudo-nsenter -t <leader> -a -S 1001 -G 1001 -- ...`, NEVER `docker exec`:--```bash-# Check what ports the watcher sees-limactl shell hydrogen-builder -- sudo nsenter -t <leader> -a -S 1001 -G 1001 -- bash -lc "cat /proc/net/tcp | awk '\$4 == \"0A\" { split(\$2, a, \":\"); printf \"%d\\n\", strtonum(\"0x\" a[2]) }' | sort -n"--# Check the watcher's own log-limactl shell hydrogen-builder -- sudo nsenter -t <leader> -a -S 1001 -G 1001 -- cat /tmp/port-watcher.log--# Check what Hydrogen has forwarded (from the Mac host)-curl -sf http://127.0.0.1:47084/port-forward--# Manually forward a port (for testing, from the Mac host)-curl -sf -X POST http://127.0.0.1:47084/port-forward \-  -H Content-Type:application/json \-  -d '{"port":1455,"action":"open"}'--# Check Hydrogen log for port-fwd events-# Look for [port-fwd] entries-```--## Related skills--- `hydrogen-networking` — `0.0.0.0` exposure, reaching the host at the `adom-host`-  gateway (vz does NOT mirror loopback), NEVER do these-- `hydrogen-ports` — bind `0.0.0.0` first; fall back to the port-forward registry for-  `127.0.0.1`-only services or unreliable exposure