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