Slice 1 of #3202. The listen set, the per-name TLS attrs and the security headers move out of vhosts.nix into ./vhost-lib.nix and are published as `services.hyperhive.gateway.lib` (internal, readOnly). No behaviour change: vhosts.nix consumes the published value, so the rendered vhost tree is identical. The point is the next slice. Today a swarm service's vhost lives in the gateway because only the gateway knows the port pair, the issuer for a name, and the header block. Publishing those three is what lets a service module declare its own vhost without the gateway having to know that service by name.
511 lines
21 KiB
Nix
511 lines
21 KiB
Nix
# nginx virtual-host tree for the gateway: the `_` default server
|
|
# (dashboard, per-agent routing, matrix discovery), the forge, matrix
|
|
# and authelia sub-domain vhosts, and the Accept-header SPA map for the
|
|
# matrix GUI. Pure function — called from ./default.nix with the
|
|
# outer-scope config values as arguments; returns
|
|
# `{ virtualHosts, appendHttpConfig }`.
|
|
{
|
|
lib,
|
|
cfg, # services.hyperhive.gateway
|
|
forgeCfg,
|
|
matrixCfg,
|
|
autheliaCfg, # services.hyperhive.swarm.authelia
|
|
uiCfg, # services.hyperhive.swarm.ui
|
|
controllerCfg, # services.hyperhive.swarm.controller
|
|
hyperhiveDomain,
|
|
dashboardDist,
|
|
swaggerUiTheme, # nix/packages/swagger-ui-theme.nix: has index.html + hyperhive-theme.css
|
|
errorPages, # ./error-pages.nix: { notFound, unreachable, unauthorized, ssoUnavailable }
|
|
gwLib, # `services.hyperhive.gateway.lib` — ./vhost-lib.nix's kit, via the option
|
|
}:
|
|
let
|
|
# The kit's three members, bound to the names this file already used.
|
|
# Read through `gwLib` (the published option) rather than importing
|
|
# ./vhost-lib.nix directly: a service module declaring its own vhost
|
|
# gets the same object, so "the forge vhost listens where the gateway
|
|
# listens" is true by construction and not by review.
|
|
inherit (gwLib) securityHeaders;
|
|
vhostListen = gwLib.listen;
|
|
vhostTlsFor = gwLib.tlsFor;
|
|
|
|
# Public-facing scheme + port-suffix for URLs the gateway
|
|
# mints into responses (well-known JSON, the deprecated
|
|
# `<hive>/matrix/*` 301 redirect, future absolute-URL needs):
|
|
# always `https://<host>` (matrix-spec compliance) — the canonical
|
|
# 443 elides the port. See `docs/gateway.md` ("Self-signed TLS").
|
|
publicScheme = "https";
|
|
publicPort = cfg.httpsPort;
|
|
publicPortSuffix = if publicPort == 443 then "" else ":${toString publicPort}";
|
|
|
|
# Forge sub-domain vhost. `server_name = forge.domain`, proxies
|
|
# all `/` → forgejo. Tuned for git: `client_max_body_size 1G`,
|
|
# `proxy_read_timeout 1h` (multi-GB clones). SSH stays direct on
|
|
# `forge.sshPort`. See `docs/gateway.md`. Empty attrset when the
|
|
# forge isn't behind the gateway.
|
|
forgeVhost = lib.optionalAttrs (forgeCfg.behindGateway or false) {
|
|
"${forgeCfg.domain}" = (vhostTlsFor forgeCfg.domain) // {
|
|
listen = vhostListen;
|
|
extraConfig = securityHeaders;
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:${toString forgeCfg.httpPort}/";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
proxy_buffering off;
|
|
client_max_body_size 1G;
|
|
proxy_read_timeout 1h;
|
|
proxy_send_timeout 1h;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
# Authelia sub-domain vhost. `server_name = authelia.domain`, all of
|
|
# `/` → authelia. Empty attrset unless THIS host runs the container:
|
|
# every hive knows the swarm's `authelia.url`, but only the one
|
|
# serving it may claim the name — a client hive declaring this vhost
|
|
# would answer for a service it does not run.
|
|
#
|
|
# ⚠️ The server name must be exactly `autheliaCfg.domain`, not a
|
|
# near-miss: authelia validates `authelia_url ⊂ session cookie domain`
|
|
# at STARTUP, so a mismatch is a container that refuses to boot rather
|
|
# than a login that misbehaves.
|
|
#
|
|
# ⚠️ And deliberately NO `dashboardAuth` here. That block is the
|
|
# gateway's `auth_basic`; applying it to the SSO provider would put
|
|
# the login page behind the login mechanism it exists to replace.
|
|
autheliaVhost = lib.optionalAttrs autheliaCfg.enable {
|
|
"${autheliaCfg.domain}" = (vhostTlsFor autheliaCfg.domain) // {
|
|
listen = vhostListen;
|
|
extraConfig = securityHeaders;
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:${toString autheliaCfg.port}/";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
proxy_buffering off;
|
|
# authelia decides by the ORIGINAL request, not by the hop it
|
|
# sees — the login redirect and the session cookie's domain
|
|
# both derive from these. Without them every request looks
|
|
# like it arrived at 127.0.0.1 over plain http.
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
proxy_set_header X-Forwarded-Uri $request_uri;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
# A dead upstream here means "not bootstrapped" far more often
|
|
# than "misconfigured proxy", and a bare 502 says the opposite.
|
|
proxy_intercept_errors on;
|
|
error_page 502 503 504 = /__hive_sso_unavailable;
|
|
'';
|
|
};
|
|
locations."= /__hive_sso_unavailable" = {
|
|
extraConfig = ''
|
|
internal;
|
|
alias ${errorPages.ssoUnavailable};
|
|
default_type text/html;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
# Swarm UI vhost — the swarm's front page, on the swarm apex, and the
|
|
# FIRST `auth_request` anywhere in this gateway (everything else is
|
|
# `auth_basic` + htpasswd).
|
|
#
|
|
# ⚠️ `auth_request` answers "is there a session", not "is this an
|
|
# operator". The operator-only part is authelia's `access_control`
|
|
# rule (../swarm-authelia.nix) requiring `group:operators` — agents
|
|
# are getting authelia accounts of their own, and without that rule a
|
|
# session alone would open this page.
|
|
#
|
|
# ⚠️ Failure mode here is LOCKED OUT, not unprotected: a subrequest
|
|
# that wrongly denies takes the whole UI away. That is the reason the
|
|
# redirect target and the header set below are copied from a measured
|
|
# source rather than from an example.
|
|
# ⚠️ `forceSSL`, not `addSSL` like every other vhost — not a hardening
|
|
# preference, the only way this page works at all. authelia answers the
|
|
# auth subrequest for an `http://` target with **400**, and nginx's
|
|
# `auth_request` only understands 2xx/401/403, so a plain-http visit
|
|
# dies as "auth request unexpected status: 400" with no hint a login
|
|
# exists. `vhostListen` binds :80, so without this the door is open on
|
|
# a port the lock cannot work on. Serving forge or matrix over http is
|
|
# merely insecure rather than broken, so they keep `addSSL` and the
|
|
# asymmetry stays local to the vhost whose correctness depends on the
|
|
# scheme. `removeAttrs` because nixos asserts on a vhost declaring both.
|
|
# Shared with every swarm-UI-vhost location below (`/`, `/api/`,
|
|
# `/api/docs/`) — auth_request does not inherit across sibling
|
|
# locations, so each one that should be operator-gated repeats this
|
|
# verbatim rather than only the page itself being protected while its
|
|
# own API and API docs are reachable unauthenticated.
|
|
swarmAuthRequest = ''
|
|
auth_request /__hive_authelia;
|
|
# Captured BEFORE the error_page jump: inside the 401 handler
|
|
# `$request_uri` is the internal one, so building the return
|
|
# link there sends the operator back to the auth subrequest
|
|
# instead of the page they asked for.
|
|
auth_request_set $target_url $scheme://$http_host$request_uri;
|
|
error_page 401 =302 https://${autheliaCfg.domain}/?rd=$target_url;
|
|
'';
|
|
|
|
swarmUiVhost = lib.optionalAttrs uiCfg.enable {
|
|
"${uiCfg.domain}" = (builtins.removeAttrs (vhostTlsFor uiCfg.domain) [ "addSSL" ]) // {
|
|
forceSSL = true;
|
|
listen = vhostListen;
|
|
extraConfig = securityHeaders;
|
|
locations = {
|
|
"/" = {
|
|
root = "${uiCfg.package}";
|
|
extraConfig = ''
|
|
${swarmAuthRequest}
|
|
# SPA: any path the bundle routes client-side is served the
|
|
# entry document rather than a 404 from the filesystem.
|
|
try_files $uri /index.html;
|
|
'';
|
|
};
|
|
# swarm-controller's whole HTTP surface, including the live
|
|
# `/api/openapi.json` spec — proxied untouched (no URI segment
|
|
# after the socket path, same "pass the request through as-is"
|
|
# shape as the per-hive dashboard's own `/api/` proxy) so the
|
|
# path swarm-controller registered a route at is the path
|
|
# nginx forwards, no prefix-stripping to keep in sync by hand.
|
|
"/api/" = {
|
|
proxyPass = "http://unix:${controllerCfg.socketPath}:";
|
|
extraConfig = swarmAuthRequest;
|
|
};
|
|
# Swagger UI: same "nginx hosts the themed dist straight from
|
|
# the store, only /api/openapi.json is dynamic" shape as the
|
|
# per-hive gateway's `swaggerUiLocations` — see that block's
|
|
# comment for why core-equivalent (here, swarm-controller)
|
|
# does not also mount its own copy.
|
|
"= /api/docs" = {
|
|
extraConfig = ''
|
|
return 301 /api/docs/;
|
|
'';
|
|
};
|
|
"/api/docs/" = {
|
|
alias = "${swaggerUiTheme}/";
|
|
extraConfig = ''
|
|
index index.html;
|
|
${swarmAuthRequest}
|
|
'';
|
|
};
|
|
# The subrequest itself. `auth-request` is the implementation
|
|
# name authelia exposes under `/api/authz/`; `/api/verify` is the
|
|
# LEGACY path every older example shows.
|
|
#
|
|
# Header set measured against the pinned binary (4.39.20), not
|
|
# copied: `X-Original-URL` and `X-Original-Method` are present as
|
|
# literals and are what this implementation reads —
|
|
# `X-Forwarded-Uri` does not appear in it at all, so sending it
|
|
# would look like configuration and be dead weight.
|
|
"= /__hive_authelia" = {
|
|
proxyPass = "http://127.0.0.1:${toString autheliaCfg.port}/api/authz/auth-request";
|
|
extraConfig = ''
|
|
internal;
|
|
# A subrequest carries no body, and forwarding one here makes
|
|
# authelia read a payload it will never use.
|
|
proxy_pass_request_body off;
|
|
proxy_set_header Content-Length "";
|
|
proxy_set_header X-Original-Method $request_method;
|
|
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-Host $http_host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# Matrix sub-domain vhost. `server_name = matrixCfg.gatewayHost`.
|
|
# `/_matrix/*` → tuwunel (CORS *, 50M body cap, 1h long-poll
|
|
# timeout). `/` serves fluffychat or 404 if GUI off. nginx
|
|
# longer-prefix-wins puts `/_matrix/` ahead of `/`. See
|
|
# `docs/gateway.md`. Empty attrset when matrix has no gateway host.
|
|
matrixVhost = lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) {
|
|
"${matrixCfg.gatewayHost}" = (vhostTlsFor matrixCfg.gatewayHost) // {
|
|
listen = vhostListen;
|
|
extraConfig = securityHeaders;
|
|
locations = {
|
|
"/_matrix/" = {
|
|
proxyPass = "http://127.0.0.1:${toString matrixCfg.httpPort}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
proxy_buffering off;
|
|
client_max_body_size 50M;
|
|
proxy_read_timeout 1h;
|
|
proxy_send_timeout 1h;
|
|
${securityHeaders}
|
|
add_header Access-Control-Allow-Origin *;
|
|
'';
|
|
};
|
|
}
|
|
// lib.optionalAttrs (matrixCfg.gui.enable) (
|
|
{
|
|
# fluffychat at sub-domain root, SPA-fallback via
|
|
# the Accept-header `$matrix_spa_target` map.
|
|
"/" = {
|
|
alias = "${matrixCfg.gui.package}/";
|
|
extraConfig = ''
|
|
try_files $uri $uri/ $matrix_spa_target =404;
|
|
'';
|
|
};
|
|
}
|
|
// {
|
|
# FluffyChat boot-config pre-fill so the client's
|
|
# `.well-known/matrix/client` lookup hits the
|
|
# right delegation endpoint. `domain` is required, so
|
|
# this is always present.
|
|
"= /config.json" = {
|
|
extraConfig = ''
|
|
default_type application/json;
|
|
return 200 '{"defaultHomeserver":"${hyperhiveDomain}"}';
|
|
'';
|
|
};
|
|
}
|
|
)
|
|
// lib.optionalAttrs (!matrixCfg.gui.enable) {
|
|
"/" = {
|
|
return = "404";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# `<hive>/matrix/*` → 301 → `matrix.<hive>/$1` (legacy deep-link
|
|
# shim during the fluffychat sub-domain move). See `docs/gateway.md`.
|
|
matrixRedirectLocations =
|
|
lib.optionalAttrs (matrixCfg.enable && matrixCfg.gui.enable && matrixCfg.gatewayHost != null)
|
|
(
|
|
let
|
|
target = "${publicScheme}://${matrixCfg.gatewayHost}${publicPortSuffix}";
|
|
in
|
|
{
|
|
"/matrix/" = {
|
|
extraConfig = ''
|
|
rewrite ^/matrix/(.*)$ ${target}/$1 permanent;
|
|
'';
|
|
};
|
|
}
|
|
);
|
|
|
|
# `.well-known/matrix/{client,server}` discovery JSON. Points
|
|
# clients at `matrixCfg.gatewayHost` when set; falls back to direct
|
|
# `<hive>:<httpPort>`. CORS `*` per matrix spec. The `m.server`
|
|
# port-8448 carve-out is documented inline. See `docs/gateway.md`.
|
|
wellKnownLocations = lib.optionalAttrs matrixCfg.enable (
|
|
let
|
|
clientBaseUrl =
|
|
if matrixCfg.gatewayHost != null then
|
|
"${publicScheme}://${matrixCfg.gatewayHost}${publicPortSuffix}"
|
|
else
|
|
"${publicScheme}://${hyperhiveDomain}:${toString matrixCfg.httpPort}";
|
|
# `m.server` is NOT a URL: per the matrix server-server spec
|
|
# (Resolving Server Names) a delegated host with NO port resolves
|
|
# to the federation default 8448 (after the SRV check) — the
|
|
# https-implies-443 rule does NOT apply here. So the port must be
|
|
# explicit even when it's the HTTPS default; `publicPortSuffix`
|
|
# (which drops :443) is right for the client base_url above but
|
|
# wrong for federation delegation. Without this, peers federate to
|
|
# <gatewayHost>:8448 (closed) while the endpoint actually lives on
|
|
# the gateway's 443 vhost. See docs/gateway.md discovery flow.
|
|
serverHostPort =
|
|
if matrixCfg.gatewayHost != null then
|
|
"${matrixCfg.gatewayHost}:${toString publicPort}"
|
|
else
|
|
"${hyperhiveDomain}:${toString matrixCfg.httpPort}";
|
|
in
|
|
{
|
|
"= /.well-known/matrix/client" = {
|
|
extraConfig = ''
|
|
default_type application/json;
|
|
${securityHeaders}
|
|
add_header Access-Control-Allow-Origin *;
|
|
return 200 '{"m.homeserver":{"base_url":"${clientBaseUrl}"}}';
|
|
'';
|
|
};
|
|
"= /.well-known/matrix/server" = {
|
|
extraConfig = ''
|
|
default_type application/json;
|
|
return 200 '{"m.server":"${serverHostPort}"}';
|
|
'';
|
|
};
|
|
}
|
|
);
|
|
|
|
# `/agent/` catch-all 404 + the two internal error-page targets it
|
|
# points at. Per-agent `location /agent/<name>/` blocks live in the
|
|
# runtime-generated `/var/lib/hive-gateway/conf/agents.conf` (included via
|
|
# `extraConfig` on the vhost); nginx longest-prefix-match makes a
|
|
# real `/agent/<name>/` beat this catch-all. `internal` keeps the
|
|
# error pages reachable only through nginx's error handling.
|
|
agentLocations = {
|
|
"/agent/" = {
|
|
extraConfig = ''
|
|
error_page 404 = /__hive_agent_not_found;
|
|
return 404;
|
|
'';
|
|
};
|
|
"= /__hive_agent_not_found" = {
|
|
extraConfig = ''
|
|
internal;
|
|
alias ${errorPages.notFound};
|
|
default_type text/html;
|
|
'';
|
|
};
|
|
"= /__hive_agent_unreachable" = {
|
|
extraConfig = ''
|
|
internal;
|
|
alias ${errorPages.unreachable};
|
|
default_type text/html;
|
|
'';
|
|
};
|
|
};
|
|
|
|
# Shared auth block — separate locations don't inherit auth_basic, so
|
|
# each dashboard location (`/`, `/api/`) needs it or that surface is
|
|
# unauthed. `/webhook/` is intentionally excluded: Forgejo cannot
|
|
# send HTTP Basic credentials with webhook deliveries, and the HMAC
|
|
# secret (`X-Hub-Signature-256`) protects those endpoints instead.
|
|
dashboardAuth = lib.optionalString cfg.auth.enable ''
|
|
auth_basic "${cfg.auth.realm}";
|
|
auth_basic_user_file /var/lib/hive-gateway/conf/gateway.htpasswd;
|
|
# `=401` keeps the status 401 so the login dialog shows; the
|
|
# internal page explains `hivectl gateway create-user`.
|
|
error_page 401 =401 /__hive_auth_unauthorized;
|
|
'';
|
|
|
|
# Dashboard: nginx static-serves the dist, c0re is API-only. Routing
|
|
# is by PATH, never content-type. c0re serves exactly three prefixes —
|
|
# `/api/` (all dashboard data + actions + the SSE streams), `/webhook/`
|
|
# (knowledge push + config-PR approval triggers, HMAC-guarded), and
|
|
# `/health/` (liveness + readiness) — so those proxy to c0re and
|
|
# everything else serves the dist with an SPA fallback to index.html.
|
|
# Path routing is deterministic where an Accept-header split would make
|
|
# the SAME url behave differently by content-type (e.g. `/api/state`
|
|
# fetched with `Accept: text/html` wrongly getting index.html). A new
|
|
# top-level c0re route prefix (beyond /api + /webhook + /health) needs
|
|
# a matching location added here.
|
|
dashboardProxyLocation = {
|
|
"/" = {
|
|
root = dashboardDist;
|
|
extraConfig = ''
|
|
try_files $uri /index.html;
|
|
${dashboardAuth}
|
|
'';
|
|
};
|
|
"/api/" = {
|
|
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
# off + 1d keep the SSE streams (/api/dashboard/stream,
|
|
# /api/build-logs/id/{id}/stream) live.
|
|
proxy_buffering off;
|
|
proxy_read_timeout 1d;
|
|
${dashboardAuth}
|
|
'';
|
|
};
|
|
"/webhook/" = {
|
|
# No dashboardAuth here: Forgejo cannot send HTTP Basic credentials
|
|
# with webhook deliveries. HMAC (X-Hub-Signature-256) is the auth
|
|
# for these endpoints; hive-c0re verifies it in the handler.
|
|
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
|
|
};
|
|
"/health/" = {
|
|
# No dashboardAuth here either, for a different reason than
|
|
# /webhook/: an external uptime monitor generally can't do
|
|
# interactive HTTP Basic. The endpoints themselves are scoped to
|
|
# status + warning kind/message (see hive-c0re/src/dashboard/
|
|
# health.rs) — no tokens, no agent detail — so exposing them
|
|
# unauthenticated isn't a new secret surface.
|
|
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
|
|
};
|
|
};
|
|
|
|
# Swagger UI: nginx hosts the FULL themed dist (`swaggerUiTheme` —
|
|
# vendored Swagger UI + our overlay, see nix/packages/swagger-ui-
|
|
# dist.nix + swagger-ui-theme.nix) straight from the store, with NO
|
|
# fallback to hive-c0re at all — per the operator's shape: "core
|
|
# should not need the swagger ui at all if it is hosted in gateway"
|
|
# / "core only hosts the json". Only `/api/openapi.json`
|
|
# (the live-generated spec `index.html` fetches; not under this
|
|
# prefix) still proxies to c0re via "/api/" below — that's the one
|
|
# thing that has to stay dynamic.
|
|
#
|
|
# Prefix location, not exact-match: wins over "/api/" on plain
|
|
# prefix length (no ordering/`=` needed), and now needs to cover
|
|
# every file in the tree (bundle.js, maps, favicons, …), not just
|
|
# our 2 override files — hive-c0re no longer serves any of this as
|
|
# a fallback once its own `utoipa-swagger-ui` mount is removed.
|
|
# `= /api/docs` (no trailing slash) issues the same redirect
|
|
# `utoipa-swagger-ui`'s router used to: that mount is going away
|
|
# too, so nginx has to own it now, or the H0M3 hub's own `/api/docs`
|
|
# link (no trailing slash) would 404 once hive-c0re drops the route.
|
|
swaggerUiLocations = {
|
|
"= /api/docs" = {
|
|
extraConfig = ''
|
|
return 301 /api/docs/;
|
|
'';
|
|
};
|
|
"/api/docs/" = {
|
|
alias = "${swaggerUiTheme}/";
|
|
extraConfig = ''
|
|
index index.html;
|
|
${dashboardAuth}
|
|
'';
|
|
};
|
|
};
|
|
in
|
|
{
|
|
# Accept-header SPA map for the matrix GUI only (see docs/gateway.md
|
|
# "SPA fallback"): text/html → index.html, else a sentinel so
|
|
# try_files falls through to 404. The dashboard doesn't use an
|
|
# Accept-header map — it routes by path (see dashboardProxyLocation).
|
|
appendHttpConfig = lib.optionalString (matrixCfg.enable && matrixCfg.gui.enable) ''
|
|
map $http_accept $matrix_spa_target {
|
|
default "/__matrix_spa_no_html_fallback";
|
|
"~*text/html" "/index.html";
|
|
}
|
|
'';
|
|
|
|
virtualHosts = {
|
|
# `tlsFor "_"`, not a separate binding: the default server is a
|
|
# vhost named `_`, and a name that is not a swarm service domain
|
|
# (`_` never is) resolves to the hive's own leaf — which is what
|
|
# this vhost has always served.
|
|
"_" = (vhostTlsFor "_") // {
|
|
listen = vhostListen;
|
|
locations =
|
|
matrixRedirectLocations
|
|
// wellKnownLocations
|
|
// agentLocations
|
|
// dashboardProxyLocation
|
|
// swaggerUiLocations
|
|
// lib.optionalAttrs cfg.auth.enable {
|
|
# Internal-only target for the 401 error_page above.
|
|
# `internal` prevents direct client access; `alias` serves
|
|
# the pre-built HTML from the Nix store.
|
|
"= /__hive_auth_unauthorized" = {
|
|
extraConfig = ''
|
|
internal;
|
|
alias ${errorPages.unauthorized};
|
|
default_type text/html;
|
|
'';
|
|
};
|
|
};
|
|
# Per-agent location blocks, generated at runtime by
|
|
# hive-c0re and written to /var/lib/hive-gateway/conf/agents.conf
|
|
# on the host — the same machine nginx runs on. nginx parses
|
|
# `include` at config-load time so a reload (triggered by c0re
|
|
# after each agents.conf write) picks up new or removed
|
|
# agents without a nixos-rebuild. nginx's longest-prefix-
|
|
# match rule ensures `/agent/<name>/` from this file beats
|
|
# the `/agent/` catch-all above.
|
|
extraConfig = securityHeaders + ''
|
|
include /var/lib/hive-gateway/conf/agents.conf;
|
|
'';
|
|
};
|
|
}
|
|
// forgeVhost
|
|
// autheliaVhost
|
|
// matrixVhost
|
|
// swarmUiVhost;
|
|
}
|