Streamline is a self-hosted media manager — a modern, all-in-one alternative to Sonarr + Radarr, built from scratch with a clean dark UI.
| Dashboard | Mediathek |
|---|---|
![]() |
![]() |
| Suche | Einstellungen |
|---|---|
![]() |
![]() |
- 🎬 Movies & TV Shows in one interface — replaces Sonarr + Radarr
- 🔍 TMDB & TheTVDB — metadata, posters and ratings (user-configurable)
- 📥 SABnzbd — live queue, pause/resume/delete, send NZBs directly
- 🔎 NZBHydra2 / Newznab — integrated indexer search
- 🌐 Custom Indexers — add any Newznab/Torznab indexer by URL and API key
- ⭐ Custom Formats — import scoring rules directly from Radarr/Sonarr JSON export
- 📊 Interactive NZB Search — Radarr-style sortable table with quality, language and score columns
- 📦 Bulk Import — import your existing media collection from a text list
- 🤖 Telegram Bot — search, add and monitor media from Telegram
- 💬 Discord Webhooks — notifications for downloads, new media and daily digest
- 👤 User Management — create users and change passwords from the UI
- 🧙 Onboarding Wizard — guided first-run setup with a 5-minute time window
- 🔐 JWT Authentication with secure token handling
- 🐳 Fully Dockerized — starts with a single command
- Docker >= 24.0
- Docker Compose >= 2.0
git clone https://github.com/fatihbtw/streamline.git
cd streamlinecp .env.example .envOpen .env and set at least JWT_SECRET and BOT_SECRET:
openssl rand -hex 32 # for JWT_SECRET
openssl rand -hex 16 # for BOT_SECRETJWT_SECRET=your_generated_secret_here
BOT_SECRET=your_generated_secret_here
HOST_PORT=7878
ALLOWED_ORIGINS=http://localhost:7878docker compose up -d --buildStreamline is now available at: http://localhost:7878
On first visit, Streamline shows a guided setup wizard where you create your admin account and optionally connect SABnzbd, TMDB/TheTVDB and your first indexer. The wizard is available for 5 minutes — after that it expires and you land on the normal login page.
| Variable | Default | Description |
|---|---|---|
JWT_SECRET |
— | Required. Min. 32 chars. Generate with openssl rand -hex 32 |
BOT_SECRET |
— | Shared secret between backend and bot. Generate with openssl rand -hex 16 |
HOST_PORT |
7878 |
Port exposed on the host |
ALLOWED_ORIGINS |
http://localhost:7878 |
Comma-separated list of allowed CORS origins |
TELEGRAM_TOKEN |
— | Telegram bot token from @BotFather |
TELEGRAM_CHAT_ID |
— | Restrict bot to one chat ID (leave empty for no restriction) |
STREAMLINE_BOT_USER |
bot |
Username of the Streamline bot account |
STREAMLINE_BOT_PASS |
— | Password of the Streamline bot account |
DISCORD_WEBHOOK_URL |
— | Discord webhook URL for notifications |
LOG_LEVEL |
info |
Logging level: error / warn / info / debug |
After login, configure everything under the Settings menu:
Choose between TMDB (movies + TV) and TheTVDB (best for TV shows). Enter the API key and test the connection.
- TMDB API Key (v3): themoviedb.org/settings/api — free
- TheTVDB API Key (v4): thetvdb.com/api-information — free
- URL:
http://192.168.1.x:8080 - API Key: SABnzbd → Configuration → General → API Key
- URL:
http://192.168.1.x:5076 - API Key: NZBHydra2 → Config → Authorization
Add any Newznab/Torznab indexer by name, type, URL and API key. Compatible with Prowlarr, Jackett and NZBHydra2.
Import scoring rules from Radarr or Sonarr:
- In Radarr/Sonarr: Settings → Custom Formats → Export
- Paste the JSON in Streamline → Custom Formats → Import
- Scores are applied automatically to all NZB search results
Go to Users in the sidebar to create users, change passwords and manage roles.
docker exec -it streamline-backend node -e "
const bcrypt = require('bcryptjs');
const Database = require('better-sqlite3');
bcrypt.hash('your_bot_password', 12).then(hash => {
const db = new Database('/app/data/streamline.db');
db.prepare('INSERT OR IGNORE INTO users (id, username, password_hash, role) VALUES (?, ?, ?, ?)').run(
require('crypto').randomUUID(), 'bot', hash, 'user'
);
console.log('Bot user created!');
process.exit(0);
});
"docker exec -it streamline-backend node -e "
const bcrypt = require('bcryptjs');
const Database = require('better-sqlite3');
bcrypt.hash('NewPassword123', 12).then(hash => {
const db = new Database('/app/data/streamline.db');
db.prepare('UPDATE users SET password_hash = ? WHERE username = ?').run(hash, 'admin');
console.log('Password updated!');
process.exit(0);
});
"Streamline includes a native bot container — no external tools needed.
1. Create a bot via @BotFather:
/newbot → Name: Streamline → get token
2. Find your Chat ID:
curl https://api.telegram.org/bot<TOKEN>/getUpdates
# look for "chat":{"id": 123456789}3. Add to .env and restart:
TELEGRAM_TOKEN=123456:ABC-DEF...
TELEGRAM_CHAT_ID=123456789
STREAMLINE_BOT_USER=bot
STREAMLINE_BOT_PASS=your_bot_password| Command | Description |
|---|---|
/start |
Show help and all commands |
/status |
System overview |
/library |
Full media library |
/movies |
Movies only |
/series |
TV shows only |
/wanted |
Wanted media |
/search <title> |
Search and add inline via buttons |
/add_movie <title or ID> |
Add a movie by title or TMDB ID |
/add_series <title or ID> |
Add a TV show by title or TMDB ID |
/queue |
SABnzbd live queue |
/history |
Download history |
Discord: Server Settings → Integrations → Webhooks → New Webhook → copy URL
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...Automatic notifications for: new media added, download completed/failed, daily digest at 08:00.
┌─────────────────────────────────────────────┐
│ Browser / Client │
└──────────────────────┬──────────────────────┘
│ HTTP :7878
┌──────────────────────▼──────────────────────┐
│ Nginx (Frontend Container) │
│ React SPA + Reverse Proxy for /api/* │
└──────────┬──────────────────────────────────┘
│ /api/* → http://backend:3001
┌──────────▼──────────────────────────────────┐
│ Node.js / Express (Backend Container) │
│ Auth │ Media │ Search │ Settings │ DLs │
└──────────┬──────────────────────────────────┘
│
┌──────────▼──────────┐ ┌─────────────────┐
│ SQLite (Persistent │ │ External APIs: │
│ Docker Volume) │ │ TMDB, TheTVDB, │
└─────────────────────┘ │ SABnzbd, Hydra │
└─────────────────┘
┌─────────────────────────────────────────────┐
│ Bot Container (Node.js) │
│ Telegram polling + Discord webhooks │
└─────────────────────────────────────────────┘
| Measure | Details |
|---|---|
| Password hashing | bcrypt with 12 salt rounds |
| JWT tokens | HS256, 24h expiry, issuer validation |
| Rate limiting | 200 req/15min general, 20 req/15min for login |
| Helmet.js | Security HTTP headers (CSP, HSTS, etc.) |
| Input validation | express-validator on all endpoints |
| SQL injection protection | Prepared statements (better-sqlite3) |
| CORS whitelist | Only origins listed in ALLOWED_ORIGINS |
| Non-root container | Backend runs as unprivileged streamline user |
| API keys masked | Never returned in plaintext after saving |
| Timing-attack protection | Always runs bcrypt.compare(), even for invalid users |
# Backup
docker cp streamline-backend:/app/data/streamline.db ./backup_$(date +%Y%m%d).db
# Restore
docker cp ./backup_20260101.db streamline-backend:/app/data/streamline.db
docker restart streamline-backendgit pull
docker compose down
docker compose up -d --builddocker compose logs -f # all containers
docker compose logs -f backend # backend only
docker compose logs -f frontend # nginx only
docker compose logs -f bot # bot onlyContainer won't start:
docker compose logs backend
# Common cause: JWT_SECRET not set or too short (min. 32 chars)Login not working after update:
Browser: Ctrl + Shift + R (hard refresh, clears cached JS)
TMDB/TheTVDB search not working:
- Check the API key under Settings → Metadata Provider
- TMDB requires v3 API keys (not Bearer tokens)
SABnzbd unreachable:
- Use the host IP instead of
localhost— e.g.http://192.168.1.100:8080
Port conflict:
HOST_PORT=8989Bot not responding:
- Verify
STREAMLINE_BOT_USER/STREAMLINE_BOT_PASSmatch the user in Streamline → Users - Run
docker compose logs botfor details
MIT License
Inspired by Sonarr and Radarr. Uses the TMDB API and TheTVDB API.
⚠️ Disclaimer: Streamline is a management tool only. Downloading copyrighted content without permission is illegal and the sole responsibility of the user.



