1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.mirakurun;
7 mirakurun = pkgs.mirakurun;
8 username = config.users.users.mirakurun.name;
9 groupname = config.users.users.mirakurun.group;
10 settingsFmt = pkgs.formats.yaml {};
11
12 polkitRule = pkgs.writeTextDir "share/polkit-1/rules.d/10-mirakurun.rules" ''
13 polkit.addRule(function (action, subject) {
14 if (
15 (action.id == "org.debian.pcsc-lite.access_pcsc" ||
16 action.id == "org.debian.pcsc-lite.access_card") &&
17 subject.user == "${username}"
18 ) {
19 return polkit.Result.YES;
20 }
21 });
22 '';
23in
24 {
25 options = {
26 services.mirakurun = {
27 enable = mkEnableOption mirakurun.meta.description;
28
29 port = mkOption {
30 type = with types; nullOr port;
31 default = 40772;
32 description = ''
33 Port to listen on. If <literal>null</literal>, it won't listen on
34 any port.
35 '';
36 };
37
38 openFirewall = mkOption {
39 type = types.bool;
40 default = false;
41 description = ''
42 Open ports in the firewall for Mirakurun.
43
44 <warning>
45 <para>
46 Exposing Mirakurun to the open internet is generally advised
47 against. Only use it inside a trusted local network, or
48 consider putting it behind a VPN if you want remote access.
49 </para>
50 </warning>
51 '';
52 };
53
54 unixSocket = mkOption {
55 type = with types; nullOr path;
56 default = "/var/run/mirakurun/mirakurun.sock";
57 description = ''
58 Path to unix socket to listen on. If <literal>null</literal>, it
59 won't listen on any unix sockets.
60 '';
61 };
62
63 allowSmartCardAccess = mkOption {
64 type = types.bool;
65 default = true;
66 description = ''
67 Install polkit rules to allow Mirakurun to access smart card readers
68 which is commonly used along with tuner devices.
69 '';
70 };
71
72 serverSettings = mkOption {
73 type = settingsFmt.type;
74 default = {};
75 example = literalExpression ''
76 {
77 highWaterMark = 25165824;
78 overflowTimeLimit = 30000;
79 };
80 '';
81 description = ''
82 Options for server.yml.
83
84 Documentation:
85 <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
86 '';
87 };
88
89 tunerSettings = mkOption {
90 type = with types; nullOr settingsFmt.type;
91 default = null;
92 example = literalExpression ''
93 [
94 {
95 name = "tuner-name";
96 types = [ "GR" "BS" "CS" "SKY" ];
97 dvbDevicePath = "/dev/dvb/adapterX/dvrX";
98 }
99 ];
100 '';
101 description = ''
102 Options which are added to tuners.yml. If none is specified, it will
103 automatically be generated at runtime.
104
105 Documentation:
106 <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
107 '';
108 };
109
110 channelSettings = mkOption {
111 type = with types; nullOr settingsFmt.type;
112 default = null;
113 example = literalExpression ''
114 [
115 {
116 name = "channel";
117 types = "GR";
118 channel = "0";
119 }
120 ];
121 '';
122 description = ''
123 Options which are added to channels.yml. If none is specified, it
124 will automatically be generated at runtime.
125
126 Documentation:
127 <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
128 '';
129 };
130 };
131 };
132
133 config = mkIf cfg.enable {
134 environment.systemPackages = [ mirakurun ] ++ optional cfg.allowSmartCardAccess polkitRule;
135 environment.etc = {
136 "mirakurun/server.yml".source = settingsFmt.generate "server.yml" cfg.serverSettings;
137 "mirakurun/tuners.yml" = mkIf (cfg.tunerSettings != null) {
138 source = settingsFmt.generate "tuners.yml" cfg.tunerSettings;
139 mode = "0644";
140 user = username;
141 group = groupname;
142 };
143 "mirakurun/channels.yml" = mkIf (cfg.channelSettings != null) {
144 source = settingsFmt.generate "channels.yml" cfg.channelSettings;
145 mode = "0644";
146 user = username;
147 group = groupname;
148 };
149 };
150
151 networking.firewall = mkIf cfg.openFirewall {
152 allowedTCPPorts = mkIf (cfg.port != null) [ cfg.port ];
153 };
154
155 users.users.mirakurun = {
156 description = "Mirakurun user";
157 group = "video";
158 isSystemUser = true;
159 };
160
161 services.mirakurun.serverSettings = {
162 logLevel = mkDefault 2;
163 path = mkIf (cfg.unixSocket != null) cfg.unixSocket;
164 port = mkIf (cfg.port != null) cfg.port;
165 };
166
167 systemd.tmpfiles.rules = [
168 "d '/etc/mirakurun' - ${username} ${groupname} - -"
169 ];
170
171 systemd.services.mirakurun = {
172 description = mirakurun.meta.description;
173 wantedBy = [ "multi-user.target" ];
174 after = [ "network.target" ];
175 serviceConfig = {
176 ExecStart = "${mirakurun}/bin/mirakurun-start";
177 User = username;
178 Group = groupname;
179 RuntimeDirectory="mirakurun";
180 StateDirectory="mirakurun";
181 Nice = -10;
182 IOSchedulingClass = "realtime";
183 IOSchedulingPriority = 7;
184 };
185
186 environment = {
187 SERVER_CONFIG_PATH = "/etc/mirakurun/server.yml";
188 TUNERS_CONFIG_PATH = "/etc/mirakurun/tuners.yml";
189 CHANNELS_CONFIG_PATH = "/etc/mirakurun/channels.yml";
190 SERVICES_DB_PATH = "/var/lib/mirakurun/services.json";
191 PROGRAMS_DB_PATH = "/var/lib/mirakurun/programs.json";
192 NODE_ENV = "production";
193 };
194
195 restartTriggers = let
196 getconf = target: config.environment.etc."mirakurun/${target}.yml".source;
197 targets = [
198 "server"
199 ] ++ optional (cfg.tunerSettings != null) "tuners"
200 ++ optional (cfg.channelSettings != null) "channels";
201 in (map getconf targets);
202 };
203 };
204 }