{ config, lib, options, pkgs, ... }: with lib; let gid = config.ids.gids.mediatomb; cfg = config.services.mediatomb; opt = options.services.mediatomb; name = cfg.package.pname; pkg = cfg.package; optionYesNo = option: if option then "yes" else "no"; # configuration on media directory mediaDirectory = { options = { path = mkOption { type = types.str; description = lib.mdDoc '' Absolute directory path to the media directory to index. ''; }; recursive = mkOption { type = types.bool; default = false; description = lib.mdDoc "Whether the indexation must take place recursively or not."; }; hidden-files = mkOption { type = types.bool; default = true; description = lib.mdDoc "Whether to index the hidden files or not."; }; }; }; toMediaDirectory = d: "\n"; transcodingConfig = if cfg.transcoding then with pkgs; '' audio/mpeg no yes no video/mpeg yes yes yes '' else '' ''; configText = optionalString (! cfg.customCfg) '' ${cfg.serverName} uuid:${cfg.uuid} ${cfg.dataDir} ${cfg.interface} ${pkg}/share/${name}/web ${name}.db ${optionalString cfg.dsmSupport '' redsonic.com 105 ''} ${optionalString cfg.tg100Support '' 101 ''} * video ${concatMapStrings toMediaDirectory cfg.mediaDirectories} ${pkg}/share/${name}/js/common.js ${pkg}/share/${name}/js/playlists.js ${pkg}/share/${name}/js/import.js ${optionalString cfg.ps3Support '' ''} ${optionalString cfg.dsmSupport '' ''} ${transcodingConfig} ''; defaultFirewallRules = { # udp 1900 port needs to be opened for SSDP (not configurable within # mediatomb/gerbera) cf. # http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup allowedUDPPorts = [ 1900 cfg.port ]; allowedTCPPorts = [ cfg.port ]; }; in { ###### interface options = { services.mediatomb = { enable = mkOption { type = types.bool; default = false; description = lib.mdDoc '' Whether to enable the Gerbera/Mediatomb DLNA server. ''; }; serverName = mkOption { type = types.str; default = "Gerbera (Mediatomb)"; description = lib.mdDoc '' How to identify the server on the network. ''; }; package = mkOption { type = types.package; default = pkgs.gerbera; defaultText = literalExpression "pkgs.gerbera"; description = lib.mdDoc '' Underlying package to be used with the module. ''; }; ps3Support = mkOption { type = types.bool; default = false; description = lib.mdDoc '' Whether to enable ps3 specific tweaks. WARNING: incompatible with DSM 320 support. ''; }; dsmSupport = mkOption { type = types.bool; default = false; description = lib.mdDoc '' Whether to enable D-Link DSM 320 specific tweaks. WARNING: incompatible with ps3 support. ''; }; tg100Support = mkOption { type = types.bool; default = false; description = lib.mdDoc '' Whether to enable Telegent TG100 specific tweaks. ''; }; transcoding = mkOption { type = types.bool; default = false; description = lib.mdDoc '' Whether to enable transcoding. ''; }; dataDir = mkOption { type = types.path; default = "/var/lib/${name}"; defaultText = literalExpression ''"/var/lib/''${config.${opt.package}.pname}"''; description = lib.mdDoc '' The directory where Gerbera/Mediatomb stores its state, data, etc. ''; }; pcDirectoryHide = mkOption { type = types.bool; default = true; description = lib.mdDoc '' Whether to list the top-level directory or not (from upnp client standpoint). ''; }; user = mkOption { type = types.str; default = "mediatomb"; description = lib.mdDoc "User account under which the service runs."; }; group = mkOption { type = types.str; default = "mediatomb"; description = lib.mdDoc "Group account under which the service runs."; }; port = mkOption { type = types.int; default = 49152; description = lib.mdDoc '' The network port to listen on. ''; }; interface = mkOption { type = types.str; default = ""; description = lib.mdDoc '' A specific interface to bind to. ''; }; openFirewall = mkOption { type = types.bool; default = false; description = lib.mdDoc '' If false (the default), this is up to the user to declare the firewall rules. If true, this opens port 1900 (tcp and udp) and the port specified by {option}`sercvices.mediatomb.port`. If the option {option}`services.mediatomb.interface` is set, the firewall rules opened are dedicated to that interface. Otherwise, those rules are opened globally. ''; }; uuid = mkOption { type = types.str; default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687"; description = lib.mdDoc '' A unique (on your network) to identify the server by. ''; }; mediaDirectories = mkOption { type = with types; listOf (submodule mediaDirectory); default = []; description = lib.mdDoc '' Declare media directories to index. ''; example = [ { path = "/data/pictures"; recursive = false; hidden-files = false; } { path = "/data/audio"; recursive = true; hidden-files = false; } ]; }; customCfg = mkOption { type = types.bool; default = false; description = lib.mdDoc '' Allow the service to create and use its own config file inside the `dataDir` as configured by {option}`services.mediatomb.dataDir`. Deactivated by default, the service then runs with the configuration generated from this module. Otherwise, when enabled, no service configuration is generated. Gerbera/Mediatomb then starts using config.xml within the configured `dataDir`. It's up to the user to make a correct configuration file. ''; }; }; }; ###### implementation config = let binaryCommand = "${pkg}/bin/${name}"; interfaceFlag = optionalString ( cfg.interface != "") "--interface ${cfg.interface}"; configFlag = optionalString (! cfg.customCfg) "--config ${pkgs.writeText "config.xml" configText}"; in mkIf cfg.enable { systemd.services.mediatomb = { description = "${cfg.serverName} media Server"; # Gerbera might fail if the network interface is not available on startup # https://github.com/gerbera/gerbera/issues/1324 after = [ "network.target" "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig.ExecStart = "${binaryCommand} --port ${toString cfg.port} ${interfaceFlag} ${configFlag} --home ${cfg.dataDir}"; serviceConfig.User = cfg.user; serviceConfig.Group = cfg.group; }; users.groups = optionalAttrs (cfg.group == "mediatomb") { mediatomb.gid = gid; }; users.users = optionalAttrs (cfg.user == "mediatomb") { mediatomb = { isSystemUser = true; group = cfg.group; home = cfg.dataDir; createHome = true; description = "${name} DLNA Server User"; }; }; # Open firewall only if users enable it networking.firewall = mkMerge [ (mkIf (cfg.openFirewall && cfg.interface != "") { interfaces."${cfg.interface}" = defaultFirewallRules; }) (mkIf (cfg.openFirewall && cfg.interface == "") defaultFirewallRules) ]; }; }