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