mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
feat: add docs app with Fumadocs
- Add `docs/` — Next.js 15 app powered by Fumadocs with a landing page, docs layout, search, OG image generation, and LLM text routes - Cover getting started, configuration, integrations (Plex, Emby, Jellyfin, Radarr, Sonarr), mobile app setup, and telemetry in MDX - Exclude `docs/` from root Biome config since it has its own `biome.json`
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
---
|
||||
title: Configuration
|
||||
description: Environment variables and server settings.
|
||||
---
|
||||
|
||||
## Required Variables
|
||||
|
||||
These three environment variables must be set for Sofa to start:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|---|---|---|
|
||||
| `TMDB_API_READ_ACCESS_TOKEN` | TMDB API authentication token | [Get one here](https://www.themoviedb.org/settings/api) |
|
||||
| `BETTER_AUTH_SECRET` | Session encryption secret (min 32 chars) | `openssl rand -base64 32` |
|
||||
| `BETTER_AUTH_URL` | Public URL of your instance | `https://sofa.example.com` |
|
||||
|
||||
`BETTER_AUTH_URL` is critical when using a reverse proxy or OIDC — it must match the URL users access in their browser.
|
||||
|
||||
## Data & Storage
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `DATA_DIR` | `/data` (Docker) or `./data` (dev) | Root directory for all persistent data |
|
||||
| `DATABASE_URL` | `${DATA_DIR}/sqlite.db` | SQLite database file path |
|
||||
| `CACHE_DIR` | `${DATA_DIR}/images` | Image cache directory |
|
||||
|
||||
`DATABASE_URL` and `CACHE_DIR` are derived from `DATA_DIR` by default but can be overridden independently.
|
||||
|
||||
The database uses SQLite with WAL (Write-Ahead Logging) mode for concurrent read access. Migrations run automatically on every startup.
|
||||
|
||||
## Image Caching
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `IMAGE_CACHE_ENABLED` | `true` | Cache TMDB images locally |
|
||||
|
||||
When enabled, Sofa downloads poster, backdrop, and other images from TMDB and serves them from disk. This improves load times and reduces external requests but uses disk space.
|
||||
|
||||
Cached images are stored in subdirectories under `CACHE_DIR`:
|
||||
|
||||
```
|
||||
images/
|
||||
├── posters/ # 500px wide
|
||||
├── backdrops/ # 1280px wide
|
||||
├── stills/ # 1280px wide (episode stills)
|
||||
├── logos/ # 92px wide (provider logos)
|
||||
└── profiles/ # 185px wide (cast photos)
|
||||
```
|
||||
|
||||
Set `IMAGE_CACHE_ENABLED=false` to serve images directly from TMDB's CDN instead.
|
||||
|
||||
## Logging
|
||||
|
||||
| Variable | Default | Options |
|
||||
|---|---|---|
|
||||
| `LOG_LEVEL` | `info` | `error`, `warn`, `info`, `debug`, `trace` |
|
||||
|
||||
Sofa uses structured JSON logging via [Pino](https://getpino.io/). Set to `debug` for troubleshooting.
|
||||
|
||||
## OIDC / SSO
|
||||
|
||||
Sofa supports any OpenID Connect provider (Authentik, Keycloak, Authelia, Google Workspace, etc.). OIDC is enabled when all three of `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and `OIDC_ISSUER_URL` are set.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `OIDC_CLIENT_ID` | — | Client ID from your identity provider |
|
||||
| `OIDC_CLIENT_SECRET` | — | Client secret from your identity provider |
|
||||
| `OIDC_ISSUER_URL` | — | Issuer discovery URL |
|
||||
| `OIDC_PROVIDER_NAME` | `SSO` | Label shown on the login button |
|
||||
| `OIDC_AUTO_REGISTER` | `true` | Auto-create accounts on first OIDC login |
|
||||
| `DISABLE_PASSWORD_LOGIN` | `false` | Hide the email/password form (requires OIDC) |
|
||||
|
||||
### Callback URL
|
||||
|
||||
Configure this redirect URI in your identity provider:
|
||||
|
||||
```
|
||||
https://your-sofa-url.com/api/auth/oauth2/callback/oidc
|
||||
```
|
||||
|
||||
Replace `https://your-sofa-url.com` with your `BETTER_AUTH_URL` value.
|
||||
|
||||
### Example: Authentik
|
||||
|
||||
1. In Authentik, create a new **OAuth2/OpenID Provider**
|
||||
2. Set the redirect URI to `https://sofa.example.com/api/auth/oauth2/callback/oidc`
|
||||
3. Note the Client ID and Client Secret
|
||||
4. Add to your `.env`:
|
||||
|
||||
```bash
|
||||
OIDC_CLIENT_ID=your_client_id
|
||||
OIDC_CLIENT_SECRET=your_client_secret
|
||||
OIDC_ISSUER_URL=https://authentik.example.com/application/o/sofa
|
||||
OIDC_PROVIDER_NAME=Authentik
|
||||
```
|
||||
|
||||
### Example: Keycloak
|
||||
|
||||
1. In Keycloak, create a new **OpenID Connect** client in your realm
|
||||
2. Set the Valid Redirect URI to `https://sofa.example.com/api/auth/oauth2/callback/oidc`
|
||||
3. Add to your `.env`:
|
||||
|
||||
```bash
|
||||
OIDC_CLIENT_ID=sofa
|
||||
OIDC_CLIENT_SECRET=your_client_secret
|
||||
OIDC_ISSUER_URL=https://keycloak.example.com/realms/your-realm
|
||||
OIDC_PROVIDER_NAME=Keycloak
|
||||
```
|
||||
|
||||
### Disabling Password Login
|
||||
|
||||
To enforce SSO-only login, set `DISABLE_PASSWORD_LOGIN=true`. This only takes effect when OIDC is configured. The admin account created during setup will still work via OIDC.
|
||||
|
||||
## TMDB Overrides
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `TMDB_API_BASE_URL` | `https://api.themoviedb.org/3` | Override TMDB API endpoint |
|
||||
| `TMDB_IMAGE_BASE_URL` | `https://image.tmdb.org/t/p` | Override TMDB image CDN |
|
||||
|
||||
These are only needed if you're proxying TMDB requests or using an alternative endpoint.
|
||||
|
||||
## Network
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `CORS_ORIGIN` | `http://localhost:3000` | Allowed CORS origin |
|
||||
| `PORT` | `3000` | Server listening port |
|
||||
|
||||
Set `CORS_ORIGIN` to your public URL when using a reverse proxy.
|
||||
|
||||
## Update Checks
|
||||
|
||||
Sofa checks for new versions every 6 hours by contacting the public API.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `PUBLIC_API_URL` | `https://public-api.sofa.watch` | Endpoint for version checks |
|
||||
|
||||
Update checks can be disabled in **Settings → Admin → Update Checks**. When a new version is available, a notification appears in the admin settings.
|
||||
|
||||
## Telemetry
|
||||
|
||||
Sofa includes opt-in anonymous telemetry. It is **disabled by default**. See the [Telemetry](/docs/telemetry) page for full details on what is and isn't collected.
|
||||
|
||||
## Scheduled Backups
|
||||
|
||||
Automatic backups can be enabled in **Settings → Admin → Backups**. Configuration options:
|
||||
|
||||
| Setting | Default | Options |
|
||||
|---|---|---|
|
||||
| Frequency | Every day | `6h`, `12h`, `1d`, `7d` |
|
||||
| Time | 02:00 | Any `HH:MM` |
|
||||
| Day of week | Sunday | (for weekly backups) |
|
||||
| Retention | 7 | Number of scheduled backups to keep |
|
||||
|
||||
Backups are SQLite database dumps stored in the `backups/` directory inside `DATA_DIR`. Old backups beyond the retention count are automatically deleted.
|
||||
|
||||
## Background Jobs
|
||||
|
||||
Sofa runs several background jobs automatically:
|
||||
|
||||
| Job | Schedule | Description |
|
||||
|---|---|---|
|
||||
| Library refresh | Daily at 3:00 AM | Refreshes metadata for tracked titles |
|
||||
| Streaming availability | Every 6 hours | Updates where titles are available to stream |
|
||||
| Recommendations | Every 12 hours | Refreshes personalized recommendations |
|
||||
| New episodes | Every 12 hours | Checks for new episodes of tracked TV shows |
|
||||
| Image caching | Twice daily | Caches images for tracked titles |
|
||||
| Credits refresh | Daily at 2:00 AM | Updates cast and crew information |
|
||||
| Update check | Every 6 hours | Checks for new Sofa versions |
|
||||
| Telemetry | Daily at 12:30 AM | Sends anonymized usage data (if enabled) |
|
||||
|
||||
All schedules are in UTC. TMDB API calls are rate-limited with a 300ms delay between requests per title to stay within API limits.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Deploy Sofa with Docker in under a minute.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose installed
|
||||
- A free TMDB API Read Access Token — [get one here](https://www.themoviedb.org/settings/api)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Create a `docker-compose.yml`:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
sofa:
|
||||
image: ghcr.io/jakejarvis/sofa:edge
|
||||
container_name: sofa
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- sofa-data:/data
|
||||
environment:
|
||||
- TMDB_API_READ_ACCESS_TOKEN=${TMDB_API_READ_ACCESS_TOKEN}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
- BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000}
|
||||
|
||||
volumes:
|
||||
sofa-data:
|
||||
```
|
||||
|
||||
Create a `.env` file next to it with your configuration:
|
||||
|
||||
```bash title=".env"
|
||||
# Get a token at https://www.themoviedb.org/settings/api
|
||||
TMDB_API_READ_ACCESS_TOKEN=your_token_here
|
||||
|
||||
# Random secret for session encryption (min 32 chars)
|
||||
BETTER_AUTH_SECRET=your_secret_here
|
||||
|
||||
# Public URL of your instance
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
Generate a secure secret:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Start the container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) and create your account.
|
||||
|
||||
## First Account
|
||||
|
||||
The first account you create automatically becomes the **admin**. After that, registration closes by default. You can reopen it in **Settings → Admin → Registration**.
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
If you're running Sofa behind a reverse proxy (Caddy, nginx, Traefik, etc.), make sure to:
|
||||
|
||||
1. Set `BETTER_AUTH_URL` to your public URL (e.g. `https://sofa.example.com`)
|
||||
2. Set `CORS_ORIGIN` to the same URL if your proxy uses a different origin
|
||||
3. Forward the `Host` header and WebSocket connections
|
||||
|
||||
Example with Caddy:
|
||||
|
||||
```txt title="Caddyfile"
|
||||
sofa.example.com {
|
||||
reverse_proxy localhost:3000
|
||||
}
|
||||
```
|
||||
|
||||
Example with nginx:
|
||||
|
||||
```nginx title="nginx.conf"
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name sofa.example.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Updating
|
||||
|
||||
Pull the latest image and recreate the container:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
Database migrations run automatically on startup — no manual steps needed.
|
||||
|
||||
## Health Check
|
||||
|
||||
Sofa exposes a health endpoint at `GET /api/health`:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"instanceId": "..."
|
||||
}
|
||||
```
|
||||
|
||||
The Docker image includes a built-in health check that polls this endpoint every 30 seconds.
|
||||
|
||||
## Data Directory
|
||||
|
||||
All persistent data lives in the `/data` volume:
|
||||
|
||||
```
|
||||
/data/
|
||||
├── sqlite.db # SQLite database
|
||||
├── backups/ # Manual and scheduled backups
|
||||
├── images/ # Cached TMDB images (if enabled)
|
||||
└── avatars/ # User profile pictures
|
||||
```
|
||||
|
||||
To back up your instance, you can either:
|
||||
|
||||
- Use Sofa's built-in backup system (Settings → Admin → Backups)
|
||||
- Copy the Docker volume directly: `docker cp sofa:/data ./sofa-backup`
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Emby
|
||||
description: Automatically log watches from Emby.
|
||||
---
|
||||
|
||||
Sofa can automatically log movies and episodes when you finish watching them on Emby.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Emby Premiere](https://emby.media/premiere.html) license
|
||||
- Emby Server **4.7.9** or later
|
||||
|
||||
## Setup
|
||||
|
||||
1. In Sofa, go to **Settings → Integrations** and click **Connect** next to Emby
|
||||
2. Copy the generated webhook URL
|
||||
3. In Emby, go to **Settings → Webhooks**
|
||||
4. Add a new webhook and paste the URL
|
||||
5. Enable the **Playback** event category
|
||||
6. Save
|
||||
|
||||
The webhook URL looks like:
|
||||
|
||||
```
|
||||
https://your-sofa-server.com/api/webhooks/{token}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When you finish watching something on Emby, it sends a `playback.stop` event to Sofa. Sofa only processes events where `PlayedToCompletion` is `true`.
|
||||
|
||||
Sofa resolves titles using TMDB, IMDB, or TVDB IDs from Emby's provider metadata.
|
||||
|
||||
## Deduplication
|
||||
|
||||
Duplicate events within 5 minutes are automatically ignored.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Check the event log in **Settings → Integrations → Emby**:
|
||||
|
||||
- **No events appearing** — verify you have Emby Premiere active and are running Emby Server 4.7.9+. Webhooks require Premiere.
|
||||
- **Events show as "ignored"** — the content wasn't played to completion.
|
||||
- **"Could not resolve TMDB ID"** — ensure the title has provider IDs configured in Emby's metadata settings.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Integrations
|
||||
description: Connect Sofa to your media stack.
|
||||
---
|
||||
|
||||
Sofa integrates with popular media servers and the *arr stack to automate your tracking workflow.
|
||||
|
||||
## Webhook Integrations
|
||||
|
||||
Webhook integrations automatically log what you watch. When you finish a movie or episode on your media server, Sofa records it — no manual input needed.
|
||||
|
||||
- [Plex](/docs/integrations/plex) — requires Plex Pass
|
||||
- [Jellyfin](/docs/integrations/jellyfin) — requires the Webhook plugin
|
||||
- [Emby](/docs/integrations/emby) — requires Emby Premiere 4.7.9+
|
||||
|
||||
## Import List Integrations
|
||||
|
||||
Import list integrations export your Sofa watchlist so Sonarr and Radarr can automatically grab new content.
|
||||
|
||||
- [Sonarr](/docs/integrations/sonarr) — exports TV shows from your watchlist
|
||||
- [Radarr](/docs/integrations/radarr) — exports movies from your watchlist
|
||||
|
||||
## Setup
|
||||
|
||||
All integrations are managed in **Settings → Integrations**. The general flow:
|
||||
|
||||
1. Click **Connect** next to the integration you want
|
||||
2. A unique URL is generated for your account
|
||||
3. Copy the URL and paste it into your media server's configuration
|
||||
4. Sofa will start receiving events or serving your watchlist
|
||||
|
||||
Each integration URL contains a unique token for authentication. You can regenerate the token at any time if it's compromised — just update the URL in your media server.
|
||||
|
||||
## Event Log
|
||||
|
||||
Every webhook event is logged and visible in the integration settings. The event log shows:
|
||||
|
||||
- Whether the event was processed successfully, ignored, or caused an error
|
||||
- The media title and type (movie or episode)
|
||||
- A timestamp for each event
|
||||
|
||||
This makes it easy to troubleshoot if something isn't being tracked as expected.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Jellyfin
|
||||
description: Automatically log watches from Jellyfin.
|
||||
---
|
||||
|
||||
Sofa can automatically log movies and episodes when you finish watching them on Jellyfin.
|
||||
|
||||
## Requirements
|
||||
|
||||
- The [Webhook plugin](https://github.com/jellyfin/jellyfin-plugin-webhook) installed from the Jellyfin plugin catalog
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install the Webhook plugin: **Dashboard → Plugins → Catalog → Webhook** and restart Jellyfin
|
||||
2. In Sofa, go to **Settings → Integrations** and click **Connect** next to Jellyfin
|
||||
3. Copy the generated webhook URL
|
||||
4. In Jellyfin, go to **Dashboard → Plugins → Webhook**
|
||||
5. Click **Add Generic Destination**
|
||||
6. Paste the webhook URL
|
||||
7. Enable the **Playback Stop** notification type
|
||||
8. Save
|
||||
|
||||
The webhook URL looks like:
|
||||
|
||||
```
|
||||
https://your-sofa-server.com/api/webhooks/{token}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When you finish watching something on Jellyfin, the Webhook plugin sends a `PlaybackStop` event to Sofa. Sofa only processes events where `PlayedToCompletion` is `true` — partially watched content is ignored.
|
||||
|
||||
Sofa resolves titles using TMDB, IMDB, or TVDB IDs from Jellyfin's provider metadata.
|
||||
|
||||
## Deduplication
|
||||
|
||||
Duplicate events within 5 minutes are automatically ignored.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Check the event log in **Settings → Integrations → Jellyfin**:
|
||||
|
||||
- **No events appearing** — make sure the Webhook plugin is installed and the **Playback Stop** notification type is enabled.
|
||||
- **Events show as "ignored"** — the content wasn't played to completion. Jellyfin only reports `PlayedToCompletion: true` when the full runtime is watched.
|
||||
- **"Could not resolve TMDB ID"** — ensure the title has provider IDs configured in Jellyfin (TMDB, IMDB, or TVDB).
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Integrations",
|
||||
"pages": ["index", "plex", "jellyfin", "emby", "sonarr", "radarr"]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Plex
|
||||
description: Automatically log watches from Plex.
|
||||
---
|
||||
|
||||
Sofa can automatically log movies and episodes when you finish watching them on Plex.
|
||||
|
||||
## Requirements
|
||||
|
||||
- An active [Plex Pass](https://www.plex.tv/plex-pass/) subscription (required for webhooks)
|
||||
|
||||
## Setup
|
||||
|
||||
1. In Sofa, go to **Settings → Integrations** and click **Connect** next to Plex
|
||||
2. Copy the generated webhook URL
|
||||
3. Open [Plex Webhook Settings](https://app.plex.tv/desktop/#!/settings/webhooks)
|
||||
4. Click **Add Webhook** and paste the URL
|
||||
|
||||
The webhook URL looks like:
|
||||
|
||||
```
|
||||
https://your-sofa-server.com/api/webhooks/{token}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When you finish watching something on Plex, it sends a `media.scrobble` event to Sofa. Sofa then:
|
||||
|
||||
1. Extracts the media IDs (TMDB, IMDB, or TVDB) from the webhook payload
|
||||
2. Resolves the title in the TMDB database
|
||||
3. Logs the watch to your Sofa account with a `plex` source tag
|
||||
|
||||
For TV shows, Sofa matches the specific season and episode number to track your progress accurately.
|
||||
|
||||
## Deduplication
|
||||
|
||||
If Plex sends the same event multiple times within 5 minutes (which can happen with some clients), Sofa ignores the duplicates.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If watches aren't being logged, check the event log in **Settings → Integrations → Plex**:
|
||||
|
||||
- **"Could not resolve TMDB ID"** — the media's external IDs don't map to a TMDB entry. This is rare but can happen with obscure titles.
|
||||
- **"Season/episode not found"** — the show's season data hasn't been loaded yet. Try searching for the show in Sofa first.
|
||||
- **No events appearing** — verify the webhook URL is correct in Plex settings and that your Sofa server is reachable from the Plex server.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Radarr
|
||||
description: Export your movie watchlist to Radarr.
|
||||
---
|
||||
|
||||
Sofa can export your movie watchlist as a custom import list that Radarr polls automatically.
|
||||
|
||||
## Setup
|
||||
|
||||
1. In Sofa, go to **Settings → Integrations** and click **Connect** next to Radarr
|
||||
2. Copy the generated list URL
|
||||
3. In Radarr, go to **Settings → Import Lists**
|
||||
4. Click **+** and select **Custom Lists**
|
||||
5. Paste the URL into the **List URL** field
|
||||
6. Configure your preferred quality profile and root folder
|
||||
7. Save
|
||||
|
||||
The list URL looks like:
|
||||
|
||||
```
|
||||
https://your-sofa-server.com/api/lists/{token}
|
||||
```
|
||||
|
||||
Radarr polls the list periodically (default: every 12 hours) and adds any new movies it finds.
|
||||
|
||||
## Status Filtering
|
||||
|
||||
By default, the list exports titles with a **watchlist** status. You can customize which statuses are included by appending a query parameter:
|
||||
|
||||
```
|
||||
# Only watchlist items (default)
|
||||
https://your-sofa-server.com/api/lists/{token}?status=watchlist
|
||||
|
||||
# Watchlist and in-progress items
|
||||
https://your-sofa-server.com/api/lists/{token}?status=watchlist,in_progress
|
||||
|
||||
# All tracked items
|
||||
https://your-sofa-server.com/api/lists/{token}?status=watchlist,in_progress,completed
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When Radarr polls the list, Sofa returns all matching movies in Radarr's custom list format with TMDB IDs. Only movies are included — TV shows are filtered out.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Sonarr
|
||||
description: Export your TV watchlist to Sonarr.
|
||||
---
|
||||
|
||||
Sofa can export your TV show watchlist as a custom import list that Sonarr polls automatically.
|
||||
|
||||
## Setup
|
||||
|
||||
1. In Sofa, go to **Settings → Integrations** and click **Connect** next to Sonarr
|
||||
2. Copy the generated list URL
|
||||
3. In Sonarr, go to **Settings → Import Lists**
|
||||
4. Click **+** and select **Custom Lists**
|
||||
5. Paste the URL into the **List URL** field
|
||||
6. Configure your preferred quality profile and root folder
|
||||
7. Save
|
||||
|
||||
The list URL looks like:
|
||||
|
||||
```
|
||||
https://your-sofa-server.com/api/lists/{token}
|
||||
```
|
||||
|
||||
Sonarr polls the list periodically (default: every 6 hours) and adds any new shows it finds.
|
||||
|
||||
## Status Filtering
|
||||
|
||||
By default, the list exports titles with a **watchlist** status. You can customize which statuses are included by appending a query parameter:
|
||||
|
||||
```
|
||||
# Only watchlist items (default)
|
||||
https://your-sofa-server.com/api/lists/{token}?status=watchlist
|
||||
|
||||
# Watchlist and in-progress items
|
||||
https://your-sofa-server.com/api/lists/{token}?status=watchlist,in_progress
|
||||
|
||||
# All tracked items
|
||||
https://your-sofa-server.com/api/lists/{token}?status=watchlist,in_progress,completed
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When Sonarr polls the list, Sofa returns all matching TV shows in Sonarr's custom list format with TVDB IDs. Only TV shows are included — movies are filtered out.
|
||||
|
||||
If a show in your watchlist doesn't have a TVDB ID cached, Sofa fetches it from TMDB on the fly and caches it for future requests.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["index", "configuration", "integrations", "mobile-app", "telemetry"]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Mobile App
|
||||
description: Set up the iOS and Android app.
|
||||
---
|
||||
|
||||
Sofa has a native mobile app for iOS and Android built with [Expo](https://expo.dev/) and React Native. It connects to your self-hosted Sofa server and provides the same core features as the web app.
|
||||
|
||||
<Callout type="info">
|
||||
The mobile app is not currently available on the App Store or Google Play. You'll need to build it from source.
|
||||
</Callout>
|
||||
|
||||
## Features
|
||||
|
||||
The mobile app includes:
|
||||
|
||||
- **Home** — dashboard with stats, continue watching, and recommendations
|
||||
- **Explore** — trending and popular movies and TV shows, genre browsing
|
||||
- **Search** — full-text search for movies, TV shows, and people
|
||||
- **Settings** — account management, integrations, and admin controls
|
||||
- Episode-level tracking, ratings, and watchlist management
|
||||
- Media server integration management (Plex, Jellyfin, Emby, Sonarr, Radarr)
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) 22+
|
||||
- [Bun](https://bun.sh/) runtime
|
||||
- [Xcode](https://developer.apple.com/xcode/) (for iOS) or [Android Studio](https://developer.android.com/studio) (for Android)
|
||||
- An Apple Developer account (for running on a physical iOS device)
|
||||
|
||||
### Steps
|
||||
|
||||
Clone the repository and install dependencies:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jakejarvis/sofa.git
|
||||
cd sofa
|
||||
bun install
|
||||
```
|
||||
|
||||
Generate native projects:
|
||||
|
||||
```bash
|
||||
cd apps/native
|
||||
bun prebuild
|
||||
```
|
||||
|
||||
Run on your target platform:
|
||||
|
||||
```bash
|
||||
# iOS Simulator
|
||||
bun ios
|
||||
|
||||
# Android Emulator
|
||||
bun android
|
||||
```
|
||||
|
||||
For physical devices, follow the [Expo development build guide](https://docs.expo.dev/develop/development-builds/create-a-build/).
|
||||
|
||||
## Connecting to Your Server
|
||||
|
||||
On first launch, the app will ask for your Sofa server URL:
|
||||
|
||||
1. Enter the URL of your self-hosted instance (e.g. `https://sofa.example.com`)
|
||||
2. The app validates the connection by checking the `/api/health` endpoint
|
||||
3. Once connected, log in with the same credentials you use on the web
|
||||
|
||||
The server URL is stored locally on the device. You can change it at any time from the login screen by tapping **Change server**.
|
||||
|
||||
### Connection Requirements
|
||||
|
||||
- Your server must be reachable from the device's network
|
||||
- HTTPS is recommended, especially when connecting over the internet
|
||||
- If using a self-signed certificate, you may need to trust it on the device
|
||||
|
||||
## Authentication
|
||||
|
||||
The mobile app uses the same authentication system as the web app:
|
||||
|
||||
- **Email & password** — standard login
|
||||
- **OIDC / SSO** — if configured on your server, the SSO button appears on the login screen
|
||||
|
||||
Auth tokens are stored in the platform's secure storage (iOS Keychain / Android Keystore) and scoped per server instance, so you can connect to multiple Sofa servers without conflicts.
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: Telemetry
|
||||
description: How Sofa collects anonymous usage statistics.
|
||||
---
|
||||
|
||||
Sofa includes optional, privacy-focused telemetry to help understand how the project is used. Both systems are **opt-in** and can be disabled at any time.
|
||||
|
||||
## What We Collect
|
||||
|
||||
Sofa has two separate telemetry systems depending on the platform:
|
||||
|
||||
| | Server | Mobile App | Web App |
|
||||
|---|---|---|---|
|
||||
| **System** | Custom reporting | PostHog | None |
|
||||
| **Default** | Disabled | Disabled | N/A |
|
||||
| **Opt-in** | Admin toggle | User toggle + ATT | N/A |
|
||||
|
||||
The web app has **zero analytics** — no tracking scripts, no third-party services.
|
||||
|
||||
---
|
||||
|
||||
## Server Telemetry
|
||||
|
||||
The self-hosted server can send a small, anonymous report once every 24 hours. It is **disabled by default** and must be explicitly enabled by an admin.
|
||||
|
||||
### Enabling
|
||||
|
||||
Toggle telemetry in **Settings → Admin → Telemetry**.
|
||||
|
||||
### What Gets Sent
|
||||
|
||||
A single JSON payload is sent to `https://public-api.sofa.watch/v1/telemetry`:
|
||||
|
||||
```json
|
||||
{
|
||||
"instanceId": "a1b2c3d4-...",
|
||||
"version": "1.2.0",
|
||||
"arch": "linux-arm64",
|
||||
"users": "2-5",
|
||||
"titles": "51-200",
|
||||
"features": {
|
||||
"imageCache": true,
|
||||
"oidc": false,
|
||||
"scheduledBackups": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `instanceId` | A random UUID generated once per server instance. Not tied to any user or IP. |
|
||||
| `version` | The Sofa version running on your server. |
|
||||
| `arch` | OS and CPU architecture (e.g. `linux-x64`, `darwin-arm64`). |
|
||||
| `users` | User count in a bucketed range: `1`, `2-5`, `6-10`, `11-25`, or `26+`. |
|
||||
| `titles` | Tracked title count in a bucketed range: `0`, `1-50`, `51-200`, `201-500`, or `501+`. |
|
||||
| `features` | Which optional features are enabled — not how they're used, just whether they're on. |
|
||||
|
||||
User and title counts are bucketed into ranges so we can understand general adoption without knowing exact numbers.
|
||||
|
||||
### What Is NOT Sent
|
||||
|
||||
- No personal data (names, emails, IP addresses)
|
||||
- No watch history, ratings, or library contents
|
||||
- No API keys or credentials
|
||||
- No information about individual users
|
||||
- No tracking of feature usage patterns — only whether features are enabled
|
||||
|
||||
### How It Works
|
||||
|
||||
- Reports are sent at most **once every 24 hours** (cron job at 12:30 AM UTC)
|
||||
- The instance ID is generated with `Bun.randomUUIDv7()` on first use and stored locally in your database — it is never tied to your identity
|
||||
- If the report fails (network error, timeout), it is silently skipped — telemetry never blocks normal operation
|
||||
- The `PUBLIC_API_URL` environment variable controls where reports are sent (default: `https://public-api.sofa.watch`)
|
||||
|
||||
---
|
||||
|
||||
## Mobile App Analytics
|
||||
|
||||
The native iOS and Android app uses [PostHog](https://posthog.com/) for anonymous usage analytics. It is **disabled by default** and requires explicit user consent.
|
||||
|
||||
### Consent Flow
|
||||
|
||||
On iOS, the app follows Apple's App Tracking Transparency (ATT) framework:
|
||||
|
||||
1. On first launch, iOS prompts you to allow or deny tracking
|
||||
2. If you allow, analytics is enabled by default (you can still disable it in settings)
|
||||
3. If you deny, analytics stays disabled
|
||||
|
||||
On Android, analytics starts disabled and can be toggled on in settings.
|
||||
|
||||
Regardless of platform, you can change your preference at any time in **Settings → Anonymous Usage Reporting**.
|
||||
|
||||
### What Gets Tracked
|
||||
|
||||
When analytics is enabled:
|
||||
|
||||
- **Screen views** — which screens you navigate to (e.g. "home", "search", "title detail"), without any content identifiers
|
||||
- **App lifecycle** — when the app is opened or backgrounded
|
||||
- **Errors** — uncaught JavaScript exceptions for crash reporting
|
||||
|
||||
### What Is NOT Tracked
|
||||
|
||||
- No personal data (name, email, watch history, ratings)
|
||||
- No media library contents or search queries
|
||||
- No passwords or authentication tokens
|
||||
- No user profiles are created in PostHog (`personProfiles: "never"`)
|
||||
|
||||
### Identification
|
||||
|
||||
When analytics is enabled, the app uses your device's platform advertising ID (IDFA on iOS, AAID on Android) as an anonymous identifier. This is the same ID managed by your device's privacy settings — if you've disabled ad tracking at the OS level, the app won't attempt to use it.
|
||||
|
||||
No other identifying information is sent.
|
||||
|
||||
### Disabling
|
||||
|
||||
Toggle analytics off in **Settings → Anonymous Usage Reporting**. This immediately opts out of PostHog and stops all event collection. Your explicit preference persists even if ATT permissions change later.
|
||||
|
||||
---
|
||||
|
||||
## Source Code
|
||||
|
||||
Both telemetry implementations are fully open source:
|
||||
|
||||
- Server telemetry: [`packages/core/src/telemetry.ts`](https://github.com/jakejarvis/sofa/blob/main/packages/core/src/telemetry.ts)
|
||||
- Mobile analytics: [`apps/native/src/lib/posthog.ts`](https://github.com/jakejarvis/sofa/blob/main/apps/native/src/lib/posthog.ts)
|
||||
Reference in New Issue
Block a user