# 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 inside the container binds <1024 because the container's init runs as root; 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 the gateway container shares the host netns, 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. The directory is bind-mounted read-only into the gateway container at `/run/hive-tls/`. nginx reads `/` and `/`. 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, peer hives can declare this hive in `services.hyperhive.swarm.peers` 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 nginx inside the gateway container 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 inside the gateway container's persistent state dir. 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, peer hives can omit `certFingerprint` in `swarm.peers` — 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/hyperhive/gateway/gateway.htpasswd` on the host (exposed as `/run/hive-state/gateway.htpasswd` inside the container via the existing gateway state bind-mount). 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). ''; }; }; 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. ''; }; }; }; }