{ config, lib, pkgs, ... }: with lib; let cfg = config.services.tt-rss; configVersion = 26; dbPort = if cfg.database.port == null then (if cfg.database.type == "pgsql" then 5432 else 3306) else cfg.database.port; poolName = "tt-rss"; mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; tt-rss-config = let password = if (cfg.database.password != null) then "'${(escape ["'" "\\"] cfg.database.password)}'" else if (cfg.database.passwordFile != null) then "file_get_contents('${cfg.database.passwordFile}')" else null ; in pkgs.writeText "config.php" '' ''; }; forceArticlePurge = mkOption { type = types.int; default = 0; description = lib.mdDoc '' When this option is not 0, users ability to control feed purging intervals is disabled and all articles (which are not starred) older than this amount of days are purged. ''; }; enableGZipOutput = mkOption { type = types.bool; default = true; description = lib.mdDoc '' Selectively gzip output to improve wire performance. This requires PHP Zlib extension on the server. Enabling this can break tt-rss in several httpd/php configurations, if you experience weird errors and tt-rss failing to start, blank pages after login, or content encoding errors, disable it. ''; }; plugins = mkOption { type = types.listOf types.str; default = ["auth_internal" "note"]; description = lib.mdDoc '' List of plugins to load automatically for all users. System plugins have to be specified here. Please enable at least one authentication plugin here (auth_*). Users may enable other user plugins from Preferences/Plugins but may not disable plugins specified in this list. Disabling auth_internal in this list would automatically disable reset password link on the login form. ''; }; pluginPackages = mkOption { type = types.listOf types.package; default = []; description = lib.mdDoc '' List of plugins to install. The list elements are expected to be derivations. All elements in this derivation are automatically copied to the `plugins.local` directory. ''; }; themePackages = mkOption { type = types.listOf types.package; default = []; description = lib.mdDoc '' List of themes to install. The list elements are expected to be derivations. All elements in this derivation are automatically copied to the `themes.local` directory. ''; }; logDestination = mkOption { type = types.enum ["" "sql" "syslog"]; default = "sql"; description = lib.mdDoc '' Log destination to use. Possible values: sql (uses internal logging you can read in Preferences -> System), syslog - logs to system log. Setting this to blank uses PHP logging (usually to http server error.log). ''; }; extraConfig = mkOption { type = types.lines; default = ""; description = lib.mdDoc '' Additional lines to append to `config.php`. ''; }; }; }; imports = [ (mkRemovedOptionModule ["services" "tt-rss" "checkForUpdates"] '' This option was removed because setting this to true will cause TT-RSS to be unable to start if an automatic update of the code in services.tt-rss.root leads to a database schema upgrade that is not supported by the code active in the Nix store. '') ]; ###### implementation config = mkIf cfg.enable { assertions = [ { assertion = cfg.database.password != null -> cfg.database.passwordFile == null; message = "Cannot set both password and passwordFile"; } { assertion = cfg.database.createLocally -> cfg.database.name == cfg.user && cfg.database.user == cfg.user; message = '' When creating a database via NixOS, the db user and db name must be equal! If you already have an existing DB+user and this assertion is new, you can safely set `services.tt-rss.database.createLocally` to `false` because removal of `ensureUsers` and `ensureDatabases` doesn't have any effect. ''; } ]; services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") { ${poolName} = { inherit (cfg) user; phpPackage = pkgs.php81; settings = mapAttrs (name: mkDefault) { "listen.owner" = "nginx"; "listen.group" = "nginx"; "listen.mode" = "0600"; "pm" = "dynamic"; "pm.max_children" = 75; "pm.start_servers" = 10; "pm.min_spare_servers" = 5; "pm.max_spare_servers" = 20; "pm.max_requests" = 500; "catch_workers_output" = 1; }; }; }; # NOTE: No configuration is done if not using virtual host services.nginx = mkIf (cfg.virtualHost != null) { enable = true; virtualHosts = { ${cfg.virtualHost} = { root = "${cfg.root}/www"; locations."/" = { index = "index.php"; }; locations."^~ /feed-icons" = { root = "${cfg.root}"; }; locations."~ \\.php$" = { extraConfig = '' fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket}; fastcgi_index index.php; ''; }; }; }; }; systemd.tmpfiles.rules = [ "d '${cfg.root}' 0555 ${cfg.user} tt_rss - -" "d '${cfg.root}/lock' 0755 ${cfg.user} tt_rss - -" "d '${cfg.root}/cache' 0755 ${cfg.user} tt_rss - -" "d '${cfg.root}/cache/upload' 0755 ${cfg.user} tt_rss - -" "d '${cfg.root}/cache/images' 0755 ${cfg.user} tt_rss - -" "d '${cfg.root}/cache/export' 0755 ${cfg.user} tt_rss - -" "d '${cfg.root}/feed-icons' 0755 ${cfg.user} tt_rss - -" "L+ '${cfg.root}/www' - - - - ${servedRoot}" ]; systemd.services = { phpfpm-tt-rss = mkIf (cfg.pool == "${poolName}") { restartTriggers = [ servedRoot ]; }; tt-rss = { description = "Tiny Tiny RSS feeds update daemon"; preStart = '' ${pkgs.php81}/bin/php ${cfg.root}/www/update.php --update-schema --force-yes ''; serviceConfig = { User = "${cfg.user}"; Group = "tt_rss"; ExecStart = "${pkgs.php}/bin/php ${cfg.root}/www/update.php --daemon --quiet"; Restart = "on-failure"; RestartSec = "60"; SyslogIdentifier = "tt-rss"; }; wantedBy = [ "multi-user.target" ]; requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; }; }; services.mysql = mkIf mysqlLocal { enable = true; package = mkDefault pkgs.mariadb; ensureDatabases = [ cfg.database.name ]; ensureUsers = [ { name = cfg.user; ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; } ]; }; services.postgresql = mkIf pgsqlLocal { enable = mkDefault true; ensureDatabases = [ cfg.database.name ]; ensureUsers = [ { name = cfg.database.user; ensureDBOwnership = true; } ]; }; users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") { description = "tt-rss service user"; isSystemUser = true; group = "tt_rss"; }; users.groups.tt_rss = {}; }; }