From 74b66f785b29076740c822afafcab99fa33768f7 Mon Sep 17 00:00:00 2001 From: Vonfry Date: Fri, 27 Mar 2026 20:47:12 +0800 Subject: [PATCH 1/2] nixos/module: init rsshub --- .../manual/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/rsshub.nix | 138 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 nixos/modules/services/web-apps/rsshub.nix diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 5f056851c8dc..cc4ea223552e 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -100,6 +100,8 @@ - [turborepo-remote-cache](https://ducktors.github.io/turborepo-remote-cache/), an open-source implementation of the [Turborepo custom remote cache server](https://turbo.build/repo/docs/core-concepts/remote-caching#self-hosting). Available as [services.turborepo-remote-cache](options.html#opt-services.turborepo-remote-cache). +- [RSSHub](https://github.com/DIYgod/RSSHub), a service to convert many sources into rss. Available as `services.rsshub`. + - [Komodo Periphery](https://github.com/moghtech/komodo), a multi-server Docker and Git deployment agent by Komodo. Available as [services.komodo-periphery](#opt-services.komodo-periphery.enable). - [Shoko](https://shokoanime.com), an anime management system. Available as [services.shoko](#opt-services.shoko.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 10d04094dd74..635a67a2decd 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1755,6 +1755,7 @@ ./services/web-apps/reposilite.nix ./services/web-apps/rimgo.nix ./services/web-apps/rss-bridge.nix + ./services/web-apps/rsshub.nix ./services/web-apps/rutorrent.nix ./services/web-apps/screego.nix ./services/web-apps/selfoss.nix diff --git a/nixos/modules/services/web-apps/rsshub.nix b/nixos/modules/services/web-apps/rsshub.nix new file mode 100644 index 000000000000..912a6d36c1d3 --- /dev/null +++ b/nixos/modules/services/web-apps/rsshub.nix @@ -0,0 +1,138 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + cfg = config.services.rsshub; +in +{ + options.services.rsshub = { + enable = lib.mkEnableOption "RSSHub service"; + + package = lib.mkPackageOption pkgs "rsshub" { }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the firewall for the specified port."; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.str; + options = { + LISTEN_INADDR_ANY = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Listen to any address"; + apply = x: if x then "1" else "0"; + }; + PORT = lib.mkOption { + type = lib.types.port; + default = 1200; + description = "Listen on port."; + apply = toString; + }; + NO_LOGFILES = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Print logs into stderr."; + apply = x: if x then "1" else "0"; + }; + }; + }; + default = { }; + example = lib.literalExpression '' + { + REQUEST_TIMEOUT = "3000"; + REQUEST_RETRY = "10"; + PUPPETEER_EXECUTABLE_PATH = lib.getExe pkgs.chromium"; + } + ''; + description = '' + Environment variables for RSSHub. + See for available options. + ''; + }; + + secretFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + example = lib.literalExpression '' + [ config.sops.secrets.rsshub.path ] + ''; + description = '' + Environment variables stored in files for secrets. + See for available options. + ''; + }; + + redis = { + enable = lib.mkEnableOption "Redis for RSSHub"; + createLocally = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Create and use a local Redis instance. Sets `services.redis.servers.rsshub`."; + }; + host = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = "The Redis host."; + }; + port = lib.mkOption { + type = lib.types.port; + default = 6379; + description = "The Redis port."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.redis.servers.rsshub = lib.mkIf (cfg.redis.enable && cfg.redis.createLocally) { + enable = true; + port = cfg.redis.port; + }; + + services.rsshub.settings = lib.mkIf cfg.redis.enable { + CACHE_TYPE = "redis"; + REDIS_URL = "redis://${cfg.redis.host}:${toString cfg.redis.port}"; + }; + + systemd.services.rsshub = { + description = "RSSHub - Everything is RSSible"; + wantedBy = [ "multi-user.target" ]; + after = lib.optional (cfg.redis.enable && cfg.redis.createLocally) "redis-rsshub.service"; + requires = lib.optional (cfg.redis.enable && cfg.redis.createLocally) "redis-rsshub.service"; + + environment = cfg.settings; + + serviceConfig = { + Type = "simple"; + User = "rsshub"; + Group = "rsshub"; + DynamicUser = true; + StateDirectory = "rsshub"; + EnvironmentFile = cfg.secretFiles; + ExecStart = lib.getExe cfg.package; + Restart = "on-failure"; + RestartSec = "10s"; + + # Hardening + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ (lib.toInt cfg.settings.PORT) ]; + }; + + meta.maintainers = with lib.maintainers; [ vonfry ]; +} From 553b8b5ea215e74ffca897c24ea632ffcc040418 Mon Sep 17 00:00:00 2001 From: Vonfry Date: Fri, 27 Mar 2026 20:47:30 +0800 Subject: [PATCH 2/2] nixosTests.rsshub: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/web-apps/rsshub.nix | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 nixos/tests/web-apps/rsshub.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 603f647f0402..d6e804875aea 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1424,6 +1424,7 @@ in rspamd-trainer = runTest ./rspamd-trainer.nix; rss-bridge = handleTest ./web-apps/rss-bridge { }; rss2email = handleTest ./rss2email.nix { }; + rsshub = runTest ./web-apps/rsshub.nix; rstp = runTest ./rstp.nix; rstudio-server = runTest ./rstudio-server.nix; rsync = runTest ./rsync.nix; diff --git a/nixos/tests/web-apps/rsshub.nix b/nixos/tests/web-apps/rsshub.nix new file mode 100644 index 000000000000..89557aa2ab01 --- /dev/null +++ b/nixos/tests/web-apps/rsshub.nix @@ -0,0 +1,46 @@ +{ lib, ... }: +{ + name = "rsshub"; + + meta.maintainers = with lib.maintainers; [ vonfry ]; + + nodes = { + basic = { + services.rsshub.enable = true; + }; + + redis = { + services.rsshub = { + enable = true; + + redis = { + enable = true; + createLocally = true; + }; + }; + }; + }; + + testScript = + { nodes, ... }: + let + port = nodes.basic.services.rsshub.settings.PORT; + in + '' + def assert_http_200(machine, url): + code = machine.succeed(f"curl -s -o /dev/null -w '%{{http_code}}' {url}").strip() + assert code == "200", f"Expected HTTP 200 from {url}, got {code}" + + def test_node(node): + node.wait_for_unit("rsshub.service") + node.wait_for_open_port(${port}) + assert_http_200(node, "http://localhost:${port}/healthz") + + with subtest("RSSHub works"): + test_node(basic) + + with subtest("RSSHub works with local Redis"): + test_node(redis) + + ''; +}