mara on #651: "Dont default openFirewall to true." Flip the `openFirewall` default from `true` to `false` for all three modules that expose host-side ports: - `services.hyperhive.forge.openFirewall` (httpPort 3000 + sshPort 2222) - `services.hyperhive.gateway.openFirewall` (port 80) - `services.hyperhive.matrix.openFirewall` (httpPort 8008) Rationale: secure-by-default. With shared host netns, the host + every agent container reach these services via `localhost` regardless of the firewall — the open only matters for access from outside the host. Operators who want external reach now flip the bool explicitly: services.hyperhive.gateway.openFirewall = true; Each description updated to explain the new default + when to flip it (operator's browser, external git clients, federation announcement, etc.). Behind a host-level reverse proxy that handles TLS, leave off. Verified via `nix eval` on a clean stub config: - forge openFirewall = false - gateway openFirewall = false - matrix openFirewall = false - networking.firewall.allowedTCPPorts = [] (was: [80 2222 3000 8008]) Note: c0re's direct ports (7000/8000/8100-8999) are gated separately via #621 on `gateway.enable` — that gate stays; this PR only touches the per-module `openFirewall` knobs. Closes #651.
192 lines
7 KiB
Nix
192 lines
7 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.hyperhive.gateway;
|
|
hyperhiveDomain = config.services.hyperhive.domain;
|
|
matrixCfg = config.services.hyperhive.matrix;
|
|
in
|
|
{
|
|
# Single nginx in front of every hyperhive surface (#609 / #15 v0).
|
|
# Lives in its own nixos-container (like hive-forge / hive-matrix) so
|
|
# the operator can opt out without touching the host's own nginx, and
|
|
# so the static-serve responsibility for the matrix GUI moves off
|
|
# hive-c0re's axum router. Shares host netns so `localhost`
|
|
# upstream resolution works without any port-forward dance.
|
|
#
|
|
# Routes (v0):
|
|
# `location /matrix/` → static-serve fluffychat-web dist (when
|
|
# `services.hyperhive.matrix.gui.enable` is true)
|
|
# `location /` → proxy_pass to hive-c0re's dashboard upstream
|
|
#
|
|
# Container name `hive-gateway` keeps hive-c0re's lifecycle scanner
|
|
# (which only sees `h-*`) out of the picture. State-free — nginx
|
|
# config lives in the nix store, no runtime persistence to manage.
|
|
|
|
options.services.hyperhive.gateway = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = ''
|
|
Run hive-gateway — a single nginx in front of every hyperhive
|
|
surface. On by default: the gateway hosts the matrix GUI static
|
|
dist (when `services.hyperhive.matrix.gui.enable` is true) and
|
|
proxies everything else to hive-c0re's dashboard upstream. Set
|
|
`services.hyperhive.gateway.enable = false` to bypass nginx
|
|
entirely and reach hive-c0re directly on its dashboard port
|
|
(7000 by default).
|
|
|
|
v0 is HTTP-only; TLS / public-domain shape is tracked
|
|
separately.
|
|
'';
|
|
};
|
|
|
|
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 (#651,
|
|
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`.
|
|
'';
|
|
};
|
|
|
|
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.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = !cfg.localHostsEntry || hyperhiveDomain != null;
|
|
message = ''
|
|
services.hyperhive.gateway.localHostsEntry = true requires
|
|
services.hyperhive.domain to be set. Either pin a hostname
|
|
or leave `localHostsEntry` at its default of false.
|
|
'';
|
|
}
|
|
];
|
|
|
|
containers.hive-gateway = {
|
|
autoStart = true;
|
|
ephemeral = false;
|
|
# Share host netns — nginx then binds host-level ports directly,
|
|
# `localhost` upstream resolution reaches hive-c0re without any
|
|
# port-forward dance, and the firewall config below is the only
|
|
# layer that matters.
|
|
privateNetwork = false;
|
|
config =
|
|
{ pkgs, ... }:
|
|
{
|
|
system.stateVersion = "26.05";
|
|
services.nginx = {
|
|
enable = true;
|
|
recommendedProxySettings = true;
|
|
recommendedOptimisation = true;
|
|
virtualHosts."_" = {
|
|
listen = [
|
|
{
|
|
addr = "0.0.0.0";
|
|
port = cfg.port;
|
|
}
|
|
];
|
|
locations =
|
|
# Matrix GUI: when the operator has flipped both
|
|
# `services.hyperhive.matrix.enable` and `matrix.gui.enable`
|
|
# on, nginx serves fluffychat-web (or whatever override)
|
|
# as a static dist at `/matrix/`. fluffychat is a SPA
|
|
# — fall back to its index.html on deep links.
|
|
lib.optionalAttrs (matrixCfg.enable && matrixCfg.gui.enable) {
|
|
"/matrix/" = {
|
|
alias = "${matrixCfg.gui.package}/";
|
|
extraConfig = ''
|
|
try_files $uri $uri/ /matrix/index.html;
|
|
'';
|
|
};
|
|
}
|
|
// {
|
|
# Everything else proxies to hive-c0re. Upgrade
|
|
# headers stay set so SSE (`/dashboard/stream`,
|
|
# `/events/stream`) + websocket (`/screen/ws`)
|
|
# endpoints keep working transparently.
|
|
"/" = {
|
|
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
proxy_buffering off;
|
|
proxy_read_timeout 1d;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
networking.firewall = lib.mkIf cfg.openFirewall {
|
|
allowedTCPPorts = [ cfg.port ];
|
|
};
|
|
|
|
networking.hosts = lib.mkIf (cfg.localHostsEntry && hyperhiveDomain != null) {
|
|
"127.0.0.1" = [ hyperhiveDomain ];
|
|
};
|
|
};
|
|
}
|