10 cfg = config.services.epgstation;
11 opt = options.services.epgstation;
13 description = "EPGStation: DVR system for Mirakurun-managed TV tuners";
15 username = config.users.users.epgstation.name;
16 groupname = config.users.users.epgstation.group;
18 sock = config.services.mirakurun.unixSocket;
19 option = options.services.mirakurun.unixSocket;
22 yaml = pkgs.formats.yaml { };
23 settingsTemplate = yaml.generate "config.yml" cfg.settings;
24 preStartScript = pkgs.writeScript "epgstation-prestart" ''
25 #!${pkgs.runtimeShell}
27 DB_PASSWORD_FILE=${lib.escapeShellArg cfg.database.passwordFile}
29 if [[ ! -f "$DB_PASSWORD_FILE" ]]; then
30 printf "[FATAL] File containing the DB password was not found in '%s'. Double check the NixOS option '%s'." \
31 "$DB_PASSWORD_FILE" ${lib.escapeShellArg opt.database.passwordFile} >&2
35 DB_PASSWORD="$(head -n1 ${lib.escapeShellArg cfg.database.passwordFile})"
38 touch /etc/epgstation/config.yml
39 chmod 640 /etc/epgstation/config.yml
41 -e "s,@dbPassword@,$DB_PASSWORD,g" \
42 ${settingsTemplate} > /etc/epgstation/config.yml
43 chown "${username}:${groupname}" /etc/epgstation/config.yml
45 # NOTE: Use password authentication, since mysqljs does not yet support auth_socket
46 if [ ! -e /var/lib/epgstation/db-created ]; then
47 ${pkgs.mariadb}/bin/mysql -e \
48 "GRANT ALL ON \`${cfg.database.name}\`.* TO '${username}'@'localhost' IDENTIFIED by '$DB_PASSWORD';"
49 touch /var/lib/epgstation/db-created
53 streamingConfig = lib.importJSON ./streaming.json;
54 logConfig = yaml.generate "logConfig.yml" {
55 appenders.stdout.type = "stdout";
58 appenders = [ "stdout" ];
62 appenders = [ "stdout" ];
66 appenders = [ "stdout" ];
70 appenders = [ "stdout" ];
76 # Deprecate top level options that are redundant.
77 deprecateTopLevelOption =
79 lib.mkRenamedOptionModule
98 lib.mkRemovedOptionModule (
107 meta.maintainers = with lib.maintainers; [ midchildan ];
110 (deprecateTopLevelOption [ "port" ])
111 (deprecateTopLevelOption [ "socketioPort" ])
112 (deprecateTopLevelOption [ "clientSocketioPort" ])
113 (removeOption [ "basicAuth" ] "Use a TLS-terminated reverse proxy with authentication instead.")
116 options.services.epgstation = {
117 enable = lib.mkEnableOption description;
119 package = lib.mkPackageOption pkgs "epgstation" { };
121 ffmpeg = lib.mkPackageOption pkgs "ffmpeg" {
122 default = "ffmpeg-headless";
123 example = "ffmpeg-full";
126 usePreconfiguredStreaming = lib.mkOption {
127 type = lib.types.bool;
130 Use preconfigured default streaming options.
133 <https://github.com/l3tnun/EPGStation/blob/master/config/config.yml.template>
137 openFirewall = lib.mkOption {
138 type = lib.types.bool;
141 Open ports in the firewall for the EPGStation web interface.
144 Exposing EPGStation to the open internet is generally advised
145 against. Only use it inside a trusted local network, or consider
146 putting it behind a VPN if you want remote access.
152 name = lib.mkOption {
153 type = lib.types.str;
154 default = "epgstation";
156 Name of the MySQL database that holds EPGStation's data.
160 passwordFile = lib.mkOption {
161 type = lib.types.path;
162 example = "/run/keys/epgstation-db-password";
164 A file containing the password for the database named
165 {option}`database.name`.
170 # The defaults for some options come from the upstream template
171 # configuration, which is the one that users would get if they follow the
172 # upstream instructions. This is, in some cases, different from the
173 # application defaults. Some options like encodeProcessNum and
174 # concurrentEncodeNum doesn't have an optimal default value that works for
175 # all hardware setups and/or performance requirements. For those kind of
176 # options, the application default wouldn't always result in the expected
177 # out-of-the-box behavior because it's the responsibility of the user to
178 # configure them according to their needs. In these cases, the value in the
179 # upstream template configuration should serve as a "good enough" default.
180 settings = lib.mkOption {
182 Options to add to config.yml.
185 <https://github.com/l3tnun/EPGStation/blob/master/doc/conf-manual.md>
191 conflictPriority = 10;
194 type = lib.types.submodule {
195 freeformType = yaml.type;
197 options.port = lib.mkOption {
198 type = lib.types.port;
201 HTTP port for EPGStation to listen on.
205 options.socketioPort = lib.mkOption {
206 type = lib.types.port;
207 default = cfg.settings.port + 1;
208 defaultText = lib.literalExpression "config.${opt.settings}.port + 1";
210 Socket.io port for EPGStation to listen on. It is valid to share
211 ports with {option}`${opt.settings}.port`.
215 options.clientSocketioPort = lib.mkOption {
216 type = lib.types.port;
217 default = cfg.settings.socketioPort;
218 defaultText = lib.literalExpression "config.${opt.settings}.socketioPort";
220 Socket.io port that the web client is going to connect to. This may
221 be different from {option}`${opt.settings}.socketioPort` if
222 EPGStation is hidden behind a reverse proxy.
226 options.mirakurunPath =
229 type = lib.types.str;
230 default = "http+unix://${lib.replaceStrings [ "/" ] [ "%2F" ] sock}";
231 defaultText = lib.literalExpression ''
232 "http+unix://''${lib.replaceStrings ["/"] ["%2F"] config.${option}}"
234 example = "http://localhost:40772";
235 description = "URL to connect to Mirakurun.";
238 options.encodeProcessNum = lib.mkOption {
239 type = lib.types.ints.positive;
242 The maximum number of processes that EPGStation would allow to run
243 at the same time for encoding or streaming videos.
247 options.concurrentEncodeNum = lib.mkOption {
248 type = lib.types.ints.positive;
251 The maximum number of encoding jobs that EPGStation would run at the
256 options.encode = lib.mkOption {
257 type = with lib.types; listOf attrs;
258 description = "Encoding presets for recorded videos.";
262 cmd = "%NODE% ${cfg.package}/libexec/enc.js";
266 defaultText = lib.literalExpression ''
270 cmd = "%NODE% config.${opt.package}/libexec/enc.js";
280 config = lib.mkIf cfg.enable {
283 assertion = !(lib.hasAttr "readOnlyOnce" cfg.settings);
285 The option config.${opt.settings}.readOnlyOnce can no longer be used
286 since it's been removed. No replacements are available.
292 "epgstation/epgUpdaterLogConfig.yml".source = logConfig;
293 "epgstation/operatorLogConfig.yml".source = logConfig;
294 "epgstation/serviceLogConfig.yml".source = logConfig;
297 networking.firewall = lib.mkIf cfg.openFirewall {
298 allowedTCPPorts = with cfg.settings; [
304 users.users.epgstation = {
305 description = "EPGStation user";
306 group = config.users.groups.epgstation.name;
309 # NPM insists on creating ~/.npm
310 home = "/var/cache/epgstation";
313 users.groups.epgstation = { };
315 services.mirakurun.enable = lib.mkDefault true;
318 enable = lib.mkDefault true;
319 package = lib.mkDefault pkgs.mariadb;
320 ensureDatabases = [ cfg.database.name ];
321 # FIXME: enable once mysqljs supports auth_socket
322 # https://github.com/mysqljs/mysql/issues/1507
326 # ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
330 services.epgstation.settings =
333 dbtype = lib.mkDefault "mysql";
335 socketPath = lib.mkDefault "/run/mysqld/mysqld.sock";
337 password = lib.mkDefault "@dbPassword@";
338 database = cfg.database.name;
341 ffmpeg = lib.mkDefault "${cfg.ffmpeg}/bin/ffmpeg";
342 ffprobe = lib.mkDefault "${cfg.ffmpeg}/bin/ffprobe";
344 # for disambiguation with TypeScript files
345 recordedFileExtension = lib.mkDefault ".m2ts";
350 (lib.mkIf cfg.usePreconfiguredStreaming streamingConfig)
353 systemd.tmpfiles.settings."10-epgstation" = lib.listToAttrs (
357 lib.nameValuePair dir {
365 "/var/lib/epgstation/key"
366 "/var/lib/epgstation/streamfiles"
367 "/var/lib/epgstation/drop"
368 "/var/lib/epgstation/recorded"
369 "/var/lib/epgstation/thumbnail"
370 "/var/lib/epgstation/db/subscribers"
371 "/var/lib/epgstation/db/migrations/mysql"
372 "/var/lib/epgstation/db/migrations/postgres"
373 "/var/lib/epgstation/db/migrations/sqlite"
377 systemd.services.epgstation = {
380 wantedBy = [ "multi-user.target" ];
383 ++ lib.optional config.services.mirakurun.enable "mirakurun.service"
384 ++ lib.optional config.services.mysql.enable "mysql.service";
386 environment.NODE_ENV = "production";
389 ExecStart = "${cfg.package}/bin/epgstation start";
390 ExecStartPre = "+${preStartScript}";
393 CacheDirectory = "epgstation";
394 StateDirectory = "epgstation";
395 LogsDirectory = "epgstation";
396 ConfigurationDirectory = "epgstation";