diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 430872242d57..febd3a3548b8 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1735,6 +1735,7 @@ ./services/web-apps/suwayomi-server.nix ./services/web-apps/szurubooru.nix ./services/web-apps/trilium.nix + ./services/web-apps/tt-rss.nix ./services/web-apps/tuliprox.nix ./services/web-apps/umami.nix ./services/web-apps/vikunja.nix diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 5733f0b4bf9c..8b9a1c998520 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -290,9 +290,6 @@ in (mkRemovedOptionModule [ "services" "sourcehut" ] '' The sourcehut packages and the corresponding module have been removed due to being broken and unmaintained. '') - (mkRemovedOptionModule [ "services" "tt-rss" ] '' - The tt-rss package and module have been removed, since upstream development ceased 2025-11-01 and the source is no longer available officially. - '') (mkRemovedOptionModule [ "services" "tvheadend" ] "The tvheadend package and the corresponding module have been removed as nobody was willing to maintain them and they were stuck on an unmaintained version that required FFmpeg 4; please see https://github.com/NixOS/nixpkgs/pull/332259 if you are interested in maintaining a newer version." ) diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix new file mode 100644 index 000000000000..607b99537f30 --- /dev/null +++ b/nixos/modules/services/web-apps/tt-rss.nix @@ -0,0 +1,714 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; +let + cfg = config.services.tt-rss; + + inherit (cfg) phpPackage; + + 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 = '' + 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 = '' + 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. + ''; + }; + + phpPackage = lib.mkOption { + type = lib.types.package; + default = pkgs.php; + defaultText = "pkgs.php"; + description = '' + php package to use for php fpm and update daemon. + ''; + }; + + plugins = mkOption { + type = types.listOf types.str; + default = [ + "auth_internal" + "note" + ]; + description = '' + 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 = '' + 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 = '' + 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). + ''; + }; + + updateDaemon = { + commandFlags = mkOption { + type = types.str; + default = "--quiet"; + description = '' + Command-line flags passed to the update daemon. + The default --quiet flag mutes all logging, including errors. + ''; + }; + }; + + 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"; + } + { + 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; + inherit phpPackage; + 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"; + }; + + # some clients might still access this old path directly, forward them to the API instead + # e.g. https://apps.apple.com/de/app/tiny-reader-rss/id689519762 at version 2.2.0 + locations."~* /feed-icons/(\\d+)\\.ico" = { + return = "302 /public.php?op=feed_icon&id=$1"; + }; + + 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}/cache/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 = '' + ${phpPackage}/bin/php ${cfg.root}/www/update.php --update-schema --force-yes + ''; + + serviceConfig = { + User = "${cfg.user}"; + Group = "tt_rss"; + ExecStart = "${phpPackage}/bin/php ${cfg.root}/www/update.php --daemon ${cfg.updateDaemon.commandFlags}"; + Restart = "on-failure"; + RestartSec = "60"; + SyslogIdentifier = "tt-rss"; + }; + + wantedBy = [ "multi-user.target" ]; + requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.target"; + after = [ + "network.target" + ] + ++ optional mysqlLocal "mysql.service" + ++ optional pgsqlLocal "postgresql.target"; + }; + }; + + 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 = { }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index ba951d160591..dea336cb1173 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1582,6 +1582,7 @@ in trickster = runTest ./trickster.nix; trilium-server = runTestOn [ "x86_64-linux" ] ./trilium-server.nix; tsm-client-gui = runTest ./tsm-client-gui.nix; + tt-rss = runTest ./web-apps/tt-rss.nix; ttyd = runTest ./web-servers/ttyd.nix; tuliprox = runTest ./tuliprox.nix; tuned = runTest ./tuned.nix; diff --git a/nixos/tests/web-apps/tt-rss.nix b/nixos/tests/web-apps/tt-rss.nix new file mode 100644 index 000000000000..933a6419923c --- /dev/null +++ b/nixos/tests/web-apps/tt-rss.nix @@ -0,0 +1,45 @@ +{ ... }: +{ + name = "tt-rss-nixos"; + + nodes.machine = + { pkgs, ... }: + { + services.tt-rss = { + enable = true; + virtualHost = "localhost"; + selfUrlPath = "http://localhost/"; + pluginPackages = with pkgs; [ + tt-rss-plugin-auth-ldap + tt-rss-plugin-feediron + ]; + plugins = [ + "auth_internal" + "feediron" + "note" + ]; + singleUserMode = true; + themePackages = with pkgs; [ tt-rss-theme-feedly ]; + }; + }; + + testScript = '' + import json + import re + machine.wait_for_unit("tt-rss.service") + + matches = re.search('__csrf_token = "([^"]*)"', machine.succeed("curl -sSfL --cookie cjar --cookie-jar cjar -sSfL http://localhost/")) + if matches is None: + assert False, "CSRF token not found" + csrf_token = matches.group(1) + + # Ensure themes are loaded. No API found for these, so it's a crude check. + preference_page = machine.succeed("curl -sSfL --cookie cjar --cookie-jar cjar http://localhost/backend.php?op=Pref_Prefs") + assert "feedly" in preference_page + + plugins = json.loads(machine.succeed(f"curl -sSfL --cookie cjar --cookie-jar cjar 'http://localhost/backend.php' -X POST --data-raw 'op=Pref_Prefs&method=getPluginsList&csrf_token={csrf_token}'"))["plugins"] + expected_plugins = ["auth_internal", "auth_ldap", "feediron", "note"]; + found_plugins = [p["name"] for p in plugins if p["name"] in expected_plugins] + assert len(found_plugins) == len(expected_plugins), f"Expected plugins {expected_plugins}, found {found_plugins}" + ''; +} diff --git a/pkgs/by-name/tt/tt-rss-plugin-auth-ldap/package.nix b/pkgs/by-name/tt/tt-rss-plugin-auth-ldap/package.nix new file mode 100644 index 000000000000..713fd3978416 --- /dev/null +++ b/pkgs/by-name/tt/tt-rss-plugin-auth-ldap/package.nix @@ -0,0 +1,34 @@ +{ + lib, + nixosTests, + stdenv, + fetchFromGitHub, +}: + +stdenv.mkDerivation { + pname = "tt-rss-plugin-auth-ldap"; + version = "unstable-2022-11-30"; + + src = fetchFromGitHub { + owner = "hydrian"; + repo = "TTRSS-Auth-LDAP"; + rev = "582ade49fd433a30b403caa1d0689fca5f3c99e1"; + sha256 = "sha256-favz/2KvWqvv8ehTv3gc7TBbFDjkrOmutChnyKPgces="; + }; + + installPhase = '' + install -D plugins/auth_ldap/init.php $out/auth_ldap/init.php + ''; + + passthru = { + tests = { inherit (nixosTests) tt-rss; }; + }; + + meta = with lib; { + description = "Plugin for TT-RSS to authenticate users via ldap"; + license = licenses.asl20; + homepage = "https://github.com/hydrian/TTRSS-Auth-LDAP"; + maintainers = with maintainers; [ mic92 ]; + platforms = platforms.all; + }; +} diff --git a/pkgs/by-name/tt/tt-rss-plugin-data-migration/package.nix b/pkgs/by-name/tt/tt-rss-plugin-data-migration/package.nix new file mode 100644 index 000000000000..c5c484eda3e9 --- /dev/null +++ b/pkgs/by-name/tt/tt-rss-plugin-data-migration/package.nix @@ -0,0 +1,33 @@ +{ + lib, + stdenv, + fetchgit, +}: + +stdenv.mkDerivation { + pname = "tt-rss-plugin-data-migration"; + version = "0-unstable-2023-11-01"; + + src = fetchgit { + url = "https://git.tt-rss.org/fox/ttrss-data-migration.git"; + rev = "e13d5f97b4887ce7b57b3d76228d838dec15963d"; + hash = "sha256-xnbR5IQ0h7ilxchNj55ROZdq1L7MIAwv3/00k09WTTs="; + }; + + installPhase = '' + runHook preInstall + + install -D init.php $out/data_migration/init.php + + runHook postInstall + ''; + + meta = { + description = "Plugin for TT-RSS to exports and imports *all* articles of a specific user via neutral format (JSON files in a ZIP archive)"; + # this plugin doesn't have a license file + license = lib.licenses.unfree; + homepage = "https://git.tt-rss.org/fox/ttrss-data-migration.git/"; + maintainers = with lib.maintainers; [ wrvsrx ]; + platforms = lib.platforms.all; + }; +} diff --git a/pkgs/by-name/tt/tt-rss-plugin-feediron/package.nix b/pkgs/by-name/tt/tt-rss-plugin-feediron/package.nix new file mode 100644 index 000000000000..8e92d249f6f4 --- /dev/null +++ b/pkgs/by-name/tt/tt-rss-plugin-feediron/package.nix @@ -0,0 +1,38 @@ +{ + lib, + stdenv, + fetchFromGitHub, + tt-rss, +}: + +stdenv.mkDerivation rec { + pname = "tt-rss-plugin-feediron"; + version = "1.33"; + + src = fetchFromGitHub { + owner = "feediron"; + repo = "ttrss_plugin-feediron"; + rev = "v${version}"; + sha256 = "sha256-KU4XQJNK7Ua7rZaXA32lv16RlloCysAb54E5kEY847A="; + }; + + installPhase = '' + mkdir -p $out/feediron + + cp -r bin filters init.php preftab recipes $out/feediron/ + ''; + + meta = with lib; { + description = "Evolution of ttrss_plugin-af_feedmod"; + longDescription = '' + This is a plugin for Tiny Tiny RSS (tt-rss). + It allows you to replace an article's contents by the contents of an element on the linked URL's page + + i.e. create a "full feed". + ''; + license = licenses.mit; + homepage = "https://github.com/feediron/ttrss_plugin-feediron"; + maintainers = with maintainers; [ milogert ]; + inherit (tt-rss.meta) platforms; + }; +} diff --git a/pkgs/by-name/tt/tt-rss-plugin-ff-instagram/package.nix b/pkgs/by-name/tt/tt-rss-plugin-ff-instagram/package.nix new file mode 100644 index 000000000000..cdae35b9baff --- /dev/null +++ b/pkgs/by-name/tt/tt-rss-plugin-ff-instagram/package.nix @@ -0,0 +1,37 @@ +{ + lib, + stdenv, + fetchFromGitHub, +}: + +stdenv.mkDerivation { + pname = "tt-rss-plugin-ff-instagram"; + version = "0-unstable-2019-01-10"; # No release, see https://github.com/wltb/ff_instagram/issues/6 + + src = fetchFromGitHub { + owner = "wltb"; + repo = "ff_instagram"; + rev = "0366ffb18c4d490c8fbfba2f5f3367a5af23cfe8"; + sha256 = "0vvzl6wi6jmrqknsfddvckjgsgfizz1d923d1nyrpzjfn6bda1vk"; + }; + + installPhase = '' + mkdir -p $out/ff_instagram + + cp *.php $out/ff_instagram + ''; + + meta = with lib; { + broken = true; # Plugin code does not conform to plugin API changes. See https://github.com/wltb/ff_instagram/issues/13 + description = "Plugin for Tiny Tiny RSS that allows to fetch posts from Instagram user sites"; + longDescription = '' + Plugin for Tiny Tiny RSS that allows to fetch posts from Instagram user sites. + + The name of the plugin in TT-RSS is 'ff_instagram'. + ''; + license = licenses.agpl3Plus; + homepage = "https://github.com/wltb/ff_instagram"; + maintainers = with maintainers; [ gileri ]; + platforms = platforms.all; + }; +} diff --git a/pkgs/by-name/tt/tt-rss-plugin-freshapi/package.nix b/pkgs/by-name/tt/tt-rss-plugin-freshapi/package.nix new file mode 100644 index 000000000000..bf86938eb852 --- /dev/null +++ b/pkgs/by-name/tt/tt-rss-plugin-freshapi/package.nix @@ -0,0 +1,35 @@ +{ + lib, + stdenv, + fetchFromGitHub, +}: + +stdenv.mkDerivation { + pname = "tt-rss-plugin-freshapi"; + version = "0-unstable-2024-11-14"; + + src = fetchFromGitHub { + owner = "eric-pierce"; + repo = "freshapi"; + rev = "44c98f12e8a4423501fc6d8cb7903cca11094dc6"; + hash = "sha256-1cQ4QMrXOdtelAbmMEuhWJPFi5XrAoR3IGlFzb8122k="; + }; + + installPhase = '' + runHook preInstall + + install -D init.php $out/freshapi/init.php + install -D api/greader.php $out/freshapi/api/greader.php + install -D api/freshapi.php $out/freshapi/api/freshapi.php + + runHook postInstall + ''; + + meta = { + description = "FreshRSS / Google Reader API Plugin for Tiny-Tiny RSS"; + license = lib.licenses.agpl3Only; + homepage = "https://github.com/eric-pierce/freshapi"; + maintainers = with lib.maintainers; [ wrvsrx ]; + platforms = lib.platforms.all; + }; +} diff --git a/pkgs/by-name/tt/tt-rss-theme-feedly/package.nix b/pkgs/by-name/tt/tt-rss-theme-feedly/package.nix new file mode 100644 index 000000000000..b211d25c17a5 --- /dev/null +++ b/pkgs/by-name/tt/tt-rss-theme-feedly/package.nix @@ -0,0 +1,38 @@ +{ + lib, + nixosTests, + stdenv, + fetchFromGitHub, +}: + +stdenv.mkDerivation rec { + pname = "tt-rss-theme-feedly"; + version = "4.1.0"; + + src = fetchFromGitHub { + owner = "levito"; + repo = "tt-rss-feedly-theme"; + rev = "v${version}"; + sha256 = "sha256-3mD1aY7gjdvucRzY7sLmZ1RsHtraAg1RGE/3uDp6/o4="; + }; + + dontBuild = true; + + installPhase = '' + mkdir $out + + cp -ra feedly *.css $out + ''; + + passthru = { + tests = { inherit (nixosTests) tt-rss; }; + }; + + meta = with lib; { + description = "Feedly theme for Tiny Tiny RSS"; + license = licenses.mit; + homepage = "https://github.com/levito/tt-rss-feedly-theme"; + maintainers = with maintainers; [ das_j ]; + platforms = platforms.all; + }; +} diff --git a/pkgs/by-name/tt/tt-rss/package.nix b/pkgs/by-name/tt/tt-rss/package.nix new file mode 100644 index 000000000000..aed3e6f154b2 --- /dev/null +++ b/pkgs/by-name/tt/tt-rss/package.nix @@ -0,0 +1,49 @@ +# nixpkgs-update: no auto update +{ + lib, + stdenv, + fetchgit, + nixosTests, + unstableGitUpdater, +}: + +stdenv.mkDerivation rec { + pname = "tt-rss"; + version = "0-unstable-2025-11-01"; + + src = fetchgit { + url = "https://github.com/tt-rss/tt-rss.git"; + rev = "912162ad811869af334232d32fe6c79b3cf095ca"; + hash = "sha256-2U9V4MTWGNZzdVr9AlH/S7KdBGPap+mm5/KieWLcF1A="; + }; + + installPhase = '' + runHook preInstall + + mkdir $out + cp -ra * $out/ + + # see the code of Config::get_version(). you can check that the version in + # the footer of the preferences pages is not UNKNOWN + echo "${version}" > $out/version_static.txt + + runHook postInstall + ''; + + passthru = { + tests = { inherit (nixosTests) tt-rss; }; + updateScript = unstableGitUpdater { hardcodeZeroVersion = true; }; + }; + + meta = with lib; { + description = "Web-based news feed (RSS/Atom) aggregator"; + license = licenses.gpl3Plus; + homepage = "https://tt-rss.org"; + maintainers = with maintainers; [ + gileri + globin + zohl + ]; + platforms = platforms.all; + }; +} diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index 32bd07b4a2a6..4044cd356c65 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -1611,13 +1611,6 @@ mapAliases { trilium-next-server = trilium-server; # Added 2025-08-30 trojita = throw "'trojita' has been dropped as it depends on KDE Gear 5, and is unmaintained"; # Added 2025-08-20 trust-dns = throw "'trust-dns' has been renamed to/replaced by 'hickory-dns'"; # Converted to throw 2025-10-27 - tt-rss = throw "'tt-rss' has been removed, as it was discontinued on 2025-11-01"; # Added 2025-10-03 - tt-rss-plugin-auth-ldap = throw "'tt-rss-plugin-auth-ldap' has been removed, as tt-rss was discontinued and removed"; # Added 2025-10-03 - tt-rss-plugin-data-migration = throw "'tt-rss-plugin-data-migration' has been removed, as tt-rss was discontinued and removed"; # Added 2025-10-03 - tt-rss-plugin-feediron = throw "'tt-rss-plugin-feediron' has been removed, as tt-rss was discontinued and removed"; # Added 2025-10-03 - tt-rss-plugin-feedly = throw "'tt-rss-plugin-feedly' has been removed, as tt-rss was discontinued and removed"; # Added 2025-10-03 - tt-rss-plugin-ff-instagram = throw "'tt-rss-plugin-ff-instagram' has been removed, as tt-rss was discontinued and removed"; # Added 2025-10-03 - tt-rss-plugin-freshapi = throw "'tt-rss-plugin-freshapi' has been removed, as tt-rss was discontinued and removed"; # Added 2025-10-03 tvbrowser-bin = throw "'tvbrowser-bin' has been renamed to/replaced by 'tvbrowser'"; # Converted to throw 2025-10-27 typst-fmt = throw "'typst-fmt' has been renamed to/replaced by 'typstfmt'"; # Converted to throw 2025-10-27 typstfmt = throw "'typstfmt' has been removed due to lack of upstream maintenance, consider using 'typstyle' instead"; # Added 2025-10-26