# X (Twitter) news reader: RSSHub feed bridge + Miniflux reader { config, lib, ... }: let cfg = config.my.profiles.newsreader; hostName = config.networking.hostName; # RSSHub only ever talks to Miniflux on the same host, so it stays on # loopback and out of the firewall. rsshubPort = 1200; in { options.my.profiles.newsreader = with lib; { enable = mkEnableOption "RSSHub + Miniflux news reader"; port = mkOption { type = types.port; default = 8085; # 8080 is taken by aria on jupiter description = "Port Miniflux listens on."; }; rsshubSecretFile = mkOption { type = types.path; default = "/var/lib/secrets/rsshub.env"; description = '' EnvironmentFile holding RSSHub's X session, in the form ``` TWITTER_AUTH_TOKEN=, ``` X removed guest access, so the bridge needs a logged-in session: copy the `auth_token` cookie from a burner account and close the tab without logging out, since logging out invalidates it. Listing several cookies gives RSSHub rotation headroom when one gets suspended. Create this file by hand, root-owned and chmod 600 — it must not end up in the Nix store. ''; }; minifluxSecretFile = mkOption { type = types.path; default = "/var/lib/secrets/miniflux.env"; description = '' EnvironmentFile holding the Miniflux admin account: ``` ADMIN_USERNAME=finn ADMIN_PASSWORD= ``` ''; }; }; config = lib.mkIf cfg.enable { # Turns X accounts, lists and keyword searches into RSS. Feed URLs look # like http://127.0.0.1:1200/twitter/user/, /twitter/list/ or # /twitter/keyword/. services.rsshub = { enable = true; redis.enable = true; secretFiles = [ cfg.rsshubSecretFile ]; settings = { PORT = rsshubPort; LISTEN_INADDR_ANY = false; # X throttles aggressively and answers with an empty 200 rather than an # error, so cache for an hour and keep retries low. CACHE_EXPIRE = "3600"; REQUEST_RETRY = "3"; }; }; services.miniflux = { enable = true; adminCredentialsFile = cfg.minifluxSecretFile; config = { LISTEN_ADDR = "0.0.0.0:${toString cfg.port}"; BASE_URL = "http://${hostName}:${toString cfg.port}/"; CREATE_ADMIN = 1; # Minutes. Matched to RSSHub's cache; polling harder just burns the # X session for nothing. POLLING_FREQUENCY = 60; }; }; networking.firewall.allowedTCPPorts = [ cfg.port ]; my.homepage.services = [ { group = "Services"; name = "Miniflux"; description = "RSS reader"; href = "http://${hostName}:${toString cfg.port}"; icon = "miniflux.png"; } ]; }; }