nix/hive-gateway: static not-found + unreachable pages for /agent/<name>/ (#755)
mara on #755: "e.g. /agent/name should show an error page stating that the agent could not be found if missing in json or that it is not reachable if we get a connection error. we dont want a fully generic fallback, only for routes already special cased in the nginx config." Adds two static HTML pages built at deploy time via `pkgs.runCommand "hyperhive-agent-error-pages"`: - **not-found.html** — served when `/agent/<unknown>/...` hits the bare `/agent/` catch-all. The catch-all `return 404`s, and `error_page 404 = /__hive_agent_not_found` rewrites to the static page. - **unreachable.html** — served when `/agent/<known>/...` proxy_pass to the harness returns 502 / 503 / 504. `proxy_intercept_errors on` + `error_page 502 503 504 = /__hive_agent_unreachable` on each per-agent location block rewrites to the static page. Mechanics: - `agentErrorPagesDir` (in the `let` block) is a `runCommand` that emits two HTML files using a `<<EOF` heredoc — no template engine needed. - Two `internal` nginx locations (`= /__hive_agent_not_found`, `= /__hive_agent_unreachable`) `alias` the exact files. `internal` keeps the URIs unreachable from direct operator request — only nginx's own error-handling can hit them. - Per-agent location blocks pick up the `error_page` directive through the existing `lib.mapAttrs'` over `agentPortsTable`. No per-agent generated content; same static page for all. - `/agent/` catch-all generates from a tiny optionalAttrs alongside the per-agent block — both are no-op when the agent table is empty (matches the pre-#15 shape). Pages: minimal inline CSS, catppuccin palette matching the dashboard (`#1e1e2e` bg, `#cdd6f4` text, `#cba6f7` not-found heading, `#f9e2af` unreachable heading). No frontend-dist dependency — render even when hive-c0re is down. Both link back to `/`. Per mara's "only for routes already special cased" — scope stays narrow. Forge / matrix / fluffychat keep nginx defaults; extending the custom-error pattern to other vhosts is a separate follow-up if/when needed. Verified: - nginx location attrset has `["/", "/agent/", "= /__hive_agent_not_found", "= /__hive_agent_unreachable"]` - container toplevel builds clean (`nixos-system-hive-gateway-26.05pre-git`) - `docs/gateway.md::Per-agent error pages` section captures the design + rationale + intentional narrowness Closes #755.
This commit is contained in:
parent
ba5a6181fc
commit
24775845a3
2 changed files with 123 additions and 2 deletions
|
|
@ -132,3 +132,36 @@ is here for state + systemd-unit isolation, not network isolation.
|
|||
State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/`
|
||||
and survives container restart / host reboot. To wipe, destroy the
|
||||
container.
|
||||
|
||||
## Per-agent error pages
|
||||
|
||||
`/agent/<name>/` requests hit two failure modes; both get static
|
||||
HTML pages instead of nginx's default error chrome (#755):
|
||||
|
||||
- **Agent not found** (`/agent/<unknown>/...`) — name isn't in
|
||||
`agentPortsTable`. nginx's prefix match falls back to the bare
|
||||
`/agent/` catch-all, which `return 404`s and `error_page 404` rewrites
|
||||
to `/__hive_agent_not_found` → serves `not-found.html` with a link
|
||||
back to the dashboard.
|
||||
|
||||
- **Agent unreachable** (`502 / 503 / 504` from `proxy_pass`) — the
|
||||
per-agent harness isn't responding (container restarting, crash
|
||||
recovery, etc.). `proxy_intercept_errors on` + `error_page 502 503
|
||||
504 = /__hive_agent_unreachable` rewrites to `unreachable.html`.
|
||||
|
||||
Both pages are built at deploy time via `pkgs.runCommand` (one nix
|
||||
derivation `hyperhive-agent-error-pages` with `not-found.html` +
|
||||
`unreachable.html` inside) and served via two `internal` nginx
|
||||
locations with `alias` to the exact file. `internal` keeps the
|
||||
files from being directly request-able by operators — only nginx's
|
||||
own error-handling can reach them.
|
||||
|
||||
Page styling: minimal inline CSS matching the dashboard's catppuccin
|
||||
palette (`#1e1e2e` bg, `#cdd6f4` text, `#cba6f7` heading). No
|
||||
dependencies on the frontend dist — these pages render even when
|
||||
hive-c0re itself is down.
|
||||
|
||||
Scope is intentionally narrow per mara on #755: "only for routes
|
||||
already special cased in the nginx config". Other gateway routes
|
||||
(forge / matrix / fluffychat) get nginx defaults — extending the
|
||||
custom-error pattern there is a separate follow-up.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,56 @@ let
|
|||
{ }
|
||||
else
|
||||
builtins.fromJSON (builtins.readFile cfg.agentPortsFile);
|
||||
|
||||
# Static error pages for `/agent/<name>/` mishaps (#755). Mara's
|
||||
# call: useful pages instead of nginx's default 404/502 for routes
|
||||
# we've already special-cased. See `docs/gateway.md::Per-agent
|
||||
# error pages` for the design rationale + page-vs-status semantics.
|
||||
agentErrorPagesDir = pkgs.runCommand "hyperhive-agent-error-pages" { } ''
|
||||
mkdir -p $out
|
||||
cat > $out/not-found.html <<'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>agent not found ◆ hyperhive</title>
|
||||
<style>
|
||||
body { background: #1e1e2e; color: #cdd6f4; font: 14px/1.5 -apple-system, system-ui, sans-serif; margin: 0; padding: 4rem 1rem; text-align: center; }
|
||||
h1 { color: #cba6f7; font-size: 1.5rem; margin: 0 0 0.5rem; }
|
||||
p { max-width: 32rem; margin: 0.5rem auto; color: #a6adc8; }
|
||||
code { background: #313244; color: #f5c2e7; padding: 0.1rem 0.35rem; border-radius: 0.2rem; }
|
||||
a { color: #89b4fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>◆ agent not found</h1>
|
||||
<p>No agent matches the requested <code>/agent/<name>/</code> path on this hive.</p>
|
||||
<p>Operator: check the agent name in <a href="/">the dashboard</a> — the gateway picks up new agents on the next <code>nixos-rebuild switch</code>.</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
cat > $out/unreachable.html <<'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>agent unreachable ◆ hyperhive</title>
|
||||
<style>
|
||||
body { background: #1e1e2e; color: #cdd6f4; font: 14px/1.5 -apple-system, system-ui, sans-serif; margin: 0; padding: 4rem 1rem; text-align: center; }
|
||||
h1 { color: #f9e2af; font-size: 1.5rem; margin: 0 0 0.5rem; }
|
||||
p { max-width: 32rem; margin: 0.5rem auto; color: #a6adc8; }
|
||||
code { background: #313244; color: #f5c2e7; padding: 0.1rem 0.35rem; border-radius: 0.2rem; }
|
||||
a { color: #89b4fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>◆ agent unreachable</h1>
|
||||
<p>The agent's harness web server isn't responding. Container restarting, or the agent crashed.</p>
|
||||
<p>Operator: <a href="/">dashboard</a> → check the container status / journal; the page will recover on retry once the harness is back up.</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Single nginx in front of every hyperhive web surface — dashboard,
|
||||
|
|
@ -268,8 +318,11 @@ in
|
|||
# per entry in `agentPortsTable`. Trailing-slash pair
|
||||
# strips the prefix; `X-Forwarded-Prefix` lets the
|
||||
# harness build absolute URLs when relative isn't
|
||||
# enough. See `docs/gateway.md` for the vhost map
|
||||
# + tuning rationale.
|
||||
# enough. `proxy_intercept_errors` + `error_page` rewrite
|
||||
# upstream 502/503/504 (container down / restarting) to
|
||||
# the static `unreachable.html` instead of nginx's
|
||||
# default Bad Gateway page (#755). See
|
||||
# `docs/gateway.md` for the vhost map + tuning.
|
||||
lib.mapAttrs' (name: port: {
|
||||
name = "/agent/${name}/";
|
||||
value = {
|
||||
|
|
@ -279,9 +332,44 @@ in
|
|||
proxy_set_header X-Forwarded-Prefix /agent/${name};
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 1d;
|
||||
proxy_intercept_errors on;
|
||||
error_page 502 503 504 = /__hive_agent_unreachable;
|
||||
'';
|
||||
};
|
||||
}) agentPortsTable
|
||||
//
|
||||
# `/agent/` catch-all (#755): hits when an operator
|
||||
# requests `/agent/<unknown>/...` — a name not in
|
||||
# `agentPortsTable`. Without this it falls through to
|
||||
# `/` (c0re dashboard upstream) which returns 404
|
||||
# with no useful context. Custom 404 page instead.
|
||||
{
|
||||
"/agent/" = {
|
||||
extraConfig = ''
|
||||
error_page 404 = /__hive_agent_not_found;
|
||||
return 404;
|
||||
'';
|
||||
};
|
||||
# Internal static-file locations the error_page
|
||||
# directives above point at. `internal` keeps
|
||||
# operators from hitting the file directly (only
|
||||
# nginx's error-handling can reach it); `alias`
|
||||
# serves the exact file regardless of request URI.
|
||||
"= /__hive_agent_not_found" = {
|
||||
extraConfig = ''
|
||||
internal;
|
||||
alias ${agentErrorPagesDir}/not-found.html;
|
||||
default_type text/html;
|
||||
'';
|
||||
};
|
||||
"= /__hive_agent_unreachable" = {
|
||||
extraConfig = ''
|
||||
internal;
|
||||
alias ${agentErrorPagesDir}/unreachable.html;
|
||||
default_type text/html;
|
||||
'';
|
||||
};
|
||||
}
|
||||
// {
|
||||
# Everything else proxies to hive-c0re. Upgrade
|
||||
# headers stay set so SSE (`/dashboard/stream`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue