{ 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 = ''
Absolute directory path to the media directory to index.
'';
};
recursive = mkOption {
type = types.bool;
default = false;
description = "Whether the indexation must take place recursively or not.";
};
hidden-files = mkOption {
type = types.bool;
default = true;
description = "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.
# https://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 = ''
Whether to enable the Gerbera/Mediatomb DLNA server.
'';
};
serverName = mkOption {
type = types.str;
default = "Gerbera (Mediatomb)";
description = ''
How to identify the server on the network.
'';
};
package = mkPackageOption pkgs "gerbera" { };
ps3Support = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable ps3 specific tweaks.
WARNING: incompatible with DSM 320 support.
'';
};
dsmSupport = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable D-Link DSM 320 specific tweaks.
WARNING: incompatible with ps3 support.
'';
};
tg100Support = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable Telegent TG100 specific tweaks.
'';
};
transcoding = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable transcoding.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/${name}";
defaultText = literalExpression ''"/var/lib/''${config.${opt.package}.pname}"'';
description = ''
The directory where Gerbera/Mediatomb stores its state, data, etc.
'';
};
pcDirectoryHide = mkOption {
type = types.bool;
default = true;
description = ''
Whether to list the top-level directory or not (from upnp client standpoint).
'';
};
user = mkOption {
type = types.str;
default = "mediatomb";
description = "User account under which the service runs.";
};
group = mkOption {
type = types.str;
default = "mediatomb";
description = "Group account under which the service runs.";
};
port = mkOption {
type = types.port;
default = 49152;
description = ''
The network port to listen on.
'';
};
interface = mkOption {
type = types.str;
default = "";
description = ''
A specific interface to bind to.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
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 = ''
A unique (on your network) to identify the server by.
'';
};
mediaDirectories = mkOption {
type = with types; listOf (submodule mediaDirectory);
default = [];
description = ''
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 = ''
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
wants = [ "network-online.target" ];
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)
];
};
}