Skip to content

Commit 8204df5

Browse files
committed
nixos/lavalink: init
1 parent 4aa840d commit 8204df5

File tree

2 files changed

+336
-0
lines changed

2 files changed

+336
-0
lines changed

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@
408408
./services/audio/icecast.nix
409409
./services/audio/jack.nix
410410
./services/audio/jmusicbot.nix
411+
./services/audio/lavalink.nix
411412
./services/audio/liquidsoap.nix
412413
./services/audio/marytts.nix
413414
./services/audio/mopidy.nix
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
{
2+
config,
3+
pkgs,
4+
lib,
5+
...
6+
}:
7+
8+
let
9+
inherit (lib)
10+
mkOption
11+
mkEnableOption
12+
mkIf
13+
types
14+
;
15+
16+
cfg = config.services.lavalink;
17+
18+
format = pkgs.formats.yaml { };
19+
in
20+
21+
{
22+
options.services.lavalink = {
23+
enable = mkEnableOption "Lavalink";
24+
25+
package = lib.mkPackageOption pkgs "lavalink" { };
26+
27+
password = mkOption {
28+
type = types.nullOr types.str;
29+
default = null;
30+
example = "s3cRe!p4SsW0rD";
31+
description = ''
32+
The password for Lavalink's authentication in plain text.
33+
'';
34+
};
35+
36+
port = mkOption {
37+
type = types.port;
38+
default = 2333;
39+
example = 4567;
40+
description = ''
41+
The port that Lavalink will use.
42+
'';
43+
};
44+
45+
address = mkOption {
46+
type = types.str;
47+
default = "0.0.0.0";
48+
example = "127.0.0.1";
49+
description = ''
50+
The network address to bind to.
51+
'';
52+
};
53+
54+
openFirewall = mkOption {
55+
type = types.bool;
56+
default = false;
57+
example = true;
58+
description = ''
59+
Whether to expose the port to the network.
60+
'';
61+
};
62+
63+
user = mkOption {
64+
type = types.str;
65+
default = "lavalink";
66+
example = "root";
67+
description = ''
68+
The user of the service.
69+
'';
70+
};
71+
72+
group = mkOption {
73+
type = types.str;
74+
default = "lavalink";
75+
example = "medias";
76+
description = ''
77+
The group of the service.
78+
'';
79+
};
80+
81+
home = mkOption {
82+
type = types.str;
83+
default = "/var/lib/lavalink";
84+
example = "/home/lavalink";
85+
description = ''
86+
The home directory for lavalink.
87+
'';
88+
};
89+
90+
enableHttp2 = mkEnableOption "HTTP/2 support";
91+
92+
jvmArgs = mkOption {
93+
type = types.str;
94+
default = "-Xmx4G";
95+
example = "-Djava.io.tmpdir=/var/lib/lavalink/tmp -Xmx6G";
96+
description = ''
97+
Set custom JVM arguments.
98+
'';
99+
};
100+
101+
environmentFile = mkOption {
102+
type = types.nullOr types.str;
103+
default = null;
104+
example = "/run/secrets/lavalink/passwordEnvFile";
105+
description = ''
106+
Add custom environment variables from a file.
107+
See <https://lavalink.dev/configuration/index.html#example-environment-variables> for the full documentation.
108+
'';
109+
};
110+
111+
plugins = mkOption {
112+
type = types.listOf (
113+
types.submodule {
114+
options = {
115+
dependency = mkOption {
116+
type = types.str;
117+
example = "dev.lavalink.youtube:youtube-plugin:1.8.0";
118+
description = ''
119+
The coordinates of the plugin.
120+
'';
121+
};
122+
123+
repository = mkOption {
124+
type = types.str;
125+
example = "https://maven.example.com/releases";
126+
default = "https://maven.lavalink.dev/releases";
127+
description = ''
128+
The plugin repository. Defaults to the lavalink releases repository.
129+
130+
To use the snapshots repository, use <https://maven.lavalink.dev/snapshots> instead
131+
'';
132+
};
133+
134+
hash = mkOption {
135+
type = types.str;
136+
example = lib.fakeHash;
137+
description = ''
138+
The hash of the plugin.
139+
'';
140+
};
141+
142+
configName = mkOption {
143+
type = types.nullOr types.str;
144+
example = "youtube";
145+
default = null;
146+
description = ''
147+
The name of the plugin to use as the key for the plugin configuration.
148+
'';
149+
};
150+
151+
extraConfig = mkOption {
152+
type = types.submodule { freeformType = format.type; };
153+
default = { };
154+
description = ''
155+
The configuration for the plugin.
156+
157+
The {option}`services.lavalink.plugins.*.configName` option must be set.
158+
'';
159+
};
160+
};
161+
}
162+
);
163+
default = [ ];
164+
165+
example = lib.literalExpression ''
166+
[
167+
{
168+
dependency = "dev.lavalink.youtube:youtube-plugin:1.8.0";
169+
repository = "https://maven.lavalink.dev/snapshots";
170+
hash = lib.fakeHash;
171+
configName = "youtube";
172+
extraConfig = {
173+
enabled = true;
174+
allowSearch = true;
175+
allowDirectVideoIds = true;
176+
allowDirectPlaylistIds = true;
177+
};
178+
}
179+
]
180+
'';
181+
182+
description = ''
183+
A list of plugins for lavalink.
184+
'';
185+
};
186+
187+
extraConfig = mkOption {
188+
type = types.submodule { freeformType = format.type; };
189+
190+
description = ''
191+
Configuration to write to {file}`application.yml`.
192+
See <https://lavalink.dev/configuration/#example-applicationyml> for the full documentation.
193+
194+
Individual configuration parameters can be overwritten using environment variables.
195+
See <https://lavalink.dev/configuration/#example-environment-variables> for more information.
196+
'';
197+
198+
default = { };
199+
200+
example = lib.literalExpression ''
201+
{
202+
lavalink.server = {
203+
sources.twitch = true;
204+
205+
filters.volume = true;
206+
};
207+
208+
logging.file.path = "./logs/";
209+
}
210+
'';
211+
};
212+
};
213+
214+
config =
215+
let
216+
pluginSymlinks = lib.concatStringsSep "\n" (
217+
map (
218+
pluginCfg:
219+
let
220+
pluginParts = lib.match ''^(.*?:(.*?):)([0-9]+\.[0-9]+\.[0-9]+)$'' pluginCfg.dependency;
221+
222+
pluginWebPath = lib.replaceStrings [ "." ":" ] [ "/" "/" ] (lib.elemAt pluginParts 0);
223+
224+
pluginFileName = lib.elemAt pluginParts 1;
225+
pluginVersion = lib.elemAt pluginParts 2;
226+
227+
pluginFile = "${pluginFileName}-${pluginVersion}.jar";
228+
pluginUrl = "${pluginCfg.repository}/${pluginWebPath}${pluginVersion}/${pluginFile}";
229+
230+
plugin = pkgs.fetchurl {
231+
url = pluginUrl;
232+
inherit (pluginCfg) hash;
233+
};
234+
in
235+
"ln -sf ${plugin} ${cfg.home}/plugins/${pluginFile}"
236+
) cfg.plugins
237+
);
238+
239+
pluginExtraConfigs = builtins.listToAttrs (
240+
builtins.map (
241+
pluginConfig: lib.attrsets.nameValuePair pluginConfig.configName pluginConfig.extraConfig
242+
) (lib.lists.filter (pluginCfg: pluginCfg.configName != null) cfg.plugins)
243+
);
244+
245+
config = lib.attrsets.recursiveUpdate cfg.extraConfig {
246+
server = {
247+
inherit (cfg) port address;
248+
http2.enabled = cfg.enableHttp2;
249+
};
250+
251+
plugins = pluginExtraConfigs;
252+
lavalink.plugins = (
253+
builtins.map (
254+
pluginConfig:
255+
builtins.removeAttrs pluginConfig [
256+
"name"
257+
"extraConfig"
258+
"hash"
259+
]
260+
) cfg.plugins
261+
);
262+
};
263+
264+
configWithPassword = lib.attrsets.recursiveUpdate config (
265+
lib.attrsets.optionalAttrs (cfg.password != null) { lavalink.server.password = cfg.password; }
266+
);
267+
268+
configFile = format.generate "application.yml" configWithPassword;
269+
in
270+
mkIf cfg.enable {
271+
assertions = [
272+
{
273+
assertion =
274+
!(lib.lists.any (
275+
pluginCfg: pluginCfg.extraConfig != { } && pluginCfg.configName == null
276+
) cfg.plugins);
277+
message = "Plugins with extra configuration need to have the `configName` attribute defined";
278+
}
279+
];
280+
281+
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
282+
283+
users.groups = mkIf (cfg.group == "lavalink") { lavalink = { }; };
284+
users.users = mkIf (cfg.user == "lavalink") {
285+
lavalink = {
286+
inherit (cfg) home;
287+
group = "lavalink";
288+
description = "The user for the Lavalink server";
289+
isSystemUser = true;
290+
};
291+
};
292+
293+
systemd.tmpfiles.settings."10-lavalink" =
294+
let
295+
dirConfig = {
296+
inherit (cfg) user group;
297+
mode = "0700";
298+
};
299+
in
300+
{
301+
"${cfg.home}/plugins".d = mkIf (cfg.plugins != [ ]) dirConfig;
302+
${cfg.home}.d = dirConfig;
303+
};
304+
305+
systemd.services.lavalink = {
306+
description = "Lavalink Service";
307+
308+
wantedBy = [ "multi-user.target" ];
309+
after = [
310+
"syslog.target"
311+
"network.target"
312+
];
313+
314+
script = ''
315+
${pluginSymlinks}
316+
317+
ln -sf ${configFile} ${cfg.home}/application.yml
318+
export _JAVA_OPTIONS="${cfg.jvmArgs}"
319+
320+
${lib.getExe cfg.package}
321+
'';
322+
323+
serviceConfig = {
324+
User = cfg.user;
325+
Group = cfg.group;
326+
327+
Type = "simple";
328+
Restart = "on-failure";
329+
330+
EnvironmentFile = cfg.environmentFile;
331+
WorkingDirectory = cfg.home;
332+
};
333+
};
334+
};
335+
}

0 commit comments

Comments
 (0)