Files
nix-config/modules/nixos/services/nebula-ui/default.nix
2026-04-10 09:48:58 -05:00

138 lines
5.4 KiB
Nix

{
config,
lib,
namespace,
pkgs,
...
}:
with lib;
let
name = "nebula-ui";
cfg = config.${namespace}.services.${name};
statsListenAddr = "${cfg.statsListenAddress}:${toString cfg.statsPort}";
nebulaUiConfig = lib.${namespace}.mkModule {
inherit config name;
description = "Nebula network web UI (stats + cert signing)";
options = {
# Override mkModule defaults: bind to localhost only; firewall closed by
# default since this service sits behind a Caddy reverse proxy.
listenAddress = lib.${namespace}.mkOpt types.str "127.0.0.1" "Address nebula-ui listens on";
openFirewall =
lib.${namespace}.mkBoolOpt false
"Open firewall for nebula-ui (not needed behind a reverse proxy)";
# ── Stats endpoint ───────────────────────────────────────────────────────
statsListenAddress =
lib.${namespace}.mkOpt types.str "127.0.0.1"
"Address nebula's stats HTTP endpoint listens on";
statsPort = lib.${namespace}.mkOpt types.port 8474 "Port nebula's stats HTTP endpoint listens on";
# ── CA secrets ───────────────────────────────────────────────────────────
# The CA cert/key are already decrypted by the nebula sops.nix.
# We need a *separate* sops secret for the CA key exposed to nebula-ui
# because the nebula module only exposes it to nebula-<network>.
caCertSecretKey =
lib.${namespace}.mkOpt types.str ""
"SOPS secret key for the CA certificate (e.g. \"pi5/nebula/ca-cert\")";
caKeySecretKey =
lib.${namespace}.mkOpt types.str ""
"SOPS secret key for the CA private key (e.g. \"pi5/nebula/ca-key\")";
secretsFile =
lib.${namespace}.mkOpt types.str ""
"Path to the SOPS secrets YAML that holds the CA cert + key";
# ── Network identity ─────────────────────────────────────────────────────
networkName =
lib.${namespace}.mkOpt types.str "jallen-nebula"
"Nebula network name (must match services.nebula.networkName)";
};
moduleConfig = {
assertions = [
{
assertion = cfg.caCertSecretKey != "";
message = "mjallen.services.nebula-ui.caCertSecretKey must be set";
}
{
assertion = cfg.caKeySecretKey != "";
message = "mjallen.services.nebula-ui.caKeySecretKey must be set";
}
{
assertion = cfg.secretsFile != "";
message = "mjallen.services.nebula-ui.secretsFile must be set";
}
];
# ── SOPS secrets ─────────────────────────────────────────────────────────
# ca-cert: already declared by the nebula module (owned by nebula-<network>,
# mode 0440). We only append restartUnits here; access is via group membership.
sops.secrets."${cfg.caCertSecretKey}" = {
restartUnits = [ "nebula-ui.service" ];
};
# ca-key: only used by nebula-ui, so we own it outright.
sops.secrets."${cfg.caKeySecretKey}" = {
sopsFile = cfg.secretsFile;
owner = name;
group = name;
restartUnits = [ "nebula-ui.service" ];
};
# ── User / group ────────────────────────────────────────────────────────
users.users.${name} = {
isSystemUser = true;
group = name;
# Grant read access to the nebula CA secrets (owned by nebula-<network>)
extraGroups = [ "nebula-${cfg.networkName}" ];
description = "Nebula UI service user";
};
users.groups.${name} = { };
# ── Systemd service ─────────────────────────────────────────────────────
systemd.services.${name} = {
description = "Nebula network web UI";
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"sops-nix.service"
];
environment = {
NEBULA_UI_CA_CERT_PATH = config.sops.secrets."${cfg.caCertSecretKey}".path;
NEBULA_UI_CA_KEY_PATH = config.sops.secrets."${cfg.caKeySecretKey}".path;
NEBULA_UI_STATS_URL = "http://${statsListenAddr}";
NEBULA_UI_NETWORK_NAME = cfg.networkName;
NEBULA_UI_LISTEN_HOST = cfg.listenAddress;
NEBULA_UI_LISTEN_PORT = toString cfg.port;
};
serviceConfig = {
ExecStart = "${pkgs.${namespace}.nebula-ui}/bin/nebula-ui";
User = name;
GlistenAddressroup = name;
Restart = "on-failure";
RestartSec = "5s";
# Hardening
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ReadOnlyPaths = [
config.sops.secrets."${cfg.caCertSecretKey}".path
config.sops.secrets."${cfg.caKeySecretKey}".path
];
};
};
};
};
in
{
imports = [ nebulaUiConfig ];
}