nixos/broadcast-box: init

+277
+2
nixos/doc/manual/release-notes/rl-2511.section.md
··· 20 20 - [LACT](https://github.com/ilya-zlobintsev/LACT), a GPU monitoring and configuration tool, can now be enabled through [services.lact.enable](#opt-services.lact.enable). 21 21 Note that for LACT to work properly on AMD GPU systems, you need to enable [hardware.amdgpu.overdrive.enable](#opt-hardware.amdgpu.overdrive.enable). 22 22 23 + - [Broadcast Box](https://github.com/Glimesh/broadcast-box), a WebRTC broadcast server. Available as [services.broadcast-box](options.html#opt-services.broadcast-box.enable). 24 + 23 25 - [SuiteNumérique Docs](https://github.com/suitenumerique/docs), a collaborative note taking, wiki and documentation web platform and alternative to Notion or Outline. Available as [services.lasuite-docs](#opt-services.lasuite-docs.enable). 24 26 25 27 ## Backward Incompatibilities {#sec-release-25.11-incompatibilities}
+1
nixos/modules/module-list.nix
··· 1491 1491 ./services/ttys/getty.nix 1492 1492 ./services/ttys/gpm.nix 1493 1493 ./services/ttys/kmscon.nix 1494 + ./services/video/broadcast-box.nix 1494 1495 ./services/video/epgstation/default.nix 1495 1496 ./services/video/frigate.nix 1496 1497 ./services/video/go2rtc/default.nix
+274
nixos/modules/services/video/broadcast-box.nix
··· 1 + { 2 + lib, 3 + pkgs, 4 + config, 5 + ... 6 + }: 7 + let 8 + inherit (lib) 9 + mkIf 10 + mkEnableOption 11 + mkPackageOption 12 + mkOption 13 + attrNames 14 + types 15 + match 16 + optional 17 + optionals 18 + toInt 19 + last 20 + splitString 21 + allUnique 22 + concatStringsSep 23 + all 24 + filter 25 + mapAttrs 26 + any 27 + getExe 28 + maintainers 29 + ; 30 + inherit (cfg) settings; 31 + cfg = config.services.broadcast-box; 32 + 33 + addressToPort = address: toInt (last (splitString ":" address)); 34 + httpPort = cfg.web.port; 35 + tcpMuxPort = addressToPort settings.TCP_MUX_ADDRESS; 36 + httpRedirect = settings.ENABLE_HTTP_REDIRECT or (settings.HTTPS_REDIRECT_PORT != null); 37 + 38 + udpPorts = 39 + optional (settings.UDP_MUX_PORT != null) settings.UDP_MUX_PORT 40 + ++ optional (settings.UDP_WHEP_PORT != null) settings.UDP_WHEP_PORT 41 + ++ optional (settings.UDP_WHIP_PORT != null) settings.UDP_WHIP_PORT; 42 + tcpPorts = optional (settings.TCP_MUX_ADDRESS != null) tcpMuxPort; 43 + webPorts = [ httpPort ] ++ optional httpRedirect settings.HTTPS_REDIRECT_PORT; 44 + in 45 + { 46 + options.services.broadcast-box = { 47 + enable = mkEnableOption "Broadcast Box"; 48 + package = mkPackageOption pkgs "broadcast-box" { }; 49 + 50 + web = { 51 + host = mkOption { 52 + type = types.str; 53 + default = ""; 54 + example = "127.0.0.1"; 55 + description = '' 56 + Host address the HTTP server listens on. By default the server 57 + listens on all interfaces. 58 + ''; 59 + }; 60 + 61 + port = mkOption { 62 + type = types.port; 63 + default = 8080; 64 + description = '' 65 + Port the HTTP server listens on. 66 + ''; 67 + }; 68 + 69 + openFirewall = mkEnableOption '' 70 + opening the HTTP server port and, if enabled, the HTTPS redirect server 71 + port in the firewall. 72 + ''; 73 + }; 74 + 75 + openFirewall = mkEnableOption '' 76 + opening WebRTC traffic ports in the firewall. Randomly selected ports 77 + will not be opened. 78 + ''; 79 + 80 + settings = mkOption { 81 + visible = "shallow"; 82 + 83 + type = types.submodule { 84 + freeformType = 85 + with types; 86 + attrsOf ( 87 + nullOr (oneOf [ 88 + bool 89 + int 90 + str 91 + ]) 92 + ); 93 + options = { 94 + TCP_MUX_ADDRESS = mkOption { 95 + type = with types; nullOr (strMatching ".*:[0-9]+"); 96 + default = null; 97 + }; 98 + 99 + DISABLE_STATUS = mkOption { 100 + type = types.bool; 101 + default = true; 102 + }; 103 + 104 + UDP_MUX_PORT = mkOption { 105 + type = with types; nullOr port; 106 + default = null; 107 + }; 108 + 109 + UDP_WHEP_PORT = mkOption { 110 + type = with types; nullOr port; 111 + default = null; 112 + }; 113 + 114 + UDP_WHIP_PORT = mkOption { 115 + type = with types; nullOr port; 116 + default = null; 117 + }; 118 + 119 + ENABLE_HTTP_REDIRECT = mkOption { 120 + type = types.bool; 121 + default = false; 122 + }; 123 + 124 + HTTPS_REDIRECT_PORT = mkOption { 125 + type = with types; nullOr port; 126 + default = if settings.ENABLE_HTTP_REDIRECT then 80 else null; 127 + }; 128 + }; 129 + }; 130 + 131 + default = { 132 + DISABLE_STATUS = true; 133 + }; 134 + 135 + example = { 136 + DISABLE_STATUS = true; 137 + INCLUDE_PUBLIC_IP_IN_NAT_1_TO_1_IP = true; 138 + UDP_MUX_PORT = 3000; 139 + }; 140 + 141 + description = '' 142 + Attribute set of environment variables. 143 + 144 + <https://github.com/Glimesh/broadcast-box#environment-variables> 145 + 146 + :::{.warning} 147 + The status API exposes stream keys so {env}`DISABLE_STATUS` is enabled 148 + by default. 149 + ::: 150 + ''; 151 + }; 152 + }; 153 + 154 + config = mkIf cfg.enable { 155 + assertions = [ 156 + { 157 + assertion = !(settings ? HTTP_ADDRESS); 158 + message = '' 159 + The Broadcast Box `HTTP_ADDRESS` variable should not be used. Instead 160 + use the `host` and `port` options. 161 + ''; 162 + } 163 + { 164 + assertion = httpRedirect -> settings ? SSL_CERT && settings ? SSL_KEY; 165 + message = '' 166 + The Broadcast Box `ENABLE_HTTP_REDIRECT` variable requires `SSL_CERT` 167 + and `SSL_KEY` to be configured. 168 + ''; 169 + } 170 + { 171 + assertion = httpRedirect -> httpPort == 443; 172 + message = '' 173 + Broadcast Box HTTP redirect only works if the HTTP server listen port 174 + is 443. 175 + ''; 176 + } 177 + { 178 + assertion = allUnique (tcpPorts ++ webPorts); 179 + message = '' 180 + Broadcast Box configuration contains duplicate TCP ports. 181 + ''; 182 + } 183 + { 184 + assertion = all (name: (match "[A-Z0-9_]+" name) != null) (attrNames settings); 185 + message = 186 + let 187 + offenders = filter (name: (match "[A-Z0-9_]+" name) == null) (attrNames settings); 188 + in 189 + '' 190 + Broadcast Box `settings` attribute names must be in uppercase snake 191 + case. Invalid attribute name(s): `${concatStringsSep ", " offenders}` 192 + ''; 193 + } 194 + ]; 195 + 196 + systemd.services.broadcast-box = { 197 + description = "Broadcast Box"; 198 + after = [ "network-online.target" ]; 199 + wants = [ "network-online.target" ]; 200 + wantedBy = [ "multi-user.target" ]; 201 + startLimitBurst = 3; 202 + startLimitIntervalSec = 180; 203 + 204 + environment = 205 + (mapAttrs ( 206 + _: value: 207 + if (builtins.typeOf value == "bool") then 208 + if !value then null else "true" 209 + else if (builtins.typeOf value == "int") then 210 + toString value 211 + else 212 + value 213 + ) cfg.settings) 214 + // { 215 + APP_ENV = "nixos"; 216 + HTTP_ADDRESS = cfg.web.host + ":" + toString cfg.web.port; 217 + }; 218 + 219 + serviceConfig = 220 + let 221 + priviledgedPort = any (p: p > 0 && p < 1024) (udpPorts ++ tcpPorts ++ webPorts); 222 + in 223 + { 224 + ExecStart = "${getExe cfg.package}"; 225 + Restart = "always"; 226 + RestartSec = "10s"; 227 + 228 + DynamicUser = true; 229 + LockPersonality = true; 230 + NoNewPrivileges = true; 231 + PrivateUsers = !priviledgedPort; 232 + PrivateDevices = true; 233 + PrivateMounts = true; 234 + PrivateTmp = true; 235 + ProtectSystem = "strict"; 236 + ProtectHome = true; 237 + ProtectControlGroups = true; 238 + ProtectClock = true; 239 + ProtectProc = "invisible"; 240 + ProtectHostname = true; 241 + ProtectKernelLogs = true; 242 + ProtectKernelModules = true; 243 + ProtectKernelTunables = true; 244 + ProcSubset = "pid"; 245 + RemoveIPC = true; 246 + RestrictAddressFamilies = [ 247 + "AF_INET" 248 + "AF_INET6" 249 + "AF_NETLINK" 250 + ]; 251 + RestrictNamespaces = true; 252 + RestrictRealtime = true; 253 + RestrictSUIDSGID = true; 254 + SystemCallArchitectures = "native"; 255 + SystemCallFilter = [ 256 + "@system-service" 257 + "~@privileged" 258 + ]; 259 + CapabilityBoundingSet = if priviledgedPort then [ "CAP_NET_BIND_SERVICE" ] else ""; 260 + AmbientCapabilities = mkIf priviledgedPort [ "CAP_NET_BIND_SERVICE" ]; 261 + DeviceAllow = ""; 262 + MemoryDenyWriteExecute = true; 263 + UMask = "0077"; 264 + }; 265 + }; 266 + 267 + networking.firewall = { 268 + allowedTCPPorts = optionals cfg.openFirewall tcpPorts ++ optionals cfg.web.openFirewall webPorts; 269 + allowedUDPPorts = optionals cfg.openFirewall udpPorts; 270 + }; 271 + }; 272 + 273 + meta.maintainers = with maintainers; [ JManch ]; 274 + }