Skip to content

fix(servapps): HostChip status dot reads the real HTTP status - #582

Open
madejackson wants to merge 1 commit into
azukaar:unstablefrom
aseracorp:hostchip-fixes
Open

madejackson wants to merge 1 commit into
azukaar:unstablefrom
aseracorp:hostchip-fixes

Conversation

@madejackson

@madejackson madejackson commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes the HostChip reachability indicator (the colored dot next to a service's URL). It used a no-cors HEAD fetch, whose response is opaque — the browser hides the status entirely, so the dot always resolved as "online". A proxied 502 (e.g. a wrong downstream port) or a dead backend therefore still showed green.

This PR makes the probe read the real HTTP status and classify it correctly, adds the server-side CORS/CORP handling the cross-origin probe needs (including for multi-subdomain + OIDC setups), and shows a grey dot when the container isn't running.

Changes

Client — HostChip (client/src/components/hostChip.jsx)

  • Probe now uses mode: 'cors' (not no-cors) so the browser exposes the actual status; cache: 'no-store' bypasses stale cached responses; redirect: 'manual' keeps 3xx login redirects from being followed into a CORS failure (e.g. a data: URL).
  • Status classification (binary):
    • 🟢 green — 2xx/3xx (incl. cross-origin opaqueredirect login redirects) and 401, 403, 405, 407, 429, 511 — the reverse proxy answered while refusing this caller; the host is up.
    • 🔴 red — everything else (404, 408, 5xx, …) and network errors.
  • Grey dot when the container isn't running (no probe fired) — container prop plumbed from the servapps list and container overview.
  • Uses the shared isContainerRunning helper from client/src/utils/container-status.jsx.

New util — client/src/utils/container-status.jsx

  • Canonical container-status helpers (getContainerDisplayStatus, rankDisplayStatus, isContainerRunning) so HostChip and the health-aware status badges PR share one implementation. This file is byte-identical with PR feat(servapps): health-aware status badges #581's copy, so the two PRs merge cleanly regardless of order.

Server — proxy (src/proxy/routeTo.go)

  • On HEAD responses, echo the request Origin in Access-Control-Allow-Origin (with Vary: Origin) so the browser can read the probe status; relax a backend-sent Cross-Origin-Resource-Policy on HEAD only.
  • When header hardening is on, strip backend COOP/COEP/CORP headers so cross-origin subresource fetches (like the probe) aren't blocked by apps on other subdomains.
  • When hardening is off, relax CORP to cross-origin on HEAD only (app headers preserved for real traffic).

Server — middleware + router (src/utils/middleware.go, src/httpServer.go)

  • Add Vary: Origin to CORSHeader/PublicCORS so origin-dependent responses are never cached and served to another origin.
  • Multi-subdomain + OIDC support: originUnderDomain + HeadProbeCORS + CORSHeader origin-echo, scoped to the Cosmos domain only (hostname or subdomains). This makes the UI readable on a different subdomain than the apps, and makes the OpenID auth-gate 302 (produced inside tokenMiddleware, before the per-route CORSHeader) readable by the browser.

Test Plan

  • go build ./src passes.
  • Client bundles cleanly (esbuild).
  • Verified mergeability with the health-aware status badges PR (feat(servapps): health-aware status badges #581) in both merge orders (no conflicts), before and after the CORS commit.

Notes

  • No server-side ping endpoint is introduced — the probe stays fully client-side.
  • All CORS additions are restricted to origins under the configured Cosmos hostname; third-party origins are never allowed.

@cla-bot cla-bot Bot added the cla-signed label Aug 30, 2026
madejackson added a commit to aseracorp/Cosmos-Server that referenced this pull request Aug 30, 2026
…r#582, drop extra CORS middleware

Rebase unstable's HostChip / servapps / proxy code onto the same content as
the two upstream PRs (hostchip-fixes azukaar#582 and health-aware status badges
azukaar#581), while keeping every unrelated fork commit in place.

- hostChip.jsx: identical to PR azukaar#582 (cors HEAD probe, no-store, redirect
  manual, binary status classification; container grey-dot gating).
- container-status.jsx / overview.jsx / servapps.jsx: merged PR azukaar#581+azukaar#582
  content (isContainerRunning shared helper, health-aware badges,
  container={app.app} / container={containerInfo}).
- routeTo.go / middleware.go: PR azukaar#582 versions (HEAD ACAO echo, CORP relax,
  Vary: Origin).
- Remove the fork's extra CORS layer (HeadProbeCORS top-level middleware +
  originUnderDomain CORS echo) to test unstable without it. These remain
  recoverable on backup/unstable-cors-full.
@madejackson

Copy link
Copy Markdown
Contributor Author

Added cross-origin / OIDC support (commit c31fea1).

The original PR covered the same-origin/path-prefix case. HostChip probes and the Cosmos UI can also run on a different subdomain than the proxied apps, and OIDC-protected apps redirect to the OpenID login inside tokenMiddleware — before the per-route CORSHeader runs — so those 302s carried no Access-Control-Allow-Origin and the browser blocked them (CORS failure).

New changes (all scoped to the Cosmos domain, so third-party origins are never allowed):

  • originUnderDomain — accept only origins equal to the Cosmos hostname or a subdomain of it.
  • CORSHeader — echo the request Origin (when under the Cosmos domain) so host-based app routes (app.*.example.com) are readable from a UI on another subdomain.
  • HeadProbeCORS — top-level middleware that re-asserts Access-Control-Allow-Origin = requesting origin at write time (via a headProbeWriter wrapper), covering auth-gate 302 redirects and anything produced before CORSHeader.
  • Wired HeadProbeCORS into the main router (httpServer.go).

Mergeability with the health-aware status badges PR (#581) remains clean in both merge orders (re-verified after this commit).

@madejackson

Copy link
Copy Markdown
Contributor Author

Added TCP/UDP socket-proxy handling (commit 018b8fa).

Raw TCP/UDP socket proxies (e.g. 0.0.0.0:32400, Mode PROXY without an HTTP target) have no HTTP layer, so an HTTP HEAD probe is meaningless and the dot never turned green. HostChip now detects IsRouteSocketProxy(route) and shows green whenever the container is running, without firing a bogus fetch.

@madejackson

madejackson commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Added a follow-up fix as the PR's latest commit (db31318): the headProbeWriter introduced here only embeds http.ResponseWriter and defines Write/WriteHeader, so it does not satisfy http.Flusher. The streaming routes (create service, image pull, container update) do flusher, ok := w.(http.Flusher) and call flusher.Flush() per line; with the wrapper in place that assertion fails and progress logs stop streaming line-by-line — output arrives buffered in chunks (or the route errors with Streaming unsupported!). Browsers send an Origin header on all POSTs (even same-origin), so every create/update is affected.

The commit adds Flush() (forwards to the inner writer when it implements http.Flusher, same pattern as SmartResponseWriter.Flush), Hijack() (keeps WebSocket/terminal upgrades working through the wrapper), and Unwrap() (lets http.ResponseController / other middleware reach the underlying writer's optional interfaces).

Verified: a copy of the wrapper no longer hides Flusher and the flush propagates to the inner writer; full go build ./src/*.go is clean.

@madejackson

Copy link
Copy Markdown
Contributor Author

Fixed a regression on the routes/URLs page (commit aec8e24).

isContainerRunning(undefined) returned false, so pages that render HostChip without a container prop (the routes/URLs page, route overview, proxyman) showed every dot grey even though the routes were reachable.

Fix: only gate on the container run state (grey dot, no probe) when a container is passed and is not running. When no container is passed there is no run state to gate on, so the URL is probed as usual. Socket proxies still show green when their container (if any) is running.

@madejackson

Copy link
Copy Markdown
Contributor Author

URLs page now behaves the same as ServApps (commit 3e79226).

Previously the URLs/routes pages rendered HostChip without a container, so the dot always probed even when the backing container was stopped (unlike ServApps, which greys out). Now:

  • utils/routes.jsx: new getContainerFromRoute(containers, route) — reverse lookup of the container a SERVAPP route points at (from the route target host).
  • proxyman.jsx (URLs list) + routeConfigPage.jsx (URL detail): fetch API.docker.list() and pass the matched container to HostChip.
  • routeoverview.jsx: accepts and forwards the container prop.

Result: a SERVAPP whose container is not running shows a grey dot on the URLs pages too; non-container routes (STATIC/SPA/PROXY/tunnels) still probe as usual.

@madejackson

Copy link
Copy Markdown
Contributor Author

Added today's fixes from the fork (commit 020d9acd):

  1. Lazy status probe answered by Cosmos, never by the app. After a lazy (dormant) container was updated while dormant and woke up, the probe was forwarded to the app, which answers HEAD / with 404 (many apps don't serve HEAD on the root) — the dot read offline even though the app was up. Now the probe is answered by Cosmos itself:

    • dormant → 503 + X-Cosmos-Container: sleeping
    • running → 200 + X-Cosmos-Container: running
    • Only intercepted when the target is a tracked lazy container (new docker.LazyIsLazy), so regular SERVAPPs still detect a real wrong-port/502.
  2. 501 counts as reachable (green). A 501 Not Implemented means the app is up and answered the probe, it just doesn't implement the HEAD/online check — a successful connection.

  3. Client: any X-Cosmos-Container probe answer (sleeping or running) is authoritative/online; falls back to status-code classification only when there's no probe header.

Tests updated: a running lazy probe returns 200 + running and never reaches the app.

@madejackson
madejackson force-pushed the hostchip-fixes branch 2 times, most recently from eccc6ee to c69035e Compare October 9, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant