diff --git a/README.md b/README.md index cb98cae..7fdfbae 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose | 🎧 **Audiobookshelf** | A self-hosted audiobook and podcast server with multi-user support and playback syncing. | [Details](services/audiobookshelf) | | 🎥 **Bazarr** | A companion tool to Radarr and Sonarr for managing subtitles. | [Details](services/bazarr) | | 📚 **BookLore** | A self-hosted application for managing and reading books. | [Details](services/booklore) | +| 📰 **FreshRSS** | A customizable feed reader with themes, extensions, and no separate database. | [Details](services/freshrss) | | 🎥 **Frigate** | A self-hosted NVR with real-time AI object detection for IP cameras and local video monitoring. | [Details](services/frigate) | | 🎮 **Hytale** | A self-hosted Hytale game server. | [Details](services/hytale) | | 🖼️ **Immich** | A self-hosted Google Photos alternative with face recognition and mobile sync. | [Details](services/immich) | diff --git a/services/freshrss/.env b/services/freshrss/.env new file mode 100644 index 0000000..7b41415 --- /dev/null +++ b/services/freshrss/.env @@ -0,0 +1,30 @@ +#version=1.1 +#URL=https://github.com/tailscale-dev/ScaleTail +#COMPOSE_PROJECT_NAME= # Optional: only use when running multiple deployments on the same infrastructure. + +# Service Configuration +SERVICE=freshrss +IMAGE_URL=freshrss/freshrss:latest + +# Network Configuration +SERVICEPORT=80 +DNS_SERVER=9.9.9.9 + +# Tailscale Configuration +TS_AUTHKEY= + +# Time Zone setting for containers +TZ=Europe/Amsterdam # See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +TAILNET_NAME= # Include the ".ts.net" if it is part of your Tailnet name + +# FreshRSS Configuration +# You must set these before the first start. They are only applied while the data +# volume is empty; changing them later has no effect (change credentials in the UI instead). +# Avoid $, backticks and backslashes here: Compose and the FreshRSS entrypoint both interpret them. +ADMIN_USERNAME=admin +ADMIN_PASSWORD= +ADMIN_API_PASSWORD= +ADMIN_EMAIL= + +# Crontab minute field for the built-in feed refresh. Leave empty to disable the internal cron. +CRON_MIN=3,33 diff --git a/services/freshrss/README.md b/services/freshrss/README.md new file mode 100644 index 0000000..48658bd --- /dev/null +++ b/services/freshrss/README.md @@ -0,0 +1,51 @@ +# FreshRSS with Tailscale Sidecar Configuration + +This Docker Compose configuration sets up [FreshRSS](https://freshrss.org/) with Tailscale as a sidecar container, enabling secure access to your self-hosted feed reader over a private Tailscale network. With this setup, your FreshRSS instance remains fully private and accessible only from devices on your Tailnet, over HTTPS. + +## FreshRSS + +[FreshRSS](https://github.com/FreshRSS/FreshRSS) is a self-hosted RSS and Atom feed aggregator. It is lightweight, powerful, and customizable through themes and extensions. It exposes a Google Reader-compatible and Fever-compatible API, so clients such as Reeder, NetNewsWire, Unread, and FeedMe can sync against your own server. + +## Key Features + +- **Self-Hosted Feed Reading** – Your subscriptions, read state, and article archive stay on your own hardware. +- **Third-Party Client Sync** – Google Reader and Fever compatible APIs let third-party apps sync with your instance. +- **Extensible** – A large catalog of community themes and extensions. +- **Built-in Refresh Cron** – Feeds can be refreshed on a schedule inside the container, no host cron required. +- **Low Resource Usage** – Runs comfortably on a Raspberry Pi with the default SQLite database. +- **Private by Default with Tailscale** – No public exposure, no reverse proxies or port forwarding, and HTTPS handled by Tailscale Serve. + +## Configuration Overview + +In this deployment, the `tailscale-freshrss` service runs the Tailscale client and joins your Tailnet as the host `freshrss`. The `app-freshrss` service uses `network_mode: service:tailscale`. That means both containers share one network namespace. Tailscale Serve terminates HTTPS on port 443 and proxies to FreshRSS on `127.0.0.1:80` inside that shared namespace. + +## Prerequisites + +- Docker and the Compose plugin, with your user in the `docker` group (or use `sudo`). +- `/dev/net/tun` available on the host and the `NET_ADMIN` capability, both already declared in `compose.yaml`. +- A Tailscale [auth key](https://console.tailscale.com/admin/settings/keys) from the web admin console (**Settings → Keys → Generate auth key**). Set it to "Pre-Approved" if that option appears. The key is used only for the initial registration — with `TS_AUTH_ONCE=true` and the persisted `ts/state` volume, restarts reuse the stored node state — so a single-use key is sufficient. Tagging the device disables key expiry, which avoids re-authentication after the default 180 days. +- HTTPS certificates [enabled for your Tailnet](https://console.tailscale.com/admin/dns) (**DNS → HTTPS Certificates**). Tailscale Serve cannot issue a certificate without it, and the container will start but never serve. + +## Files to check + +Please verify the following files and variables before deploying: + +- `.env` — set `TS_AUTHKEY`, `TAILNET_NAME`, `TZ`, `ADMIN_USERNAME`, `ADMIN_PASSWORD`, `ADMIN_API_PASSWORD`, and `ADMIN_EMAIL`. +- `compose.yaml` — confirm the volume paths and the `Proxy` port in the `ts-serve` config. + +## Usage Notes + +- **First run only.** `FRESHRSS_INSTALL` and `FRESHRSS_USER` drive FreshRSS's unattended installer, and only take effect while the data directory is empty. On later starts the entrypoint reports `FreshRSS already installed; no change performed.` and ignores `.env`. Set the passwords before the first `docker compose up` and change them from the FreshRSS UI afterwards, not by editing `.env`. Avoid `$`, backticks, and backslashes in those first-run values — Compose and the entrypoint both interpret them. +- **`TAILNET_NAME` feeds the base URL.** `compose.yaml` builds `--base-url` as `https://${SERVICE}.${TAILNET_NAME}`, so include the `.ts.net` suffix. FreshRSS displays the value read-only under **Configuration → System**; to change it after the first run, use `docker compose exec application ./cli/reconfigure.php --base-url https://freshrss.example.ts.net`. +- **Health check.** The app health check runs `./cli/health.php`, which ships with the image and requests `/api/`. Disabling the API in the UI will mark the container unhealthy even though the web interface works. +- **Ports.** The `ports` block stays commented out; the Tailnet is the only way in. Uncommenting it publishes plain HTTP on the host and bypasses Tailscale entirely. Note `SERVICEPORT` is `80`, which often collides on the host. +- **MagicDNS.** Uncomment `TS_ACCEPT_DNS=true` only if the container itself must resolve other MagicDNS names, such as an external database. It is not needed for the default SQLite setup. + +## References + +- [FreshRSS website](https://freshrss.org/) +- [FreshRSS on GitHub](https://github.com/FreshRSS/FreshRSS) +- [FreshRSS Docker documentation](https://github.com/FreshRSS/FreshRSS/blob/edge/Docker/README.md) +- [FreshRSS extensions](https://github.com/FreshRSS/Extensions) +- [Tailscale Serve documentation](https://tailscale.com/kb/1242/tailscale-serve) +- [Tailscale auth keys](https://tailscale.com/kb/1085/auth-keys) diff --git a/services/freshrss/compose.yaml b/services/freshrss/compose.yaml new file mode 100644 index 0000000..a0860b6 --- /dev/null +++ b/services/freshrss/compose.yaml @@ -0,0 +1,74 @@ +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:80"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +services: +# Make sure you have updated/checked the .env file with the correct variables. +# All the ${ xx } need to be defined there. + # 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 when using MagicDNS + - 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 port ${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 + + # ${SERVICE} + application: + image: ${IMAGE_URL} # Image to be used + network_mode: service:tailscale # Sidecar configuration to route ${SERVICE} through Tailscale + container_name: app-${SERVICE} # Name for local container management + environment: # Variables are declared in the .env file. + - TZ=${TZ} + - CRON_MIN=${CRON_MIN} # Crontab minute field for the built-in feed refresh; leave empty in .env to disable + - TRUSTED_PROXY=127.0.0.1 # Tailscale Serve reaches FreshRSS over the shared loopback, so trust it for X-Forwarded-For + # The two variables below are only read on the very first start (empty data volume). + - FRESHRSS_INSTALL=--api-enabled --base-url https://${SERVICE}.${TAILNET_NAME} --db-type sqlite --default-user ${ADMIN_USERNAME} --language en + - FRESHRSS_USER=--api-password ${ADMIN_API_PASSWORD} --email ${ADMIN_EMAIL} --language en --password ${ADMIN_PASSWORD} --user ${ADMIN_USERNAME} + volumes: + - ./${SERVICE}-data/app/data:/var/www/FreshRSS/data # Configuration, SQLite database, feeds and articles + - ./${SERVICE}-data/app/extensions:/var/www/FreshRSS/extensions # Optional third-party extensions + depends_on: + tailscale: + condition: service_healthy + healthcheck: + test: ["CMD", "php", "-f", "./cli/health.php"] # Ships with the image; requires the API to stay enabled + 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: 30s # Time to wait before starting health checks + restart: always