Skip to content

Latest commit

Β 

History

History
301 lines (220 loc) Β· 17.8 KB

File metadata and controls

301 lines (220 loc) Β· 17.8 KB

🌐 Trackarr

A modern, high-performance private BitTorrent tracker

Three containers β€” Nuxt 4 web Β· Nitro API Β· Go tracker β€” backed by PostgreSQL and Redis.

Node.js Nuxt Go TypeScript License

Features β€’ Architecture β€’ Quick Start β€’ Static deployment β€’ Documentation β€’ Live Demo

Trackarr Homepage


✨ Features

Privacy & authentication

  • Zero-Knowledge auth β€” PBKDF2-600k + SHA-256 verifier; the password never leaves the browser. Same flow drives sign-in, passkey rotation, and change-password.
  • Proof of Work on registration stops drive-by signup spam.
  • Hashed IPs β€” SHA-256 with daily-rotating salt; no raw IP persisted. Banning a user atomically banlists their last-known IP.
  • Privacy toggles β€” hide last-seen on public profile (mods/admins always see the truth).

Browse, upload & operate

  • Rich media metadata β€” TMDb (films + TV), IGDB (games), Open Library + Google Books (books); user locale drives the lookup language.
  • Smart media-id paste β€” drop an IMDb / TMDb / TVDB / IGDB slug or ISBN into search to filter the listing.
  • Dedicated upload page β€” auto title + tags from filename, multi-source search picker, duplicate preflight, conditional ID block per category, Tiptap WYSIWYG description, NFO drag-drop (CP437 β†’ UTF-8).
  • Operator console β€” /admin covers users, categories, roles, invites, branding, panic, tags, Torznab, reports, HnR.
  • Notification fan-out β€” every event-emitting route hits Postgres + Redis pub/sub + the user's chosen external transport (SMTP, Telegram, Discord, ntfy, Gotify, Pushover, Slack, Mattermost, webhook, Apprise, Web Push).

Tracker protocols

  • HTTP announce (BEP 3) on 8080/tcp; sub-ms p99; alloc-friendly bencode path.
  • UDP announce (BEP 15) on 6969/udp; ~6×–8Γ— cheaper on the wire than HTTP; stateless connection_id = HMAC-SHA256(secret, ip β€– minute), so no per-id memory.
  • BEP 41 URL_DATA passkey β€” udp://host:6969/announce/PASSKEY works as-is in every modern client.
  • Multi-tier .torrent files β€” generator advertises HTTP + UDP independently. TRACKER_UDP_ENABLED=false disables UDP and drops it from new .torrent files in one go.

Bonus economy & resilience

  • Seed-bonus points β€” customisable per-minute rules (time, torrent age, rarity); tiered curves with live preview; ledger-backed.
  • Bonus shop β€” operator-curated catalogue with built-in upload_credit and invite effects.
  • Bonus events β€” time-bounded Freeleech / Silverleech / custom multipliers, applied on the announce hot path.
  • Panic Mode β€” instant AES-256-GCM encryption of torrent data + user fields; recovery requires the original Panic Password. See Panic Mode.
  • Distributed rate limiting β€” Redis-backed sliding windows; progressive penalties; auto IP bans.
  • Optional static deployment β€” distroless nginx serves a CSR bundle in ~28 MB (see below).

Federation (inter-instance)

  • Owner-curated network β€” connect independent Trackarr instances through a double opt-in, Ed25519-signed handshake; off by default, enabled from /admin/federation.
  • Granular, asymmetric scopes β€” share/accept catalog, social, accounts, swarm per peer and per direction; suspend / block / revoke / re-scope any peer from the owner console.
  • Federated catalogue β€” browse & search partner content (cron-synced cache + live fan-out) with cross-instance dedupe; a remote download links back to its origin instance (never your local passkey).
  • Federated social & identity β€” follow remote uploaders, read-only sanitized comments/forum, and prove you own an account on a partner (verified-identity badge + read-only reputation; no SSO, economies stay isolated).
  • Optional swarm cross-announce β€” opt-in per torrent and per peer, behind TRACKER_FEDERATION_SWARM (off); only ip/port/isSeeder cross the wire and ratio/HnR stay local. See Federation.

πŸ—οΈ Architecture

Three independent containers behind Caddy, plus the usual Postgres + Redis. They share zero process state β€” Redis is the only cross-cutting bus.

                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   Browser ──HTTPS──►     β”‚ Caddy   :80 / :443 / :443/udp (HTTP/3)      β”‚
                          β”‚   /announce*  β†’  tracker  :8080   (BEP 3)   β”‚
                          β”‚   /api/*      β†’  api      :4000             β”‚
                          β”‚   /uploads/*  β†’  api      :4000             β”‚
                          β”‚   /*          β†’  web      :3000   (Nuxt SSR)β”‚
                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   BT client ──UDP──►   tracker :6969   (BEP 15, bypasses Caddy β€” UDP can't be reverse-proxied)

           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚  apps/web    β”‚    β”‚  apps/api    β”‚    β”‚ apps/tracker β”‚
           β”‚  Nuxt 4 SSR  β”‚    β”‚  Nitro 4     β”‚    β”‚  Go 1.25     β”‚
           β”‚  Vue 3 / TS  β”‚    β”‚  Drizzle ORM β”‚    β”‚  sqlc        β”‚
           β”‚  (stateless) β”‚    β”‚  Zod, h3     β”‚    β”‚  HTTP + UDP  β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚                   β”‚
                                      β–Ό                   β–Ό
                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”‚       PgBouncer  :6432         β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                               β–Ό
                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”‚       PostgreSQL :5432         β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                       api ──►│       Redis    :6379           │◄── tracker
                              β”‚  peers, sessions, rate-limit,  β”‚
                              β”‚  seed-bonus, notification bus  β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why three containers

  • The tracker is its own thing. It's the hot path β€” every BitTorrent client in the swarm hits /announce every few minutes. A static Go binary on scratch (~10 MB image, sub-ms p99) means a single broken Nuxt deploy can't take down announces. Two transports β€” BEP 3 over HTTP/8080 and BEP 15 over UDP/6969 β€” share one wire-agnostic processor.
  • API and web are split. apps/api (Nitro standalone) owns every /api/* route, upload endpoints, metadata lookups, admin tools. apps/web is Nuxt SSR β€” rendered shell + page chunks. They scale and redeploy independently.
  • Distroless everywhere. apps/web + apps/api run on gcr.io/distroless/nodejs24-debian13:nonroot; apps/tracker runs on scratch; the static frontend runs on cgr.dev/chainguard/nginx. No shells, no package managers, non-root by default.

More details: Architecture guide.


πŸš€ Quick Start

Prerequisites

  • Docker 20+ and Docker Compose v2
  • For production: a domain name, ports 80/443 open, an ACME_EMAIL for Let's Encrypt.

Pre-built images

Trackarr publishes signed multi-arch images to GHCR β€” no need to build anything locally:

Image Role
ghcr.io/dim145/opentracker/api:latest Nitro API
ghcr.io/dim145/opentracker/front-ssr:latest Nuxt SSR web
ghcr.io/dim145/opentracker/front:latest Static SPA (overlay)
ghcr.io/dim145/opentracker/tracker:latest Go BitTorrent tracker

Full list & tags: https://github.com/Dim145?tab=packages&repo_name=opentracker.

docker-compose.prod.yml pulls these by default; pin a specific tag with IMAGE_TAG=v0.17.0.

Production deployment

git clone https://github.com/Dim145/opentracker.git /opt/trackarr
cd /opt/trackarr
cp .env.example .env

cat >> .env <<EOF
NODE_ENV=production
DOMAIN=your-domain.com
TRACKER_DOMAIN=tracker.your-domain.com
ACME_EMAIL=admin@your-domain.com

NUXT_SESSION_SECRET=$(openssl rand -hex 32)
ADMIN_API_KEY=$(openssl rand -hex 32)
IP_HASH_SECRET=$(openssl rand -hex 32)
CHANNEL_ENCRYPTION_KEY=$(openssl rand -hex 32)
DB_PASSWORD=$(openssl rand -base64 24)
REDIS_PASSWORD=$(openssl rand -base64 24)

NUXT_PUBLIC_TRACKER_HTTP_URL=https://tracker.your-domain.com/announce
NUXT_PUBLIC_TRACKER_UDP_URL=udp://tracker.your-domain.com:6969/announce
EOF

docker compose -f docker-compose.prod.yml up -d

Point your-domain.com + tracker.your-domain.com at the VPS IP, then open https://your-domain.com β€” the first user to register becomes the admin and is prompted to set a panic password.

Updates are a git pull && docker compose -f docker-compose.prod.yml pull && up -d (or pin IMAGE_TAG to a release). Volumes (postgres_data, redis_data, uploads_data, caddy_data) survive rebuilds.

Full walk-through, env reference, and operations: doc/guide/getting-started.md.


πŸͺΆ Static deployment (no SSR)

A second image β€” ghcr.io/dim145/opentracker/front β€” serves a fully static SPA from distroless Chainguard nginx:

SSR (default) Static
Image size 254 MB 28 MB
Idle RSS ~120 MB ~7 MB
Cold start ~2 s <100 ms
Base image distroless/nodejs24 chainguard/nginx
First paint server-rendered HTML SPA shell, hydrates client-side
docker compose \
  -f docker-compose.prod.yml \
  -f docker-compose.static.yml \
  --env-file .env \
  up -d

The static bundle fetches GET /api/runtime-config on boot and patches useRuntimeConfig().public, so the same image is portable across domains β€” only the API container needs the NUXT_PUBLIC_TRACKER_*_URL vars.


πŸ—οΈ Tech stack

Layer Technology Notes
Frontend Nuxt 4, Vue 3, Tailwind CSS, Tiptap SSR by default, opt-in static SPA build
API Nitro 4 (Node 24), Drizzle ORM, Zod Standalone container, distroless runtime
Tracker Go 1.25, sqlc scratch-based image, sub-ms announce p99
Database PostgreSQL 16 gin_trgm_ops full-text, drizzle-kit push
Connection pool PgBouncer Transaction-mode pooling
Cache / queue Redis 7 Peer hashes, sessions, rate-limit windows, pub/sub
Reverse proxy Caddy 2 Auto-HTTPS, HTTP/3
Crypto Web Crypto API, scrypt, AES-256-GCM ZKE auth, Panic encryption
Observability Prometheus /metrics Dedicated port on the API container
Monorepo pnpm workspaces packages/{shared,db} + apps/{web,api,tracker}

Security deep-dive: doc/guide/security.md, doc/guide/zero-knowledge-auth.md, doc/guide/panic-mode.md.


πŸ“Έ Screenshots

Torrent List Torrent Details Forum User profile Admin dashboard Moderation queue


🀝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing)
  5. Open a Pull Request

The repo is a pnpm monorepo. For local hacking, see Getting Started β€” Local development; for running the full container stack on your laptop, see Local Production.


πŸ™ Acknowledgements

Trackarr is built on the shoulders of giants. Thanks to the following open-source projects:

Project Role
Nuxt Fullstack Vue framework
Vue.js Reactive frontend framework
Nitro Universal JS server engine
Drizzle ORM TypeScript ORM
sqlc Go DB codegen for the tracker
PostgreSQL Database
Redis In-memory cache
ioredis Redis client for Node.js
Caddy Reverse proxy + HTTPS
Chainguard Distroless container images
Tailwind CSS Utility-first CSS
Tiptap WYSIWYG editor
Chart.js Charts & visualizations
Iconify Icon framework (Phosphor set)
VitePress Documentation framework
Pinia State management
Zod Schema validation
TMDb Films + TV metadata
IGDB Video-game metadata
Open Library Books + ebook metadata
Google Books API Books metadata (fallback)
web-push RFC 8291 / 8292 push delivery

πŸ‘₯ Contributors

Thanks to all our contributors! Sorted by number of commits.

Avatar Contributor Commits
Dim145 4
IkiaeM 4

πŸ“„ License

MIT License β€” see LICENSE for details.


Built with ❀️ for the P2P community

Back to top