at master 7.4 kB view raw
1{ 2 lib, 3 pkgs, 4 config, 5 ... 6}: 7let 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; 44in 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}