{ 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); }; }; }