lightscale/nixos/modules/lightscale-server.nix

247 lines
8.4 KiB
Nix

{ defaultPackage }:
{ config, lib, ... }:
let
cfg = config.services.lightscale-server;
meshEnabled =
cfg.meshServerId != null
|| cfg.meshListen != null
|| cfg.meshPeers != [ ]
|| cfg.meshCaCert != null
|| cfg.meshCert != null
|| cfg.meshKey != null;
args =
[ "--listen" cfg.listen ]
++ lib.optionals (cfg.dbUrl == null && cfg.dbUrlFile == null) [ "--state" cfg.stateFile ]
++ lib.optionals (cfg.dbUrl != null) [ "--db-url" cfg.dbUrl ]
++ lib.optionals (cfg.dbUrlFile != null) [ "--db-url-file" cfg.dbUrlFile ]
++ lib.optionals (cfg.adminToken != null) [ "--admin-token" cfg.adminToken ]
++ lib.optionals (cfg.stunServers != [ ]) [ "--stun" (lib.concatStringsSep "," cfg.stunServers) ]
++ lib.optionals (cfg.turnServers != [ ]) [ "--turn" (lib.concatStringsSep "," cfg.turnServers) ]
++ lib.optionals (cfg.streamRelayServers != [ ]) [ "--stream-relay" (lib.concatStringsSep "," cfg.streamRelayServers) ]
++ lib.optionals (cfg.udpRelayServers != [ ]) [ "--udp-relay" (lib.concatStringsSep "," cfg.udpRelayServers) ]
++ lib.optionals (cfg.udpRelayListen != null) [ "--udp-relay-listen" cfg.udpRelayListen ]
++ lib.optionals (cfg.streamRelayListen != null) [ "--stream-relay-listen" cfg.streamRelayListen ]
++ lib.optionals (cfg.meshServerId != null) [ "--mesh-server-id" cfg.meshServerId ]
++ lib.optionals (cfg.meshListen != null) [ "--mesh-listen" cfg.meshListen ]
++ lib.optionals (cfg.meshPeers != [ ]) [ "--mesh-peer" (lib.concatStringsSep "," cfg.meshPeers) ]
++ lib.optionals (cfg.meshCaCert != null) [ "--mesh-ca-cert" cfg.meshCaCert ]
++ lib.optionals (cfg.meshCert != null) [ "--mesh-cert" cfg.meshCert ]
++ lib.optionals (cfg.meshKey != null) [ "--mesh-key" cfg.meshKey ]
++ lib.optionals meshEnabled [ "--mesh-max-hops" (toString cfg.meshMaxHops) ]
++ cfg.extraArgs;
startCmd = "${lib.getExe' cfg.package "lightscale-server"} ${lib.escapeShellArgs args}";
in
{
options.services.lightscale-server = {
enable = lib.mkEnableOption "lightscale control-plane server";
package = lib.mkOption {
type = lib.types.package;
default = defaultPackage;
description = "lightscale-server package.";
};
listen = lib.mkOption {
type = lib.types.str;
default = "0.0.0.0:8080";
description = "Listen address for the control plane.";
};
stateFile = lib.mkOption {
type = lib.types.str;
default = "/var/lib/lightscale-server/state.json";
description = "State file path when dbUrl is not set.";
};
dbUrl = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Postgres/CockroachDB URL. If set, stateFile is ignored.";
};
dbUrlFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Path to a file containing Postgres/CockroachDB URL. If set, stateFile is ignored.";
};
adminToken = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Admin token passed via CLI. Prefer environmentFiles for secret handling.";
};
environmentFiles = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "systemd EnvironmentFile entries (for LIGHTSCALE_ADMIN_TOKEN, etc.).";
};
environment = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Additional environment variables for the server service.";
};
stunServers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "STUN servers advertised to clients.";
};
turnServers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "TURN servers advertised to clients.";
};
streamRelayServers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Stream relay servers advertised to clients.";
};
udpRelayServers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "UDP relay servers advertised to clients.";
};
udpRelayListen = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "UDP relay listen address.";
};
streamRelayListen = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Stream relay listen address.";
};
meshServerId = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Server id used for authenticated relay mesh.";
};
meshListen = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Relay mesh listen address (mTLS).";
};
meshPeers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Relay mesh peers in ID=HOST:PORT form.";
};
meshCaCert = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Path to relay mesh CA certificate PEM.";
};
meshCert = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Path to this server's relay mesh certificate PEM.";
};
meshKey = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Path to this server's relay mesh private key PEM.";
};
meshMaxHops = lib.mkOption {
type = lib.types.ints.positive;
default = 4;
description = "Maximum relay mesh forwarding hops.";
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Additional CLI arguments for lightscale-server.";
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Open firewall ports listed in firewallTCPPorts/firewallUDPPorts.";
};
firewallTCPPorts = lib.mkOption {
type = lib.types.listOf lib.types.port;
default = [ 8080 ];
description = "TCP ports to open when openFirewall=true.";
};
firewallUDPPorts = lib.mkOption {
type = lib.types.listOf lib.types.port;
default = [ ];
description = "UDP ports to open when openFirewall=true.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !(cfg.dbUrl != null && cfg.dbUrlFile != null);
message = "services.lightscale-server: set only one of dbUrl and dbUrlFile.";
}
{
assertion = cfg.adminToken != null || cfg.environment ? LIGHTSCALE_ADMIN_TOKEN || cfg.environmentFiles != [ ];
message = "services.lightscale-server: provide LIGHTSCALE_ADMIN_TOKEN via adminToken, environment, or environmentFiles.";
}
{
assertion = !meshEnabled || (
cfg.meshServerId != null
&& cfg.meshListen != null
&& cfg.meshCaCert != null
&& cfg.meshCert != null
&& cfg.meshKey != null
);
message = "services.lightscale-server: mesh requires meshServerId, meshListen, meshCaCert, meshCert, and meshKey.";
}
{
assertion = !meshEnabled || (cfg.streamRelayListen != null || cfg.udpRelayListen != null);
message = "services.lightscale-server: mesh forwarding requires streamRelayListen or udpRelayListen.";
}
];
users.groups.lightscale = { };
users.users.lightscale = {
isSystemUser = true;
group = "lightscale";
};
systemd.services.lightscale-server = {
description = "lightscale control-plane server";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
environment = cfg.environment;
serviceConfig = {
Type = "simple";
ExecStart = startCmd;
Restart = "on-failure";
RestartSec = 2;
User = "lightscale";
Group = "lightscale";
WorkingDirectory = "/var/lib/lightscale-server";
StateDirectory = "lightscale-server";
RuntimeDirectory = "lightscale-server";
EnvironmentFile = cfg.environmentFiles;
NoNewPrivileges = true;
};
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall cfg.firewallTCPPorts;
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall cfg.firewallUDPPorts;
};
}