Prose-only sweep of every remaining claim that nginx or dnsmasq lives in a container: the port comment (root in a container -> root on the host), upstreamHost's netns rationale, the ACME state dir, the store path reachability note, the vhost tree header, dnsmasq's resolv.conf paragraph (there is no copy and no path unit watching it any more), the two hive-network bridge comments, and swarm-controller's socket access-control note, which described a bind-mount that no longer exists. No behaviour change; all of it was describing a mechanism that was deleted.
312 lines
12 KiB
Nix
312 lines
12 KiB
Nix
# Option declarations for `services.hyperhive.gateway.*`. The gateway
|
|
# is always run alongside hyperhive (it's the single nginx in front of
|
|
# every surface and the only thing exposed to the outside); there is
|
|
# no enable flag. An operator who wants their own reverse proxy in
|
|
# front points it at the gateway's `port`.
|
|
{
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.hyperhive.gateway;
|
|
in
|
|
{
|
|
imports = [
|
|
(lib.mkRemovedOptionModule [ "services" "hyperhive" "gateway" "selfSignedTls" ] ''
|
|
Self-signed TLS is the implicit default whenever neither
|
|
tls.certDir nor tls.acme is configured, and there is no http-only
|
|
mode. Remove the setting; configure `tls.certDir` or `tls.acme`
|
|
to override the self-signed default.
|
|
'')
|
|
];
|
|
|
|
options.services.hyperhive.gateway = {
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 80;
|
|
example = 8080;
|
|
description = ''
|
|
TCP port the gateway listens on. Default 80 (canonical web
|
|
port). nginx runs on the host as root, so it can bind <1024;
|
|
if 80 is already taken on the host (existing nginx, traefik,
|
|
etc.) override to an unused port like 8080 or move the
|
|
conflicting service.
|
|
'';
|
|
};
|
|
|
|
upstreamHost = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "127.0.0.1";
|
|
description = ''
|
|
Host the gateway proxies non-static requests to. Defaults to
|
|
`127.0.0.1` because nginx runs on the host itself, so loopback
|
|
resolves directly to hive-c0re.
|
|
'';
|
|
};
|
|
|
|
upstreamPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 7000;
|
|
description = ''
|
|
TCP port the gateway proxies non-static requests to. Defaults
|
|
to `7000` (hive-c0re's out-of-the-box dashboard port). Operators
|
|
who change `services.hyperhive.c0re.dashboardPort` should set
|
|
`upstreamPort` to match — kept as a hardcoded default rather
|
|
than a cross-reference to keep this module's options eval
|
|
independent of c0re's option tree shape.
|
|
'';
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Open `port` in the host firewall. Off by default (secure-by-default).
|
|
Flip to `true` to expose the gateway to
|
|
the operator's browser / external clients — required for any
|
|
out-of-host reach, since the agents themselves talk to
|
|
hive-c0re via the per-agent unix sockets and don't need the
|
|
nginx vhost. Leave off when running behind another reverse
|
|
proxy (e.g. caddy / traefik on the host) that handles TLS
|
|
termination + forwards to `port`.
|
|
|
|
**Note**: this used to default to `true`. Add
|
|
`services.hyperhive.gateway.openFirewall = true;` to your host
|
|
config if external reach stopped working after a recent upgrade.
|
|
'';
|
|
};
|
|
|
|
localHostsEntry = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Add an `/etc/hosts` entry mapping `services.hyperhive.domain`
|
|
to `127.0.0.1` on the host. Useful for local deployments +
|
|
tests where there's no real DNS for `services.hyperhive.domain`
|
|
but the operator (or browser-based tests) want to hit
|
|
`http://''${services.hyperhive.domain}` to exercise the
|
|
gateway shape. Off by default — operators running with real
|
|
DNS shouldn't have a stale `/etc/hosts` entry sticking
|
|
around. Requires `services.hyperhive.domain` to be set.
|
|
'';
|
|
};
|
|
|
|
useSelfSigned = lib.mkOption {
|
|
type = lib.types.bool;
|
|
internal = true;
|
|
readOnly = true;
|
|
default = cfg.tls.certDir == null && !cfg.tls.acme.enable;
|
|
defaultText = lib.literalExpression "tls.certDir == null && !tls.acme.enable";
|
|
description = ''
|
|
Read-only derived flag: `true` when the gateway serves the
|
|
self-signed (hive-CA-signed) leaf — i.e. neither `tls.certDir` nor
|
|
`tls.acme.enable` is configured. Single source of truth for the
|
|
self-signed condition; consumed by the `hive-tls` and `hive-ci`
|
|
modules so the derivation isn't duplicated. Internal — not meant to
|
|
be set by operators (use `tls.certDir` / `tls.acme` to override the
|
|
self-signed default).
|
|
'';
|
|
};
|
|
|
|
httpsPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 443;
|
|
example = 8443;
|
|
description = ''
|
|
TCP port for the TLS-terminated vhosts. Default 443. The gateway
|
|
always terminates TLS (self-signed is the implicit floor when no
|
|
`tls.certDir` / ACME is configured), so this port is always active
|
|
alongside the plain-http `port`.
|
|
'';
|
|
};
|
|
|
|
tls = {
|
|
certDir = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
example = lib.literalExpression ''"/var/lib/acme/example.com"'';
|
|
description = ''
|
|
Path to a host directory containing a TLS certificate and
|
|
private key for nginx. When set, nginx listens on `httpsPort`
|
|
and uses this cert, overriding the self-signed default — the
|
|
auto-generated hive-CA-signed leaf is skipped entirely.
|
|
|
|
nginx reads `<certDir>/<tls.certName>` and
|
|
`<certDir>/<tls.keyName>` directly — it runs on the host, so
|
|
the directory needs no bind mount and no copy.
|
|
Default filenames (`cert.pem` / `key.pem`) match the output
|
|
layout of nixpkgs's `security.acme` module.
|
|
|
|
Typical ACME setup:
|
|
```nix
|
|
security.acme.certs."example.com" = { ... };
|
|
services.hyperhive.gateway.tls.certDir =
|
|
config.security.acme.certs."example.com".directory;
|
|
```
|
|
|
|
When using an external CA cert, other hives can declare this
|
|
one in `services.hyperhive.swarm.hives` without
|
|
`certFingerprint` — the standard CA bundle validates.
|
|
|
|
Mutual exclusion with `tls.acme.enable` — set one or the other,
|
|
not both.
|
|
'';
|
|
};
|
|
|
|
certName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "cert.pem";
|
|
description = ''
|
|
Filename of the TLS certificate within `tls.certDir`. Defaults
|
|
to `cert.pem` which matches nixpkgs's `security.acme` output.
|
|
'';
|
|
};
|
|
|
|
keyName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "key.pem";
|
|
description = ''
|
|
Filename of the TLS private key within `tls.certDir`. Defaults
|
|
to `key.pem` which matches nixpkgs's `security.acme` output.
|
|
'';
|
|
};
|
|
|
|
acme = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Let the gateway's nginx obtain and renew TLS certificates
|
|
automatically via ACME (Let's Encrypt). When enabled, each
|
|
vhost calls out to Let's Encrypt using the HTTP-01
|
|
challenge on `port` (default 80) and stores certs in the
|
|
gateway's persistent state dir on the host.
|
|
|
|
Requirements:
|
|
- `services.hyperhive.domain` must be set and publicly
|
|
DNS-resolvable to this host.
|
|
- `services.hyperhive.gateway.openFirewall = true` so
|
|
Let's Encrypt can reach `/.well-known/acme-challenge/`.
|
|
- `tls.acme.email` must be set (ACME account contact).
|
|
|
|
Mutual exclusion: `tls.certDir` set together with
|
|
`tls.acme.enable = true` fails at eval — pick one TLS source.
|
|
|
|
Typical setup:
|
|
```nix
|
|
services.hyperhive.gateway = {
|
|
openFirewall = true;
|
|
tls.acme = {
|
|
enable = true;
|
|
email = "admin@example.com";
|
|
};
|
|
};
|
|
```
|
|
|
|
After enabling, this hive's entry in `swarm.hives` can omit
|
|
`certFingerprint` — Let's Encrypt certs are CA-trusted
|
|
by default.
|
|
'';
|
|
};
|
|
|
|
email = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "admin@example.com";
|
|
description = ''
|
|
Email address for the ACME account registration with
|
|
Let's Encrypt. Required when `tls.acme.enable = true`.
|
|
Let's Encrypt sends expiry warnings to this address.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
auth = {
|
|
enable = lib.mkEnableOption ''
|
|
HTTP basic auth on the gateway using an htpasswd file. When
|
|
enabled, every request to the gateway's main vhost requires a
|
|
valid username and password. nginx's built-in `auth_basic`
|
|
module validates credentials against
|
|
`/var/lib/hive-gateway/conf/gateway.htpasswd`. Off by default.
|
|
|
|
Manage users with `hivectl gateway create-user`, `delete-user`,
|
|
and `list-users` — see `hivectl gateway --help` for usage.
|
|
The htpasswd file is created automatically when auth is enabled;
|
|
add at least one user before enabling to avoid locking everyone out.
|
|
'';
|
|
|
|
realm = lib.mkOption {
|
|
type = lib.types.strMatching "[^\"$]*";
|
|
default = "hyperhive";
|
|
example = "my-hive";
|
|
description = ''
|
|
HTTP Basic auth `realm` value sent in the `WWW-Authenticate`
|
|
header when credentials are absent or rejected. Must not
|
|
contain `"` or `$` (nginx string metacharacters).
|
|
'';
|
|
};
|
|
};
|
|
|
|
swaggerUiTheme = lib.mkOption {
|
|
type = lib.types.package;
|
|
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swagger-ui-theme";
|
|
description = ''
|
|
Full Swagger UI static dist, hyperhive-themed (see
|
|
`nix/packages/swagger-ui-theme.nix`, built on
|
|
`nix/packages/swagger-ui-dist.nix`). The gateway serves this
|
|
whole tree directly at `/api/docs/` — hive-c0re hosts none of
|
|
it, only the dynamic `/api/openapi.json` route (proxied
|
|
through, unaffected by this option). Override to ship a
|
|
custom theme (or the plain vendored dist) without a gateway
|
|
rebuild.
|
|
'';
|
|
};
|
|
|
|
hsts = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Add `Strict-Transport-Security` to all gateway vhosts.
|
|
|
|
Disabled by default: HSTS pins HTTPS in the browser's HSTS
|
|
preload list; enabling it on a deployment that later loses TLS
|
|
will lock browsers out until the max-age expires. Only enable
|
|
this when you are certain TLS is permanent.
|
|
|
|
The gateway always terminates TLS (self-signed floor), so
|
|
HSTS is always served over https when enabled — but mind the
|
|
warning above: HSTS pins https in the browser, so only enable it
|
|
when TLS is permanent for this deployment.
|
|
'';
|
|
};
|
|
|
|
maxAge = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 31536000;
|
|
example = 86400;
|
|
description = ''
|
|
Value for the `max-age` directive in seconds.
|
|
Default: 31536000 (1 year), which is the value required for
|
|
HSTS preload list submission. Use a shorter value (e.g. 86400)
|
|
while testing so browsers forget the pin quickly.
|
|
'';
|
|
};
|
|
|
|
includeSubDomains = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Whether to include `includeSubDomains` in the HSTS header.
|
|
Only disable this if the gateway host has sub-domains that
|
|
intentionally serve plain HTTP.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
}
|