{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.mirakurun;
mirakurun = pkgs.mirakurun;
username = config.users.users.mirakurun.name;
groupname = config.users.users.mirakurun.group;
settingsFmt = pkgs.formats.yaml {};
polkitRule = pkgs.writeTextDir "share/polkit-1/rules.d/10-mirakurun.rules" ''
polkit.addRule(function (action, subject) {
if (
(action.id == "org.debian.pcsc-lite.access_pcsc" ||
action.id == "org.debian.pcsc-lite.access_card") &&
subject.user == "${username}"
) {
return polkit.Result.YES;
}
});
'';
in
{
options = {
services.mirakurun = {
enable = mkEnableOption "the Mirakurun DVR Tuner Server";
port = mkOption {
type = with types; nullOr port;
default = 40772;
description = ''
Port to listen on. If null, it won't listen on
any port.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Open ports in the firewall for Mirakurun.
Exposing Mirakurun to the open internet is generally advised
against. Only use it inside a trusted local network, or
consider putting it behind a VPN if you want remote access.
'';
};
unixSocket = mkOption {
type = with types; nullOr path;
default = "/var/run/mirakurun/mirakurun.sock";
description = ''
Path to unix socket to listen on. If null, it
won't listen on any unix sockets.
'';
};
allowSmartCardAccess = mkOption {
type = types.bool;
default = true;
description = ''
Install polkit rules to allow Mirakurun to access smart card readers
which is commonly used along with tuner devices.
'';
};
serverSettings = mkOption {
type = settingsFmt.type;
default = {};
example = literalExpression ''
{
highWaterMark = 25165824;
overflowTimeLimit = 30000;
};
'';
description = ''
Options for server.yml.
Documentation:
'';
};
tunerSettings = mkOption {
type = with types; nullOr settingsFmt.type;
default = null;
example = literalExpression ''
[
{
name = "tuner-name";
types = [ "GR" "BS" "CS" "SKY" ];
dvbDevicePath = "/dev/dvb/adapterX/dvrX";
}
];
'';
description = ''
Options which are added to tuners.yml. If none is specified, it will
automatically be generated at runtime.
Documentation:
'';
};
channelSettings = mkOption {
type = with types; nullOr settingsFmt.type;
default = null;
example = literalExpression ''
[
{
name = "channel";
types = "GR";
channel = "0";
}
];
'';
description = ''
Options which are added to channels.yml. If none is specified, it
will automatically be generated at runtime.
Documentation:
'';
};
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ mirakurun ] ++ optional cfg.allowSmartCardAccess polkitRule;
environment.etc = {
"mirakurun/server.yml".source = settingsFmt.generate "server.yml" cfg.serverSettings;
"mirakurun/tuners.yml" = mkIf (cfg.tunerSettings != null) {
source = settingsFmt.generate "tuners.yml" cfg.tunerSettings;
mode = "0644";
user = username;
group = groupname;
};
"mirakurun/channels.yml" = mkIf (cfg.channelSettings != null) {
source = settingsFmt.generate "channels.yml" cfg.channelSettings;
mode = "0644";
user = username;
group = groupname;
};
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = mkIf (cfg.port != null) [ cfg.port ];
};
users.users.mirakurun = {
description = "Mirakurun user";
group = "video";
isSystemUser = true;
};
services.mirakurun.serverSettings = {
logLevel = mkDefault 2;
path = mkIf (cfg.unixSocket != null) cfg.unixSocket;
port = mkIf (cfg.port != null) cfg.port;
};
systemd.tmpfiles.rules = [
"d '/etc/mirakurun' - ${username} ${groupname} - -"
];
systemd.services.mirakurun = {
description = mirakurun.meta.description;
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart = "${mirakurun}/bin/mirakurun-start";
User = username;
Group = groupname;
RuntimeDirectory="mirakurun";
StateDirectory="mirakurun";
Nice = -10;
IOSchedulingClass = "realtime";
IOSchedulingPriority = 7;
};
environment = {
SERVER_CONFIG_PATH = "/etc/mirakurun/server.yml";
TUNERS_CONFIG_PATH = "/etc/mirakurun/tuners.yml";
CHANNELS_CONFIG_PATH = "/etc/mirakurun/channels.yml";
SERVICES_DB_PATH = "/var/lib/mirakurun/services.json";
PROGRAMS_DB_PATH = "/var/lib/mirakurun/programs.json";
NODE_ENV = "production";
};
restartTriggers = let
getconf = target: config.environment.etc."mirakurun/${target}.yml".source;
targets = [
"server"
] ++ optional (cfg.tunerSettings != null) "tuners"
++ optional (cfg.channelSettings != null) "channels";
in (map getconf targets);
};
};
}