livekit-ingress: init (#409757)

K900 da21cd73 1329e6bb

Changed files
+318 -5
nixos
modules
tests
networking
pkgs
by-name
li
livekit-ingress
+1
nixos/modules/module-list.nix
···
./services/networking/lambdabot.nix
./services/networking/legit.nix
./services/networking/libreswan.nix
./services/networking/livekit.nix
./services/networking/lldpd.nix
./services/networking/logmein-hamachi.nix
···
./services/networking/lambdabot.nix
./services/networking/legit.nix
./services/networking/libreswan.nix
+
./services/networking/livekit-ingress.nix
./services/networking/livekit.nix
./services/networking/lldpd.nix
./services/networking/logmein-hamachi.nix
+181
nixos/modules/services/networking/livekit-ingress.nix
···
···
+
{
+
config,
+
lib,
+
pkgs,
+
utils,
+
...
+
}:
+
let
+
cfg = config.services.livekit.ingress;
+
format = pkgs.formats.yaml { };
+
settings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
+
+
isLocallyDistributed = config.services.livekit.enable;
+
in
+
{
+
meta.maintainers = with lib.maintainers; [ k900 ];
+
options.services.livekit.ingress = {
+
enable = lib.mkEnableOption "the livekit ingress service";
+
package = lib.mkPackageOption pkgs "livekit-ingress" { };
+
+
openFirewall = {
+
rtc = lib.mkOption {
+
type = lib.types.bool;
+
default = false;
+
description = "Open WebRTC ports in the firewall.";
+
};
+
+
rtmp = lib.mkOption {
+
type = lib.types.bool;
+
default = false;
+
description = "Open RTMP port in the firewall.";
+
};
+
+
whip = lib.mkOption {
+
type = lib.types.bool;
+
default = false;
+
description = "Open WHIP port in the firewall.";
+
};
+
};
+
+
settings = lib.mkOption {
+
type = lib.types.submodule {
+
freeformType = format.type;
+
options = {
+
rtmp_port = lib.mkOption {
+
type = lib.types.port;
+
default = 1935;
+
description = "TCP port for RTMP connections";
+
};
+
+
whip_port = lib.mkOption {
+
type = lib.types.port;
+
default = 8080;
+
description = "TCP port for WHIP connections";
+
};
+
+
redis = {
+
address = lib.mkOption {
+
type = with lib.types; nullOr str;
+
default =
+
if isLocallyDistributed then
+
"${config.services.livekit.redis.host}:${toString config.services.livekit.redis.port}"
+
else
+
null;
+
example = "redis.example.com:6379";
+
defaultText = "Host and port of the local livekit redis instance, if enabled, or null";
+
description = "Address or hostname and port for redis connection";
+
};
+
+
};
+
+
rtc_config = {
+
port_range_start = lib.mkOption {
+
type = lib.types.int;
+
default = 50000;
+
description = "Start of UDP port range for WebRTC";
+
};
+
+
port_range_end = lib.mkOption {
+
type = lib.types.int;
+
default = 51000;
+
description = "End of UDP port range for WebRTC";
+
};
+
+
use_external_ip = lib.mkOption {
+
type = lib.types.bool;
+
default = false;
+
description = ''
+
When set to true, attempts to discover the host's public IP via STUN.
+
This is useful for cloud environments such as AWS & Google where hosts have an internal IP that maps to an external one.
+
'';
+
};
+
};
+
};
+
};
+
default = { };
+
description = ''
+
LiveKit Ingress configuration.
+
+
See <https://github.com/livekit/ingress?tab=readme-ov-file#config> for possible options.
+
'';
+
example = {
+
prometheus_port = 9039;
+
cpu_cost = {
+
rtmp_cpu_cost = 3.0;
+
whip_cpu_cost = 1.0;
+
};
+
};
+
};
+
+
environmentFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
description = ''
+
Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
+
+
Use this to specify `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`.
+
'';
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
networking.firewall = {
+
allowedTCPPorts = lib.mkMerge [
+
(lib.mkIf cfg.openFirewall.rtmp [ cfg.settings.rtmp_port ])
+
(lib.mkIf cfg.openFirewall.whip [ cfg.settings.whip_port ])
+
];
+
allowedUDPPortRanges = lib.mkIf cfg.openFirewall.rtc [
+
{
+
from = cfg.settings.rtc_config.port_range_start;
+
to = cfg.settings.rtc_config.port_range_end;
+
}
+
];
+
};
+
+
systemd.services.livekit-ingress = {
+
description = "LiveKit Ingress server";
+
documentation = [ "https://docs.livekit.io/home/self-hosting/ingress/" ];
+
wantedBy = [ "multi-user.target" ];
+
wants = [ "network-online.target" ];
+
after = [ "network-online.target" ];
+
+
serviceConfig = {
+
ExecStart = utils.escapeSystemdExecArgs [
+
(lib.getExe cfg.package)
+
"--config=${format.generate "ingress.yaml" settings}"
+
];
+
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+
DynamicUser = true;
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
PrivateDevices = true;
+
PrivateMounts = true;
+
PrivateUsers = true;
+
RestrictAddressFamilies = [
+
"AF_INET"
+
"AF_INET6"
+
"AF_NETLINK"
+
];
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
ProtectHome = true;
+
SystemCallArchitectures = "native";
+
SystemCallFilter = [
+
"@system-service"
+
"~@privileged"
+
"~@resources"
+
];
+
Restart = "on-failure";
+
RestartSec = 5;
+
UMask = "077";
+
};
+
};
+
};
+
}
+56 -1
nixos/modules/services/networking/livekit.nix
···
let
cfg = config.services.livekit;
format = pkgs.formats.json { };
in
{
meta.maintainers = with lib.maintainers; [ quadradical ];
···
type = lib.types.bool;
default = false;
description = "Opens port range for LiveKit on the firewall.";
};
settings = lib.mkOption {
···
description = "Main TCP port for RoomService and RTC endpoint.";
};
rtc = {
port_range_start = lib.mkOption {
type = lib.types.int;
···
};
config = lib.mkIf cfg.enable {
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.settings.port
···
];
};
systemd.services.livekit = {
description = "LiveKit SFU server";
documentation = [ "https://docs.livekit.io" ];
···
LoadCredential = [ "livekit-secrets:${cfg.keyFile}" ];
ExecStart = utils.escapeSystemdExecArgs [
(lib.getExe cfg.package)
-
"--config=${format.generate "livekit.json" cfg.settings}"
"--key-file=/run/credentials/livekit.service/livekit-secrets"
];
DynamicUser = true;
···
let
cfg = config.services.livekit;
format = pkgs.formats.json { };
+
settings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
+
+
isLocallyDistributed = config.services.livekit.ingress.enable;
in
{
meta.maintainers = with lib.maintainers; [ quadradical ];
···
type = lib.types.bool;
default = false;
description = "Opens port range for LiveKit on the firewall.";
+
};
+
+
redis = {
+
createLocally = lib.mkOption {
+
type = lib.types.bool;
+
default = isLocallyDistributed;
+
defaultText = "true if any other Livekit component is enabled locally else false";
+
description = "Whether to set up a local redis instance.";
+
};
+
+
host = lib.mkOption {
+
type = with lib.types; nullOr str;
+
default = if cfg.redis.createLocally then "127.0.0.1" else null;
+
defaultText = "127.0.0.1 if config.services.livekit.redis.createLocally else null";
+
description = ''
+
Address to bind local redis instance to.
+
'';
+
};
+
+
port = lib.mkOption {
+
type = with lib.types; nullOr port;
+
default = null;
+
description = ''
+
Port to bind local redis instance to.
+
'';
+
};
};
settings = lib.mkOption {
···
description = "Main TCP port for RoomService and RTC endpoint.";
};
+
redis = {
+
address = lib.mkOption {
+
type = with lib.types; nullOr str;
+
default = if isLocallyDistributed then "${cfg.redis.host}:${toString cfg.redis.port}" else null;
+
defaultText = lib.literalExpression "Local Redis host/port when a local ingress component is enabled else null";
+
example = "redis.example.com:6379";
+
description = "Host and port used to connect to a redis instance.";
+
};
+
};
+
rtc = {
port_range_start = lib.mkOption {
type = lib.types.int;
···
};
config = lib.mkIf cfg.enable {
+
assertions = [
+
{
+
assertion = cfg.redis.createLocally -> cfg.redis.port != null;
+
message = ''
+
When `services.livekit.redis.createLocally` is enabled `services.livekit.redis.port` must be configured.
+
'';
+
}
+
];
+
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.settings.port
···
];
};
+
# Provision a redis instance, when livekit-ingress (or later livekit-egress) are enabled on the same host
+
services.redis.servers.livekit = lib.mkIf cfg.redis.createLocally {
+
enable = true;
+
bind = cfg.redis.host;
+
port = cfg.redis.port;
+
};
+
systemd.services.livekit = {
description = "LiveKit SFU server";
documentation = [ "https://docs.livekit.io" ];
···
LoadCredential = [ "livekit-secrets:${cfg.keyFile}" ];
ExecStart = utils.escapeSystemdExecArgs [
(lib.getExe cfg.package)
+
"--config=${format.generate "livekit.json" settings}"
"--key-file=/run/credentials/livekit.service/livekit-secrets"
];
DynamicUser = true;
+22 -4
nixos/tests/networking/livekit.nix
···
{
pkgs,
-
lib,
...
}:
{
···
key = "f6lQGaHtM5HfgZjIcec3cOCRfiDqIine4CpZZnqdT5cE";
};
settings.port = 8000;
};
};
testScript = ''
-
machine.wait_for_unit("livekit.service")
-
machine.wait_for_open_port(8000)
-
machine.succeed("curl 127.0.0.1:8000 -L --fail")
'';
}
···
{
+
config,
+
lib,
pkgs,
...
}:
{
···
key = "f6lQGaHtM5HfgZjIcec3cOCRfiDqIine4CpZZnqdT5cE";
};
settings.port = 8000;
+
};
+
+
specialisation.ingress = {
+
inheritParentConfig = true;
+
configuration = {
+
services.livekit = {
+
ingress.enable = true;
+
redis.port = 6379;
+
};
+
};
};
};
testScript = ''
+
with subtest("Test livekit service"):
+
machine.wait_for_unit("livekit.service")
+
machine.wait_for_open_port(8000)
+
machine.succeed("curl 127.0.0.1:8000 -L --fail")
+
+
with subtest("Test locally distributed livekit service with ingress component"):
+
machine.succeed("${config.nodes.machine.system.build.toplevel}/specialisation/ingress/bin/switch-to-configuration test")
+
machine.wait_for_unit("livekit-ingress.service")
+
machine.wait_for_open_port(8080)
+
machine.log(machine.succeed("curl --fail -X OPTIONS 127.0.0.1:8080/whip/test"))
'';
}
+58
pkgs/by-name/li/livekit-ingress/package.nix
···
···
+
{
+
lib,
+
buildGoModule,
+
fetchFromGitHub,
+
pkg-config,
+
makeWrapper,
+
glib,
+
gst_all_1,
+
}:
+
+
buildGoModule rec {
+
pname = "livekit-ingress";
+
version = "1.4.3";
+
+
src = fetchFromGitHub {
+
owner = "livekit";
+
repo = "ingress";
+
tag = "v${version}";
+
hash = "sha256-gt1oIAKEBwQWqDCLSsRgoe7oIk5jDNReN+dFYUNnRUc=";
+
};
+
+
vendorHash = "sha256-fttI+xNzHiDWKGkl20oGJOcWffElmmqNd7gbb5FiQZc=";
+
+
subPackages = [ "cmd/server" ];
+
+
postInstall = ''
+
mv $out/bin/server $out/bin/ingress
+
wrapProgram $out/bin/ingress --suffix GST_PLUGIN_SYSTEM_PATH_1_0 ":" $GST_PLUGIN_SYSTEM_PATH_1_0
+
'';
+
+
nativeBuildInputs = [
+
pkg-config
+
makeWrapper
+
];
+
+
buildInputs = with gst_all_1; [
+
glib
+
gstreamer
+
gst-plugins-base
+
gst-plugins-good
+
gst-plugins-bad
+
gst-plugins-ugly
+
gst-libav
+
];
+
+
# there are no actual tests, and we don't need to spend
+
# another 5 minutes of cgo to figure that out
+
doCheck = false;
+
+
meta = {
+
description = "Ingest streams (RTMP/WHIP) or files (HLS, MP4) to LiveKit WebRTC";
+
changelog = "https://github.com/livekit/ingress/releases/tag/${src.tag}";
+
homepage = "https://github.com/livekit/ingress";
+
license = lib.licenses.asl20;
+
maintainers = with lib.maintainers; [ k900 ];
+
mainProgram = "ingress";
+
};
+
}