diff --git a/modules/services/web-apps/betanin.nix b/modules/services/web-apps/betanin.nix index 712ad29..729db34 100644 --- a/modules/services/web-apps/betanin.nix +++ b/modules/services/web-apps/betanin.nix @@ -4,14 +4,27 @@ let inherit (lib) mkIf mkOption optionalAttrs types; cfg = config.services.betanin; - yaml = pkgs.formats.yaml { }; - configFile = - if (cfg.beetsConfigFile != null) - then cfg.beetsConfigFile - else yaml.generate "beets-config.yaml" cfg.beetsConfig; - defaultUser = "betanin"; defaultGroup = "betanin"; + + finalSettings = + let + base = lib.filterAttrsRecursive (n: _: lib.hasSuffix "_file" n) cfg.settings; + clean = { + frontend.password = cfg.settings.frontend.password or "@password@"; + clients.api_key = cfg.settings.clients.api_key or "@api_key@"; + }; + in + lib.recursiveUpdate base clean; + + settingsFormat = pkgs.formats.toml { }; + settingsFile = settingsFormat.generate "betanin.toml" finalSettings; + + beetsFormat = pkgs.formats.yaml { }; + beetsFile = + if (cfg.beetsFile != null) + then cfg.beetsFile + else beetsFormat.generate "betanin-beets.yaml" cfg.beetsConfig; in { options = { @@ -54,25 +67,123 @@ in default = "/var/lib/betanin"; }; + settings = mkOption { + type = types.submodule { + freeformType = settinsgFormat.type; + + frontend.username = mkOption { + type = types.str; + default = ""; + description = "Username used to log into the frontend. Must be set."; + }; + + frontend.password = mkOption { + type = types.str; + default = ""; + description = '' + Password used to log into the frontend. Either password or + password_file must be set. + ''; + }; + + frontend.password_file = mkOption { + type = with types; nullOr (either str path); + default = null; + description = '' + File containing the password used to log into the frontend. The + file must be readable by the betanin user/group. + + Using a password file keeps the password out of the Nix store, but + the password is still stored in plain text in the service data + directory. + ''; + }; + + clients.api_key = mkOption { + type = types.nullOr types.str; + default = ""; + description = '' + API key used to access Betanin (e.g., from other services). + ''; + }; + + clients.api_key_file = mkOption { + type = with types; nullOr (either str path); + default = null; + description = '' + File containing the API key used to access Betanin (e.g., from + other services). The file must be readable by the betanin + user/group. + + Using a API key file keeps the API key out of the Nix store, but + the API key is still stored in plain text in the service data + directory. + ''; + + notifications.strings.title = mkOption { + type = types.str; + default = "[betanin] torrent `$name` $status"; + description = "Notification title."; + }; + + notifications.strings.body = mkOption { + type = types.str; + default = "@ $time. view/use the console at http://127.0.0.1:${cfg.port}/$console_path"; + description = "Notification body."; + }; + }; + }; + example = lib.literalExpression '' + { + frontend = { + username = "foo"; + password_file = "/run/secrets/betaninPasswordFile"; + }; + clients = { + api_key_file = "/run/secrets/betaninApiKeyFile"; + }; + server = { + num_parallel_jobs = 1; + }; + } + ''; + description = "Configuration for betanin."; + }; + beetsConfig = mkOption { description = "beets configuration."; - type = yaml.type; + type = beetsFormat.type; default = { }; }; - beetsConfigFile = mkOption { + beetsFile = mkOption { description = "beets configuration file."; - type = nullOr (either str path); + type = with types; nullOr (either str path); default = null; }; }; }; config = mkIf cfg.enable { - assertions = [{ - assertion = (cfg.beetsConfig == { }) != (cfg.beetsConfigFile == null); - message = "either services.betanin.beetsConfig or services.betanin.beetsConfigFile is required"; - }]; + assertions = [ + + { + assertion = cfg.settings.frontend.username != ""; + message = "services.betanin.settings.frontend.username is required"; + } + { + assertion = (cfg.settings.frontend.password == "") != (cfg.settings.frontend.password_file); + message = "services.betanin.settings.frontend.password or services.betanin.settings.frontend.password_file is required"; + } + { + assertion = (cfg.settings.clients.api_key == "") != (cfg.settings.clients.api_key_file); + message = "services.betanin.settings.clients.api_key or services.betanin.settings.clients.api_key_file is required"; + } + { + assertion = (cfg.beetsConfig == { }) != (cfg.beetsFile == null); + message = "either services.betanin.beetsConfig or services.betanin.beetsFile is required"; + } + ]; networking.firwall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; @@ -85,11 +196,29 @@ in environment = { HOME = cfg.dataDir; }; + + script = '' + #!/bin/sh + mkdir -p ${cfg.dataDir}/.config/betanin + mkdir -p ${cfg.dataDir}/.config/beets + mkdir -p ${cfg.dataDir}/.local/share/betanin + cat ${settingsFile} > ${cfg.dataDir}/.config/betanin/config.toml + ln -sf ${beetsFile} ${cfg.dataDir}/.config/betanin/config.toml + '' ++ lib.optionalString (cfg.settings.frontend.password_file != null) '' + sed -i "s/@password@/$(cat ${cfg.settings.frontend.password_file})/" \ + ${cfg.dataDir}/.config/betanin/config.toml + '' ++ lib.optionalString (cfg.settings.frontend.api_key_file != null) '' + sed -i "s/@api_key@/$(cat ${cfg.settings.frontend.api_key_file})/" \ + ${cfg.dataDir}/.config/betanin/config.toml + '' ++ '' + chmod -w ${cfg.dataDir}/.config/betanin/config.toml + ${cfg.package}/bin/betanin --port ${cfg.port} + ''; + serviceConfig = lib.mkMerge [ { User = cfg.user; Group = cfg.group; - ExecStart = "${cfg.package}/bin/betanin --port ${cfg.port}"; PrivateTmp = true; Restart = "always"; WorkingDirectory = cfg.dataDir;