{ 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; }; }