From 8204df50e696515481a3f4415ffdb0a93398c55c Mon Sep 17 00:00:00 2001 From: nanoyaki Date: Thu, 26 Sep 2024 19:01:01 +0200 Subject: [PATCH] nixos/lavalink: init --- nixos/modules/module-list.nix | 1 + nixos/modules/services/audio/lavalink.nix | 335 ++++++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 nixos/modules/services/audio/lavalink.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 15059df3f23d..e00a1e1d52e4 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -408,6 +408,7 @@ ./services/audio/icecast.nix ./services/audio/jack.nix ./services/audio/jmusicbot.nix + ./services/audio/lavalink.nix ./services/audio/liquidsoap.nix ./services/audio/marytts.nix ./services/audio/mopidy.nix diff --git a/nixos/modules/services/audio/lavalink.nix b/nixos/modules/services/audio/lavalink.nix new file mode 100644 index 000000000000..8cb37017bf96 --- /dev/null +++ b/nixos/modules/services/audio/lavalink.nix @@ -0,0 +1,335 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (lib) + mkOption + mkEnableOption + mkIf + types + ; + + cfg = config.services.lavalink; + + format = pkgs.formats.yaml { }; +in + +{ + options.services.lavalink = { + enable = mkEnableOption "Lavalink"; + + package = lib.mkPackageOption pkgs "lavalink" { }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + example = "s3cRe!p4SsW0rD"; + description = '' + The password for Lavalink's authentication in plain text. + ''; + }; + + port = mkOption { + type = types.port; + default = 2333; + example = 4567; + description = '' + The port that Lavalink will use. + ''; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + example = "127.0.0.1"; + description = '' + The network address to bind to. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to expose the port to the network. + ''; + }; + + user = mkOption { + type = types.str; + default = "lavalink"; + example = "root"; + description = '' + The user of the service. + ''; + }; + + group = mkOption { + type = types.str; + default = "lavalink"; + example = "medias"; + description = '' + The group of the service. + ''; + }; + + home = mkOption { + type = types.str; + default = "/var/lib/lavalink"; + example = "/home/lavalink"; + description = '' + The home directory for lavalink. + ''; + }; + + enableHttp2 = mkEnableOption "HTTP/2 support"; + + jvmArgs = mkOption { + type = types.str; + default = "-Xmx4G"; + example = "-Djava.io.tmpdir=/var/lib/lavalink/tmp -Xmx6G"; + description = '' + Set custom JVM arguments. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/run/secrets/lavalink/passwordEnvFile"; + description = '' + Add custom environment variables from a file. + See for the full documentation. + ''; + }; + + plugins = mkOption { + type = types.listOf ( + types.submodule { + options = { + dependency = mkOption { + type = types.str; + example = "dev.lavalink.youtube:youtube-plugin:1.8.0"; + description = '' + The coordinates of the plugin. + ''; + }; + + repository = mkOption { + type = types.str; + example = "https://maven.example.com/releases"; + default = "https://maven.lavalink.dev/releases"; + description = '' + The plugin repository. Defaults to the lavalink releases repository. + + To use the snapshots repository, use instead + ''; + }; + + hash = mkOption { + type = types.str; + example = lib.fakeHash; + description = '' + The hash of the plugin. + ''; + }; + + configName = mkOption { + type = types.nullOr types.str; + example = "youtube"; + default = null; + description = '' + The name of the plugin to use as the key for the plugin configuration. + ''; + }; + + extraConfig = mkOption { + type = types.submodule { freeformType = format.type; }; + default = { }; + description = '' + The configuration for the plugin. + + The {option}`services.lavalink.plugins.*.configName` option must be set. + ''; + }; + }; + } + ); + default = [ ]; + + example = lib.literalExpression '' + [ + { + dependency = "dev.lavalink.youtube:youtube-plugin:1.8.0"; + repository = "https://maven.lavalink.dev/snapshots"; + hash = lib.fakeHash; + configName = "youtube"; + extraConfig = { + enabled = true; + allowSearch = true; + allowDirectVideoIds = true; + allowDirectPlaylistIds = true; + }; + } + ] + ''; + + description = '' + A list of plugins for lavalink. + ''; + }; + + extraConfig = mkOption { + type = types.submodule { freeformType = format.type; }; + + description = '' + Configuration to write to {file}`application.yml`. + See for the full documentation. + + Individual configuration parameters can be overwritten using environment variables. + See for more information. + ''; + + default = { }; + + example = lib.literalExpression '' + { + lavalink.server = { + sources.twitch = true; + + filters.volume = true; + }; + + logging.file.path = "./logs/"; + } + ''; + }; + }; + + config = + let + pluginSymlinks = lib.concatStringsSep "\n" ( + map ( + pluginCfg: + let + pluginParts = lib.match ''^(.*?:(.*?):)([0-9]+\.[0-9]+\.[0-9]+)$'' pluginCfg.dependency; + + pluginWebPath = lib.replaceStrings [ "." ":" ] [ "/" "/" ] (lib.elemAt pluginParts 0); + + pluginFileName = lib.elemAt pluginParts 1; + pluginVersion = lib.elemAt pluginParts 2; + + pluginFile = "${pluginFileName}-${pluginVersion}.jar"; + pluginUrl = "${pluginCfg.repository}/${pluginWebPath}${pluginVersion}/${pluginFile}"; + + plugin = pkgs.fetchurl { + url = pluginUrl; + inherit (pluginCfg) hash; + }; + in + "ln -sf ${plugin} ${cfg.home}/plugins/${pluginFile}" + ) cfg.plugins + ); + + pluginExtraConfigs = builtins.listToAttrs ( + builtins.map ( + pluginConfig: lib.attrsets.nameValuePair pluginConfig.configName pluginConfig.extraConfig + ) (lib.lists.filter (pluginCfg: pluginCfg.configName != null) cfg.plugins) + ); + + config = lib.attrsets.recursiveUpdate cfg.extraConfig { + server = { + inherit (cfg) port address; + http2.enabled = cfg.enableHttp2; + }; + + plugins = pluginExtraConfigs; + lavalink.plugins = ( + builtins.map ( + pluginConfig: + builtins.removeAttrs pluginConfig [ + "name" + "extraConfig" + "hash" + ] + ) cfg.plugins + ); + }; + + configWithPassword = lib.attrsets.recursiveUpdate config ( + lib.attrsets.optionalAttrs (cfg.password != null) { lavalink.server.password = cfg.password; } + ); + + configFile = format.generate "application.yml" configWithPassword; + in + mkIf cfg.enable { + assertions = [ + { + assertion = + !(lib.lists.any ( + pluginCfg: pluginCfg.extraConfig != { } && pluginCfg.configName == null + ) cfg.plugins); + message = "Plugins with extra configuration need to have the `configName` attribute defined"; + } + ]; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + users.groups = mkIf (cfg.group == "lavalink") { lavalink = { }; }; + users.users = mkIf (cfg.user == "lavalink") { + lavalink = { + inherit (cfg) home; + group = "lavalink"; + description = "The user for the Lavalink server"; + isSystemUser = true; + }; + }; + + systemd.tmpfiles.settings."10-lavalink" = + let + dirConfig = { + inherit (cfg) user group; + mode = "0700"; + }; + in + { + "${cfg.home}/plugins".d = mkIf (cfg.plugins != [ ]) dirConfig; + ${cfg.home}.d = dirConfig; + }; + + systemd.services.lavalink = { + description = "Lavalink Service"; + + wantedBy = [ "multi-user.target" ]; + after = [ + "syslog.target" + "network.target" + ]; + + script = '' + ${pluginSymlinks} + + ln -sf ${configFile} ${cfg.home}/application.yml + export _JAVA_OPTIONS="${cfg.jvmArgs}" + + ${lib.getExe cfg.package} + ''; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + + Type = "simple"; + Restart = "on-failure"; + + EnvironmentFile = cfg.environmentFile; + WorkingDirectory = cfg.home; + }; + }; + }; +}