{ config, lib, pkgs, ... }: with lib; let cfg = config.services.tt-rss; configVersion = 26; cacheDir = "cache"; lockDir = "lock"; feedIconsDir = "feed-icons"; 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 "" ; in pkgs.writeText "config.php" '' plugins.local directory. ''; }; themePackages = mkOption { type = types.listOf types.package; default = []; description = '' 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 = '' 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 = '' 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"; } ]; services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") { ${poolName} = { inherit (cfg) user; 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}"; locations."/" = { index = "index.php"; }; 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}' 0755 ${cfg.user} tt_rss - -" "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -" ]; systemd.services = { phpfpm-tt-rss = mkIf (cfg.pool == "${poolName}") { restartTriggers = [ tt-rss-config pkgs.tt-rss ]; }; tt-rss = { description = "Tiny Tiny RSS feeds update daemon"; preStart = let callSql = e: if cfg.database.type == "pgsql" then '' ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \ ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \ ${config.services.postgresql.package}/bin/psql \ -U ${cfg.database.user} \ ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \ -c '${e}' \ ${cfg.database.name}'' else if cfg.database.type == "mysql" then '' echo '${e}' | ${config.services.mysql.package}/bin/mysql \ -u ${cfg.database.user} \ ${optionalString (cfg.database.password != null) "-p${cfg.database.password}"} \ ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} -P ${toString dbPort}"} \ ${cfg.database.name}'' else ""; in '' rm -rf "${cfg.root}/*" cp -r "${pkgs.tt-rss}/"* "${cfg.root}" ${optionalString (cfg.pluginPackages != []) '' for plugin in ${concatStringsSep " " cfg.pluginPackages}; do cp -r "$plugin"/* "${cfg.root}/plugins.local/" done ''} ${optionalString (cfg.themePackages != []) '' for theme in ${concatStringsSep " " cfg.themePackages}; do cp -r "$theme"/* "${cfg.root}/themes.local/" done ''} ln -sf "${tt-rss-config}" "${cfg.root}/config.php" chmod -R 755 "${cfg.root}" chmod -R ug+rwX "${cfg.root}/${lockDir}" chmod -R ug+rwX "${cfg.root}/${cacheDir}" chmod -R ug+rwX "${cfg.root}/${feedIconsDir}" '' + (optionalString (cfg.database.type == "pgsql") '' exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \ | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//') if [ "$exists" == 'f' ]; then ${callSql "\\i ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"} else echo 'The database contains some data. Leaving it as it is.' fi; '') + (optionalString (cfg.database.type == "mysql") '' exists=$(${callSql "select count(*) > 0 from information_schema.tables where table_schema = schema()"} \ | tail -n+2 | sed -e 's/[ \n\t]*//') if [ "$exists" == '0' ]; then ${callSql "\\. ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"} else echo 'The database contains some data. Leaving it as it is.' fi; ''); serviceConfig = { User = "${cfg.user}"; Group = "tt_rss"; ExecStart = "${pkgs.php}/bin/php ${cfg.root}/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.user; ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; } ]; }; users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") { description = "tt-rss service user"; isSystemUser = true; group = "tt_rss"; }; users.groups.tt_rss = {}; }; }