hyperhive/nix/host-modules/hive-gateway/error-pages.nix
atlas 275d502639 feat(3189): the sso vhost serves a themed page instead of a bare 502
A dead authelia upstream almost always means "no users yet" — authelia
treats an empty user store as a fatal startup error, so an enabled but
unbootstrapped swarm crash-loops behind a vhost that is working
perfectly. nginx's default 502 says the opposite: it points at the
proxy, which is the one component that is fine.

Adds `ssoUnavailable` to the shared error-page set and wires it on the
authelia vhost the same way the per-agent blocks wire
`__hive_agent_unreachable`: `proxy_intercept_errors on` plus an internal
location serving the static page.

The page leads with the bootstrap command rather than burying it under
an explanation, and names the container journal as the fallback for the
cases where users are not the problem. Same Catppuccin template as its
siblings, so this costs no new styling.
2026-08-12 10:29:16 +02:00

89 lines
3.7 KiB
Nix

# Static error/help pages the gateway serves for routes it has
# special-cased, all rendered from one Catppuccin-styled template.
# Useful pages instead of nginx's default 404/502 — see
# `docs/gateway.md::Per-agent error pages` for the design rationale +
# page-vs-status semantics. Consumed by ./vhosts.nix.
{ pkgs }:
let
mkPage =
{
name,
title,
accent,
body,
}:
pkgs.writeText "hive-gateway-${name}.html" ''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${title} 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: ${accent}; font-size: 1.5rem; margin: 0 0 0.5rem; }
p { max-width: 36rem; margin: 0.5rem auto; color: #a6adc8; }
code { background: #313244; color: #f5c2e7; padding: 0.1rem 0.35rem; border-radius: 0.2rem; font-size: 0.92em; }
pre { background: #181825; color: #cdd6f4; text-align: left; display: inline-block; padding: 0.75rem 1.25rem; border-radius: 0.4rem; margin: 0.75rem 0; font-size: 0.88em; line-height: 1.6; }
a { color: #89b4fa; }
.hint { color: #a6adc8; font-size: 0.9em; margin-top: 1.5rem; }
</style>
</head>
<body>
<h1> ${title}</h1>
${body}
</body>
</html>
'';
in
{
notFound = mkPage {
name = "agent-not-found";
title = "agent not found";
accent = "#cba6f7";
body = ''
<p>No agent matches the requested <code>/agent/&lt;name&gt;/</code> path on this hive.</p>
<p>Operator: check the agent name in <a href="/">the dashboard</a>.</p>
'';
};
unreachable = mkPage {
name = "agent-unreachable";
title = "agent unreachable";
accent = "#f9e2af";
body = ''
<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>
'';
};
# Shown when the authelia vhost's upstream refuses the connection.
# Leads with the bootstrap because that is overwhelmingly the cause:
# authelia treats an empty user store as a FATAL startup error, so an
# enabled-but-unbootstrapped swarm crash-loops behind a vhost that is
# working perfectly, and the raw 502 points at the proxy instead.
ssoUnavailable = mkPage {
name = "sso-unavailable";
title = "sso unavailable";
accent = "#f9e2af";
body = ''
<p>The swarm's identity provider isn't answering. The gateway is fine nothing is listening behind it.</p>
<p class="hint">Most likely: <strong>no users exist yet.</strong> Authelia refuses to start with an empty user store, so it never finishes booting. Add the first account on the host running it:</p>
<pre>swarmctl user add &lt;username&gt; \
--display-name &lt;Name&gt; --email &lt;addr&gt; --group admins</pre>
<p class="hint">Otherwise check the container: <code>journalctl -M swarm-authelia -u authelia-swarm</code>. This page recovers on reload once the provider is up.</p>
'';
};
unauthorized = mkPage {
name = "unauthorized";
title = "unauthorized";
accent = "#f38ba8";
body = ''
<p>This hive is protected by HTTP Basic auth. Valid credentials are required.</p>
<p class="hint">Operator: add a user with <code>hivectl gateway create-user</code>:</p>
<pre>hivectl gateway create-user \
&lt;username&gt; --password-stdin</pre>
<p class="hint">Then reload your browser and enter the credentials when prompted.</p>
'';
};
}