1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8let
9 cfg = config.services.livekit;
10 format = pkgs.formats.json { };
11 settings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
12
13 isLocallyDistributed = config.services.livekit.ingress.enable;
14in
15{
16 meta.maintainers = with lib.maintainers; [ quadradical ];
17 options.services.livekit = {
18 enable = lib.mkEnableOption "the livekit server";
19 package = lib.mkPackageOption pkgs "livekit" { };
20
21 keyFile = lib.mkOption {
22 type = lib.types.path;
23 description = ''
24 LiveKit key file holding one or multiple application secrets. Use `livekit-server generate-keys` to generate a random key name and secret.
25
26 The file should have the format `<keyname>: <secret>`.
27 Example:
28 `lk-jwt-service: f6lQGaHtM5HfgZjIcec3cOCRfiDqIine4CpZZnqdT5cE`
29
30 Individual key/secret pairs need to be passed to clients to connect to this instance.
31 '';
32 };
33
34 openFirewall = lib.mkOption {
35 type = lib.types.bool;
36 default = false;
37 description = "Opens port range for LiveKit on the firewall.";
38 };
39
40 redis = {
41 createLocally = lib.mkOption {
42 type = lib.types.bool;
43 default = isLocallyDistributed;
44 defaultText = "true if any other Livekit component is enabled locally else false";
45 description = "Whether to set up a local redis instance.";
46 };
47
48 host = lib.mkOption {
49 type = with lib.types; nullOr str;
50 default = if cfg.redis.createLocally then "127.0.0.1" else null;
51 defaultText = "127.0.0.1 if config.services.livekit.redis.createLocally else null";
52 description = ''
53 Address to bind local redis instance to.
54 '';
55 };
56
57 port = lib.mkOption {
58 type = with lib.types; nullOr port;
59 default = null;
60 description = ''
61 Port to bind local redis instance to.
62 '';
63 };
64 };
65
66 settings = lib.mkOption {
67 type = lib.types.submodule {
68 freeformType = format.type;
69 options = {
70 port = lib.mkOption {
71 type = lib.types.port;
72 default = 7880;
73 description = "Main TCP port for RoomService and RTC endpoint.";
74 };
75
76 redis = {
77 address = lib.mkOption {
78 type = with lib.types; nullOr str;
79 default = if isLocallyDistributed then "${cfg.redis.host}:${toString cfg.redis.port}" else null;
80 defaultText = lib.literalExpression "Local Redis host/port when a local ingress component is enabled else null";
81 example = "redis.example.com:6379";
82 description = "Host and port used to connect to a redis instance.";
83 };
84 };
85
86 rtc = {
87 port_range_start = lib.mkOption {
88 type = lib.types.port;
89 default = 50000;
90 description = "Start of UDP port range for WebRTC";
91 };
92
93 port_range_end = lib.mkOption {
94 type = lib.types.port;
95 default = 51000;
96 description = "End of UDP port range for WebRTC";
97 };
98
99 use_external_ip = lib.mkOption {
100 type = lib.types.bool;
101 default = false;
102 description = ''
103 When set to true, attempts to discover the host's public IP via STUN.
104 This is useful for cloud environments such as AWS & Google where hosts have an internal IP that maps to an external one.
105 '';
106 };
107 };
108 };
109 };
110 default = { };
111 description = ''
112 LiveKit configuration file expressed in nix.
113
114 For an example configuration, see <https://docs.livekit.io/home/self-hosting/deployment/#configuration>.
115 For all possible values, see <https://github.com/livekit/livekit/blob/master/config-sample.yaml>.
116 '';
117 };
118 };
119
120 config = lib.mkIf cfg.enable {
121 assertions = [
122 {
123 assertion = cfg.redis.createLocally -> cfg.redis.port != null;
124 message = ''
125 When `services.livekit.redis.createLocally` is enabled `services.livekit.redis.port` must be configured.
126 '';
127 }
128 ];
129
130 networking.firewall = lib.mkIf cfg.openFirewall {
131 allowedTCPPorts = [
132 cfg.settings.port
133 ];
134 allowedUDPPortRanges = [
135 {
136 from = cfg.settings.rtc.port_range_start;
137 to = cfg.settings.rtc.port_range_end;
138 }
139 ];
140 };
141
142 # Provision a redis instance, when livekit-ingress (or later livekit-egress) are enabled on the same host
143 services.redis.servers.livekit = lib.mkIf cfg.redis.createLocally {
144 enable = true;
145 bind = cfg.redis.host;
146 port = cfg.redis.port;
147 };
148
149 systemd.services.livekit = {
150 description = "LiveKit SFU server";
151 documentation = [ "https://docs.livekit.io" ];
152 wantedBy = [ "multi-user.target" ];
153 wants = [ "network-online.target" ];
154 after = [ "network-online.target" ];
155
156 serviceConfig = {
157 LoadCredential = [ "livekit-secrets:${cfg.keyFile}" ];
158 ExecStart = utils.escapeSystemdExecArgs [
159 (lib.getExe cfg.package)
160 "--config=${format.generate "livekit.json" settings}"
161 "--key-file=/run/credentials/livekit.service/livekit-secrets"
162 ];
163 DynamicUser = true;
164 LockPersonality = true;
165 MemoryDenyWriteExecute = true;
166 ProtectClock = true;
167 ProtectControlGroups = true;
168 ProtectHostname = true;
169 ProtectKernelLogs = true;
170 ProtectKernelModules = true;
171 ProtectKernelTunables = true;
172 PrivateDevices = true;
173 PrivateMounts = true;
174 PrivateUsers = true;
175 RestrictAddressFamilies = [
176 "AF_INET"
177 "AF_INET6"
178 "AF_NETLINK"
179 ];
180 RestrictNamespaces = true;
181 RestrictRealtime = true;
182 ProtectHome = true;
183 SystemCallArchitectures = "native";
184 SystemCallFilter = [
185 "@system-service"
186 "~@privileged"
187 "~@resources"
188 ];
189 Restart = "on-failure";
190 RestartSec = 5;
191 UMask = "077";
192 };
193 };
194 };
195}