|
All checks were successful
Build & Push Docker Image / build (push) Successful in 59s
|
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| db | ||
| dev | ||
| docs | ||
| scripts | ||
| src | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| .npmrc | ||
| .nvmrc | ||
| AGENTS.md | ||
| biome.json | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| knexfile.ts | ||
| lefthook.yml | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
watchcraft
Self-hosted media recommendation app for Jellyfin libraries. Rate titles from your library, discover similar content via TMDB, and optionally request media through Jellyseerr or get one-line AI explanations via Ollama.
Features
- Star-rating UI for your Jellyfin library
- Discovery feed powered by TMDB recommendations
- Request buttons via Seerr / Jellyseerr (optional)
- One-sentence AI explanations via Ollama (optional)
- Multi-user support with shared audiences
- Three auth modes: local accounts, SSO proxy header, or single implicit user
- SQLite (zero-config), PostgreSQL, or MariaDB
Requirements
- A running Jellyfin instance
- A free TMDB API key
- Docker
Quick start
cp docker-compose.yml docker-compose.local.yml # or edit in place
# Fill in JELLYFIN_URL, JELLYFIN_API_KEY, TMDB_API_KEY, SESSION_SECRET
docker compose up -d
# Open http://localhost:3000 — setup wizard runs on first visit (local auth)
Generate a session secret:
openssl rand -hex 32
Configuration
Required
| Variable | Description |
|---|---|
JELLYFIN_URL |
Base URL of your Jellyfin instance (e.g. http://jellyfin:8096) |
JELLYFIN_API_KEY |
Jellyfin API key |
TMDB_API_KEY |
TMDB v3 API key (free tier is sufficient) |
SESSION_SECRET |
Random string for signing session cookies — use a long random value in production |
Auth
| Variable | Default | Description |
|---|---|---|
AUTH_MODE |
local |
local | proxy | none |
AUTH_HEADER |
X-authentik-username |
Header to read username from when AUTH_MODE=proxy |
Auth modes:
local— bcrypt password auth with Express sessions. A setup wizard runs on first visit; the first account created is always admin.proxy— reads the username fromAUTH_HEADER. Use with Authentik, Authelia, or any other SSO proxy. SetAUTH_MODE=proxyandAUTH_HEADERto match your proxy's header name.none— single implicit user nameddefault. For trusted-network-only deployments with no login UI.
Database
| Variable | Default | Description |
|---|---|---|
DB_CLIENT |
better-sqlite3 |
better-sqlite3 | pg | mysql2 |
DB_SQLITE_PATH |
./data/db.sqlite |
SQLite file path (SQLite only) |
DB_HOST |
— | Hostname (PostgreSQL / MariaDB) |
DB_PORT |
— | Port (PostgreSQL / MariaDB) |
DB_NAME |
— | Database name |
DB_USER |
— | Database user |
DB_PASSWORD |
— | Database password |
SQLite is zero-config and the default. PostgreSQL and MariaDB are useful if you want to run multiple watchcraft users off a shared database instance.
Optional integrations
| Variable | Description |
|---|---|
SEERR_URL |
Seerr / Jellyseerr base URL — enables Request buttons on discovery cards |
SEERR_API_KEY |
Seerr API key |
OLLAMA_URL |
Ollama base URL — enables one-sentence recommendation explanations |
OLLAMA_MODEL |
Ollama model to use (default: llama3.2:3b) |
Cache TTLs
All intervals have sensible defaults. Override if needed.
| Variable | Default | Description |
|---|---|---|
CACHE_LIBRARY_HOURS |
6 |
How often to re-sync the Jellyfin library |
CACHE_HISTORY_HOURS |
1 |
How often to re-sync play history |
CACHE_TMDB_DAYS |
30 |
How long TMDB recommendation data is cached |
CACHE_METADATA_DAYS |
90 |
How long TMDB metadata is cached |
CACHE_SEERR_MINUTES |
15 |
How often to poll Seerr for request status |
CACHE_MAX_REC_LINKS |
50000 |
Max recommendation graph edges before cache pruning |
Misc
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port |
TZ |
Etc/UTC |
Timezone (used for cron scheduling) |
SESSION_STORE_PATH |
./data/sessions.sqlite |
Path for the session SQLite store |
LOG_LEVEL |
info |
trace | debug | info | warn | error | fatal |
LOG_FORMAT |
pretty |
pretty (human-readable) | json (for log aggregators) |
Data & persistence
The app writes two files to /app/data:
db.sqlite— application databasesessions.sqlite— session store
Mount /app/data to a persistent volume so data survives container restarts. The example docker-compose.yml uses a named Docker volume (watchcraft-data).
Database migrations run automatically on every startup — no manual migration step needed.
Upgrading
docker compose pull
docker compose up -d
Migrations run on startup; no other steps required.
Health check
GET /health returns 200 OK when the app is ready.