broadcast-box: init at 0-unstable-2025-06-04 (#288443)

This commit is contained in:
Paul Haerle 2025-06-05 11:11:24 +02:00 committed by GitHub
commit f53da6c507
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 381 additions and 0 deletions

View file

@ -20,6 +20,8 @@
- [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).
Note that for LACT to work properly on AMD GPU systems, you need to enable [hardware.amdgpu.overdrive.enable](#opt-hardware.amdgpu.overdrive.enable).
- [Broadcast Box](https://github.com/Glimesh/broadcast-box), a WebRTC broadcast server. Available as [services.broadcast-box](options.html#opt-services.broadcast-box.enable).
- [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).
## Backward Incompatibilities {#sec-release-25.11-incompatibilities}

View file

@ -1491,6 +1491,7 @@
./services/ttys/getty.nix
./services/ttys/gpm.nix
./services/ttys/kmscon.nix
./services/video/broadcast-box.nix
./services/video/epgstation/default.nix
./services/video/frigate.nix
./services/video/go2rtc/default.nix

View file

@ -0,0 +1,274 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
mkIf
mkEnableOption
mkPackageOption
mkOption
attrNames
types
match
optional
optionals
toInt
last
splitString
allUnique
concatStringsSep
all
filter
mapAttrs
any
getExe
maintainers
;
inherit (cfg) settings;
cfg = config.services.broadcast-box;
addressToPort = address: toInt (last (splitString ":" address));
httpPort = cfg.web.port;
tcpMuxPort = addressToPort settings.TCP_MUX_ADDRESS;
httpRedirect = settings.ENABLE_HTTP_REDIRECT or (settings.HTTPS_REDIRECT_PORT != null);
udpPorts =
optional (settings.UDP_MUX_PORT != null) settings.UDP_MUX_PORT
++ optional (settings.UDP_WHEP_PORT != null) settings.UDP_WHEP_PORT
++ optional (settings.UDP_WHIP_PORT != null) settings.UDP_WHIP_PORT;
tcpPorts = optional (settings.TCP_MUX_ADDRESS != null) tcpMuxPort;
webPorts = [ httpPort ] ++ optional httpRedirect settings.HTTPS_REDIRECT_PORT;
in
{
options.services.broadcast-box = {
enable = mkEnableOption "Broadcast Box";
package = mkPackageOption pkgs "broadcast-box" { };
web = {
host = mkOption {
type = types.str;
default = "";
example = "127.0.0.1";
description = ''
Host address the HTTP server listens on. By default the server
listens on all interfaces.
'';
};
port = mkOption {
type = types.port;
default = 8080;
description = ''
Port the HTTP server listens on.
'';
};
openFirewall = mkEnableOption ''
opening the HTTP server port and, if enabled, the HTTPS redirect server
port in the firewall.
'';
};
openFirewall = mkEnableOption ''
opening WebRTC traffic ports in the firewall. Randomly selected ports
will not be opened.
'';
settings = mkOption {
visible = "shallow";
type = types.submodule {
freeformType =
with types;
attrsOf (
nullOr (oneOf [
bool
int
str
])
);
options = {
TCP_MUX_ADDRESS = mkOption {
type = with types; nullOr (strMatching ".*:[0-9]+");
default = null;
};
DISABLE_STATUS = mkOption {
type = types.bool;
default = true;
};
UDP_MUX_PORT = mkOption {
type = with types; nullOr port;
default = null;
};
UDP_WHEP_PORT = mkOption {
type = with types; nullOr port;
default = null;
};
UDP_WHIP_PORT = mkOption {
type = with types; nullOr port;
default = null;
};
ENABLE_HTTP_REDIRECT = mkOption {
type = types.bool;
default = false;
};
HTTPS_REDIRECT_PORT = mkOption {
type = with types; nullOr port;
default = if settings.ENABLE_HTTP_REDIRECT then 80 else null;
};
};
};
default = {
DISABLE_STATUS = true;
};
example = {
DISABLE_STATUS = true;
INCLUDE_PUBLIC_IP_IN_NAT_1_TO_1_IP = true;
UDP_MUX_PORT = 3000;
};
description = ''
Attribute set of environment variables.
<https://github.com/Glimesh/broadcast-box#environment-variables>
:::{.warning}
The status API exposes stream keys so {env}`DISABLE_STATUS` is enabled
by default.
:::
'';
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = !(settings ? HTTP_ADDRESS);
message = ''
The Broadcast Box `HTTP_ADDRESS` variable should not be used. Instead
use the `host` and `port` options.
'';
}
{
assertion = httpRedirect -> settings ? SSL_CERT && settings ? SSL_KEY;
message = ''
The Broadcast Box `ENABLE_HTTP_REDIRECT` variable requires `SSL_CERT`
and `SSL_KEY` to be configured.
'';
}
{
assertion = httpRedirect -> httpPort == 443;
message = ''
Broadcast Box HTTP redirect only works if the HTTP server listen port
is 443.
'';
}
{
assertion = allUnique (tcpPorts ++ webPorts);
message = ''
Broadcast Box configuration contains duplicate TCP ports.
'';
}
{
assertion = all (name: (match "[A-Z0-9_]+" name) != null) (attrNames settings);
message =
let
offenders = filter (name: (match "[A-Z0-9_]+" name) == null) (attrNames settings);
in
''
Broadcast Box `settings` attribute names must be in uppercase snake
case. Invalid attribute name(s): `${concatStringsSep ", " offenders}`
'';
}
];
systemd.services.broadcast-box = {
description = "Broadcast Box";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
startLimitBurst = 3;
startLimitIntervalSec = 180;
environment =
(mapAttrs (
_: value:
if (builtins.typeOf value == "bool") then
if !value then null else "true"
else if (builtins.typeOf value == "int") then
toString value
else
value
) cfg.settings)
// {
APP_ENV = "nixos";
HTTP_ADDRESS = cfg.web.host + ":" + toString cfg.web.port;
};
serviceConfig =
let
priviledgedPort = any (p: p > 0 && p < 1024) (udpPorts ++ tcpPorts ++ webPorts);
in
{
ExecStart = "${getExe cfg.package}";
Restart = "always";
RestartSec = "10s";
DynamicUser = true;
LockPersonality = true;
NoNewPrivileges = true;
PrivateUsers = !priviledgedPort;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectControlGroups = true;
ProtectClock = true;
ProtectProc = "invisible";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProcSubset = "pid";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
CapabilityBoundingSet = if priviledgedPort then [ "CAP_NET_BIND_SERVICE" ] else "";
AmbientCapabilities = mkIf priviledgedPort [ "CAP_NET_BIND_SERVICE" ];
DeviceAllow = "";
MemoryDenyWriteExecute = true;
UMask = "0077";
};
};
networking.firewall = {
allowedTCPPorts = optionals cfg.openFirewall tcpPorts ++ optionals cfg.web.openFirewall webPorts;
allowedUDPPorts = optionals cfg.openFirewall udpPorts;
};
};
meta.maintainers = with maintainers; [ JManch ];
}

View file

@ -264,6 +264,7 @@ in
bpf = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./bpf.nix { };
bpftune = runTest ./bpftune.nix;
breitbandmessung = runTest ./breitbandmessung.nix;
broadcast-box = runTest ./broadcast-box.nix;
brscan5 = runTest ./brscan5.nix;
btrbk = runTest ./btrbk.nix;
btrbk-doas = runTest ./btrbk-doas.nix;

View file

@ -0,0 +1,21 @@
{ pkgs, ... }:
{
name = "broadcast-box";
meta = { inherit (pkgs.broadcast-box.meta) maintainers; };
nodes.machine = {
services.broadcast-box = {
enable = true;
web = {
host = "127.0.0.1";
port = 8080;
};
};
};
testScript = ''
machine.wait_for_unit("broadcast-box.service")
machine.wait_for_open_port(8080)
machine.succeed("curl --fail http://localhost:8080/")
'';
}

View file

@ -0,0 +1,13 @@
diff --git a/main.go b/main.go
index 1814da0..2befc13 100644
--- a/main.go
+++ b/main.go
@@ -175,6 +175,8 @@ func main() {
if os.Getenv("APP_ENV") == "development" {
log.Println("Loading `" + envFileDev + "`")
return godotenv.Load(envFileDev)
+ } else if os.Getenv("APP_ENV") == "nixos" {
+ return nil
} else {
if _, err := os.Stat("./web/build"); os.IsNotExist(err) {
return noBuildDirectoryErr

View file

@ -0,0 +1,69 @@
{
lib,
nixosTests,
fetchFromGitHub,
buildNpmPackage,
buildGoModule,
}:
let
name = "broadcast-box";
version = "0-unstable-2025-06-04";
src = fetchFromGitHub {
repo = "broadcast-box";
owner = "Glimesh";
rev = "a091f147f750759084a2c9d25a12e815e2feebf8";
hash = "sha256-Evhye+DYtFM/VjxqmhH5kU32khvEFUxTUgH9DXytIbo=";
};
frontend = buildNpmPackage {
inherit version;
pname = "${name}-web";
src = "${src}/web";
npmDepsHash = "sha256-e1cCezmF20Q6JEXwPb1asRSXuC/GGaR+ImvrTabLl5c=";
preBuild = ''
# The VITE_API_PATH environment variable is needed
cp "${src}/.env.production" ../
'';
installPhase = ''
mkdir -p $out
cp -r build $out
'';
};
in
buildGoModule {
inherit version src frontend;
pname = name;
vendorHash = "sha256-Jpee7UmG9AB9SOoTv2fPP2l5BmkDPPdciGFu9Naq9h8=";
proxyVendor = true; # fixes darwin/linux hash mismatch
patches = [ ./allow-no-env.patch ];
postPatch = ''
substituteInPlace main.go \
--replace-fail './web/build' '${placeholder "out"}/share'
'';
installPhase = ''
runHook preInstall
mkdir -p $out/share
cp -r $frontend/build/* $out/share
install -Dm755 $GOPATH/bin/broadcast-box -t $out/bin
runHook postInstall
'';
passthru.tests = {
inherit (nixosTests) broadcast-box;
};
meta = {
description = "WebRTC broadcast server";
homepage = "https://github.com/Glimesh/broadcast-box";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ JManch ];
platforms = lib.platforms.unix;
mainProgram = "broadcast-box";
};
}