livekit-ingress: init (#409757)

This commit is contained in:
K900
2025-06-26 19:09:06 +03:00
committed by GitHub
5 changed files with 318 additions and 5 deletions
+1
View File
@@ -1197,6 +1197,7 @@
./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
@@ -0,0 +1,181 @@
{
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
View File
@@ -8,6 +8,9 @@
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 ];
@@ -34,6 +37,32 @@ in
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 {
type = lib.types.submodule {
freeformType = format.type;
@@ -44,6 +73,16 @@ in
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;
@@ -79,6 +118,15 @@ in
};
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
@@ -91,6 +139,13 @@ in
];
};
# 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" ];
@@ -102,7 +157,7 @@ in
LoadCredential = [ "livekit-secrets:${cfg.keyFile}" ];
ExecStart = utils.escapeSystemdExecArgs [
(lib.getExe cfg.package)
"--config=${format.generate "livekit.json" cfg.settings}"
"--config=${format.generate "livekit.json" settings}"
"--key-file=/run/credentials/livekit.service/livekit-secrets"
];
DynamicUser = true;
+22 -4
View File
@@ -1,6 +1,7 @@
{
pkgs,
config,
lib,
pkgs,
...
}:
{
@@ -15,11 +16,28 @@
};
settings.port = 8000;
};
specialisation.ingress = {
inheritParentConfig = true;
configuration = {
services.livekit = {
ingress.enable = true;
redis.port = 6379;
};
};
};
};
testScript = ''
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 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"))
'';
}
@@ -0,0 +1,58 @@
{
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";
};
}