From a047ad0c0522e3c158bb9e464c42fa0f83a7351b Mon Sep 17 00:00:00 2001 From: Elliot Blackburn Date: Mon, 19 Oct 2026 12:18:57 +0000 Subject: [PATCH] Service: Add fusion rss feed reader Fusion RSS is another feed reader with support for rss, atom, and JSON feeds. It's an alternative to FreshRSS with slightly less functionality and specifically only supports a single tenant (user) which keeps it quite simple. This adapts my own docker compose file that I use in my setup, for the ScaleTail project. --- README.md | 1 + services/fusion/.env | 33 +++++++++++++++++ services/fusion/README.md | 42 +++++++++++++++++++++ services/fusion/compose.yaml | 72 ++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 services/fusion/.env create mode 100644 services/fusion/README.md create mode 100644 services/fusion/compose.yaml diff --git a/README.md b/README.md index c648808d..9c852734 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ Every stack starts from the same [standard setup](documentation/standard-setup.m | **BookLore** | Book manager and browser reader, with Kobo and KOReader sync. | [Details](services/booklore) | | **Configarr** | Tool that applies YAML settings and TRaSH Guides profiles to Radarr and Sonarr, then exits. | [Details](services/configarr) | | **FreshRSS** | Feed reader for RSS and Atom, with themes, extensions, and no separate database server. | [Details](services/freshrss) | +| **Fusion** | Lightweight feed reader for RSS, Atom, and JSON feeds, with a Fever API for mobile clients. | [Details](services/fusion) | | **Hytale** | Hytale game server in a community image, for players on your Tailnet. | [Details](services/hytale) | | **Immich** | Photo and video library that backs up mobile media, with search and sharing. | [Details](services/immich) | | **Jellyfin** | Media server that organizes and streams your movies, series, and music. | [Details](services/jellyfin) | diff --git a/services/fusion/.env b/services/fusion/.env new file mode 100644 index 00000000..3348b020 --- /dev/null +++ b/services/fusion/.env @@ -0,0 +1,33 @@ +#URL=https://github.com/tailscale-dev/ScaleTail +# To run a second copy, copy the folder under another name and change SERVICE. + +# Service Configuration +# Service name (e.g., adguard). Used as hostname in Tailscale and for container naming. +SERVICE=fusion +# Docker image URL from container registry (e.g., adguard/adguardhome:latest). +IMAGE_URL=ghcr.io/0x2e/fusion:latest + +# Network Configuration +# Port to expose to local network. Uncomment the "ports:" section in compose.yaml to enable. +SERVICEPORT=8080 +# Preferred DNS server for Tailscale. Uncomment the "dns:" section in compose.yaml to enable. +DNS_SERVER=9.9.9.9 + +# Tailscale Configuration +# Auth key from https://console.tailscale.com/admin/settings/keys. See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions. +TS_AUTHKEY= + +# Time Zone setting for containers. See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +TZ=Europe/Amsterdam + +# Any Container environment variables are declared below. See https://docs.docker.com/compose/how-tos/environment-variables/ + +# Required: your Tailnet name, the part between the service name and ".ts.net" in https://fusion..ts.net, for example tail1234. +TAILNET_NAME= +# Required: password of the web interface. Generate it with: openssl rand -hex 32 +FUSION_PASSWORD= + +# Optional single sign-on. Set these three values and uncomment the FUSION_OIDC lines in compose.yaml. +#FUSION_OIDC_ISSUER= +#FUSION_OIDC_CLIENT_ID= +#FUSION_OIDC_CLIENT_SECRET= diff --git a/services/fusion/README.md b/services/fusion/README.md new file mode 100644 index 00000000..13e813e7 --- /dev/null +++ b/services/fusion/README.md @@ -0,0 +1,42 @@ +# Fusion + +[Fusion](https://github.com/0x2E/fusion) is a lightweight feed reader for RSS, Atom, and JSON feeds. It keeps your feeds, articles, and read state in a single SQLite file, and serves third-party clients such as Reeder and Unread through its Fever API. + +This stack runs Fusion with a Tailscale sidecar, as described in [the standard setup](../../documentation/standard-setup.md). + +## At a glance + +| Item | Value | +| ------------- | ---------------------------------- | +| Web interface | `https://fusion..ts.net` | +| Service port | `8080` | +| Image | `ghcr.io/0x2e/fusion` | +| Data | `./fusion-data/` (SQLite database) | + +## Before you start + +Set these values in `.env`. Compose stops with an error if one of them is empty. + +- **`TAILNET_NAME`.** Your Tailnet name, the part between the service name and `.ts.net`, for example `tail1234`. `compose.yaml` builds the allowed browser origin `https://..ts.net` from it. +- **`FUSION_PASSWORD`.** The password of the web interface. Generate one with `openssl rand -hex 32`. Fusion reads it at every start and never stores it, so you change the password by changing this value and running `docker compose up -d --force-recreate`. That also ends every open session, because Fusion keeps sessions in memory. + +## Deviations from the standard setup + +- **Allowed browser origin.** `FUSION_CORS_ALLOWED_ORIGINS` in `compose.yaml` holds the Tailnet address of the service, built from `SERVICE` and `TAILNET_NAME`. Fusion answers `403 Forbidden` to any request that carries another `Origin` header, so a browser can only use the service at that address. An empty value would accept every origin instead. For the same reason the optional `ports` block alone does not give access from your local network: the login request from a page on the address of the Docker host is rejected. To use that address as well, add it to the comma-separated list. + +## First run + +Open the web interface and log in with the password from `FUSION_PASSWORD`. Fusion has no user accounts, so the login form asks for the password only. A session lasts 30 days. + +Add your first feed with the Add button. Fusion discovers the feed of a site when you give it the address of the page itself, and it pulls every feed every 30 minutes. + +## Configuration + +- **Single sign-on.** Fusion can add a login through an OIDC provider. Set `FUSION_OIDC_ISSUER`, `FUSION_OIDC_CLIENT_ID`, and `FUSION_OIDC_CLIENT_SECRET` in `.env`, then uncomment the four `FUSION_OIDC` lines in `compose.yaml`. Register `https://fusion..ts.net/api/oidc/callback` as the redirect URI with your provider, because it must match the value that `compose.yaml` sends. Your provider must be reachable from the Fusion container, and from the browser you log in with. The password login stays available next to it, and `FUSION_PASSWORD` stays required: Fusion refuses to start with an empty password unless you also set `FUSION_ALLOW_EMPTY_PASSWORD=true`. +- **Fever API clients.** Reeder, Unread, and other Fever clients use `https://fusion..ts.net` as the server address, with the account type Fever, the username `fusion`, and your password. A client that asks for the endpoint itself takes `https://fusion..ts.net/fever`. The device must be connected to your Tailnet. Fusion derives the Fever API key from the username and `FUSION_PASSWORD`, so a new password also means a new key in every client. +- **Feed pulling.** `FUSION_PULL_INTERVAL`, `FUSION_PULL_TIMEOUT`, and `FUSION_PULL_CONCURRENCY` change how often and how fast Fusion pulls feeds. The [variable reference](https://github.com/0x2E/fusion/blob/main/.env.example) lists the defaults. + +## Links + +- [Fusion documentation and source code](https://github.com/0x2E/fusion) +- [Fever API guide](https://github.com/0x2E/fusion/blob/main/docs/fever-api.md) diff --git a/services/fusion/compose.yaml b/services/fusion/compose.yaml new file mode 100644 index 00000000..aa1545e7 --- /dev/null +++ b/services/fusion/compose.yaml @@ -0,0 +1,72 @@ +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:8080"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +services: +# Make sure you have updated/checked the .env file with the correct variables. +# Every variable used in this file must be defined there, unless it has a default such as ${VAR:-value}. + # Tailscale Sidecar Configuration + tailscale: + image: tailscale/tailscale:latest # Image to be used + container_name: tailscale-${SERVICE} # Name for local container management + hostname: ${SERVICE} # Name used within your Tailscale environment + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required + - TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz" + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The : for the healthz endpoint + #- TS_ACCEPT_DNS=true # Uncomment only if the service must resolve MagicDNS names - this replaces Docker DNS, so Compose service names no longer resolve + - TS_AUTH_ONCE=true + configs: + - source: ts-serve + target: /config/serve.json + volumes: + - ./config:/config # Config folder used to store Tailscale files - you may need to change the path + - ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path + devices: + - /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work + cap_add: + - net_admin # Tailscale requirement + #ports: + # - 0.0.0.0:${SERVICEPORT}:${SERVICEPORT} # Binding the service port to the local network - may be removed if only exposure to your Tailnet is required + # If any DNS issues arise, use your preferred DNS provider by uncommenting the config below + #dns: + # - ${DNS_SERVER} + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational + interval: 1m # How often to perform the check + timeout: 10s # Time to wait for the check to succeed + retries: 3 # Number of retries before marking as unhealthy + start_period: 10s # Time to wait before starting health checks + restart: always + + # Application + application: + image: ${IMAGE_URL} # Image to be used + network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale + container_name: app-${SERVICE} # Name for local container management + environment: # Variables are declared in .env file. + - TZ=${TZ} + - FUSION_DB_PATH=/data/fusion.db # SQLite database, kept in the mounted data folder + - FUSION_PASSWORD=${FUSION_PASSWORD:?Set FUSION_PASSWORD in .env} + - FUSION_CORS_ALLOWED_ORIGINS=https://${SERVICE}.${TAILNET_NAME:?Set TAILNET_NAME in .env}.ts.net # Only the Tailnet address may call the API from a browser; an empty value would allow any origin + - FUSION_TRUSTED_PROXIES=127.0.0.1 # Tailscale Serve reaches Fusion over the shared loopback, so trust its X-Forwarded-For for the login rate limit + # Optional single sign-on. Uncomment all four lines and set the three values in .env - see "Configuration" in the README. + #- FUSION_OIDC_ISSUER=${FUSION_OIDC_ISSUER} + #- FUSION_OIDC_CLIENT_ID=${FUSION_OIDC_CLIENT_ID} + #- FUSION_OIDC_CLIENT_SECRET=${FUSION_OIDC_CLIENT_SECRET} + #- FUSION_OIDC_REDIRECT_URI=https://${SERVICE}.${TAILNET_NAME}.ts.net/api/oidc/callback + volumes: + - ./${SERVICE}-data:/data # SQLite database with the feeds, articles, and settings + depends_on: + tailscale: + condition: service_healthy + # Healthcheck: defined by the image (wget against /api/oidc/enabled), so this file does not override it. + restart: always