4 Commits

Author SHA1 Message Date
finn.markwitz b7ae8cfec2 feat(jupiter): add newsreader profile (RSSHub + Miniflux)
X killed guest API access and now prices reads at $0.005 each with no free
tier, so the only workable personal news reader is a scraping bridge fed by a
burner account's session cookie.

RSSHub turns X accounts, lists and keyword searches into RSS on loopback;
Miniflux is the reader, exposed on the LAN and registered with homepage.
Both secrets live outside the Nix store as root-owned EnvironmentFiles.

Note: the packaged RSSHub reads TWITTER_AUTH_TOKEN — the username/password
login that upstream docs still describe is commented out in that build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 10:21:09 +02:00
finn.markwitz 70deb25080 chore: Flake Update 2026-08-21 10:30:01 +02:00
finn.markwitz ac57abf255 chore: Flake Update 2026-08-11 15:40:07 +02:00
finn.markwitz 83ee518091 Merge pull request 'feat(jupiter): native Immich profile module (docker → NixOS)' (#10) from feat/immich-nixos-module into main
Reviewed-on: #10
2026-08-07 11:13:40 +02:00
4 changed files with 124 additions and 20 deletions
Generated
+20 -20
View File
@@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1782949081, "lastModified": 1785627969,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", "narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", "rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -89,11 +89,11 @@
"nixpkgs-regression": "nixpkgs-regression" "nixpkgs-regression": "nixpkgs-regression"
}, },
"locked": { "locked": {
"lastModified": 1784762557, "lastModified": 1787274306,
"narHash": "sha256-R/r6jRnANV50c8F5Fz5+1Q1moab0IGWRk+cg5ME2nMY=", "narHash": "sha256-Qg9f9td5iphUWSQS6zmvyZWO1F+D7j8Z3U6dGyUTg08=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nix", "repo": "nix",
"rev": "d10c84cd0cc0efdcb29cf2611caf5fbcd10fa071", "rev": "649e823fb24ed118d72e613be35fa8ea1b64afe7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -143,11 +143,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1784723954, "lastModified": 1787144466,
"narHash": "sha256-1CfD8ZUjCkTgjsneLZ/lxCHhgDfqxxE7/GX0MmsgiqA=", "narHash": "sha256-HHfv2/HkNSKbbSyU9iD/g8lbP6r4tl33sSw1W4rXCk0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "a017f5b72210026af5b3ac5949f08d94380a6fbd", "rev": "0471accf8d0a8210b31d947497d179ecc99e0021",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -187,11 +187,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1782614948, "lastModified": 1785031560,
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", "narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", "rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -218,11 +218,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1784796856, "lastModified": 1787135253,
"narHash": "sha256-vwxWgF+Gj276WznzGb1LxGsK/39HaQwgQXiU3EkC844=", "narHash": "sha256-M5/r2v++FjVhdsxXYMb4BDJ5YLAdCWFt3aZotcshocA=",
"rev": "e2587caef70cea85dd97d7daab492899902dbf5d", "rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1040357.e2587caef70c/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1058091.ffb3c9b700e7/nixexprs.tar.xz"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
@@ -245,11 +245,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1784707089, "lastModified": 1787101114,
"narHash": "sha256-DUedXhD2Rg8q4Xyd07Sb90eZGy4gg6W+Vl/WbLNwAZo=", "narHash": "sha256-BA7sSNjLDuPGSOYBGpr6WQjke1MQ8AZpJ8GlYZM/mOc=",
"rev": "b3fe9581c9061c749abef42b6d4ee7b7c05c33fa", "rev": "b18a4b905f8d028dc4476412e6d6891728695379",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.5845.b3fe9581c906/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.8045.b18a4b905f8d/nixexprs.tar.xz"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
+1
View File
@@ -22,6 +22,7 @@ in
jellyfin.enable = true; jellyfin.enable = true;
jellyseerr.enable = true; jellyseerr.enable = true;
immich.enable = true; immich.enable = true;
newsreader.enable = true;
development.enable = true; development.enable = true;
home-assistant.enable = true; home-assistant.enable = true;
+1
View File
@@ -20,5 +20,6 @@
./jellyfin ./jellyfin
./jellyseerr ./jellyseerr
./immich ./immich
./newsreader
]; ];
} }
+102
View File
@@ -0,0 +1,102 @@
# 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=<auth_token cookie>,<optional second cookie>
```
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=<at least 6 characters>
```
'';
};
};
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/<handle>, /twitter/list/<id> or
# /twitter/keyword/<query>.
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";
}
];
};
}