Skip to content
florianjsPublic

About

Run your own private tracker in less than 5min

Topics

Resources

Stars

189 stars

Watchers

1 watching

Forks

Latest commit

 

History

101 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌐 Trackarr

A modern, high-performance private BitTorrent tracker

Built with Nuxt 4 • PostgreSQL • Redis

Node.js Nuxt TypeScript License Discord

Features • Quick Start • Security • Documentation • Live Demo • Discord

Trackarr member dashboard

✨ Features

Privacy & Authentication Performance
Zero-Knowledge Authentication Redis-powered sub-ms peer lookups
Proof of Work anti-abuse PostgreSQL with full-text search
Private torrents (DHT/PEX disabled) HTTP announce with passkeys
Ratio tracking & enforcement Optimized for high concurrency
Community Automation
Bonus points earned by seeding & uploading Torznab API for Prowlarr, Sonarr & Radarr
Bonus shop (upload credit, invites, avatars) IMDb / TMDb / TheTVDB IDs with ID-based search
Bounties: request torrents with a points pot Personal RSS feeds
Global freeleech (timed or open-ended) Torrent moderation queue
Forum, comments, reports, invitations, H&R English & French UI (more languages welcome)
Private messages between members, with blocks Light & dark themes, ⌘K command palette
Security Emergency
Distributed rate limiting Panic Mode — Instant database encryption
Auto IP blacklisting AES-256-GCM protected data
Sanitized user content (XSS) Full restoration with the panic password
Hashed peer IPs in logs & stats Unrecoverable without password

🔐 Security Architecture

Zero-Knowledge Authentication (ZKE)

Trackarr uses a Zero-Knowledge authentication system: the server never sees or stores your password. All cryptographic operations happen client-side.

┌─────────────────────────────────────────────────────────────────────┐
│                        REGISTRATION FLOW                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────┐                              ┌─────────────────┐  │
│  │   CLIENT    │                              │     SERVER      │  │
│  └──────┬──────┘                              └────────┬────────┘  │
│         │                                              │           │
│         │ 1. Solve PoW Challenge (anti-spam)           │           │
│         │ ◄────────────────────────────────────────────┤           │
│         │                                              │           │
│         │ 2. Generate random salt (32 bytes)           │           │
│         │ 3. Derive key = PBKDF2(password, salt)       │           │
│         │ 4. Compute verifier = SHA256(key)            │           │
│         │                                              │           │
│         │ 5. Send {username, salt, verifier} ─────────►│           │
│         │    Password NEVER leaves client           │           │
│         │                                              │           │
│         │                              6. Store salt + │           │
│         │                                 verifier     │           │
│         │                              7. Create session           │
│         │ ◄──────────────────────────────── 8. OK ─────┤           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                          LOGIN FLOW                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────┐                              ┌─────────────────┐  │
│  │   CLIENT    │                              │     SERVER      │  │
│  └──────┬──────┘                              └────────┬────────┘  │
│         │                                              │           │
│         │ 1. Request challenge ───────────────────────►│           │
│         │                                              │           │
│         │ ◄──────── 2. Return {salt, challenge} ───────┤           │
│         │                                              │           │
│         │ 3. Derive key = PBKDF2(password, salt)       │           │
│         │ 4. Compute verifier = SHA256(key)            │           │
│         │ 5. Generate proof = SHA256(verifier+challenge)           │
│         │                                              │           │
│         │ 6. Send {username, proof, challenge} ───────►│           │
│         │    Password NEVER leaves client           │           │
│         │                                              │           │
│         │                       7. Compute expected =  │           │
│         │                          SHA256(storedVerifier+challenge)│
│         │                       8. Verify proof == expected        │
│         │ ◄──────────────────────────────── 9. Session ┤           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Key Properties:

  • Password never transmitted — Only cryptographic proofs
  • PBKDF2 with 100k iterations — Brute-force resistant
  • Unique, single-use challenge per login — Prevents replay attacks
  • Proof of Work — Stops automated registration attacks

Limitation: the stored verifier is enough to compute a valid proof. Someone who obtains a copy of the database can log in as any user without knowing their password, so database backups must be protected like password files. Moving to a PAKE (SRP / OPAQUE) is planned.


🚨 Panic Mode (Emergency Encryption)

The Panic Button allows administrators to instantly encrypt all sensitive data in an emergency. Once activated, all torrent files become unusable and user data is unreadable.

┌───────────────────────────────────────────────────────────────────┐
│                       NORMAL STATE                                │
│  • Torrents downloadable                                          │
│  • User data readable                                             │
│  • Posts & comments visible                                       │
└───────────────────────────────────────────────────────────────────┘
                              │
                    PANIC ACTIVATED
                              │
                              ▼
┌───────────────────────────────────────────────────────────────────┐
│                      ENCRYPTED STATE                              │
│  • .torrent files → AES-256-GCM encrypted (unusable)              │
│  • Torrent names  → [ENCRYPTED]                                   │
│  • Torrent sizes  → 0                                             │
│  • User credentials → Encrypted                                   │
│  • Forum posts    → Encrypted                                     │
└───────────────────────────────────────────────────────────────────┘
                              │
                    RESTORE (with password)
                              │
                              ▼
┌───────────────────────────────────────────────────────────────────┐
│                       RESTORED STATE                              │
│  All data restored to original state                              │
└───────────────────────────────────────────────────────────────────┘

How it works:

  1. First admin sets a Panic Password during registration (min. 12 chars)
  2. Only a hash of the panic password is stored, never the password itself
  3. Activation: Admin → Settings → Panic → confirm and enter the Panic Password
  4. Restoration: enter the Panic Password (works without being logged in, rate limited)

The encryption key is derived from the panic password itself, so the database alone is not enough to decrypt the data. Encryption and restoration each run in a single transaction: a failure leaves the database unchanged.

Encryption details:

Component Algorithm
Key Derivation scrypt (32 bytes) from the panic password, random salt
Encryption AES-256-GCM
IV 12 bytes random, unique per encrypted value

WARNING: Without the Panic Password, encrypted data is permanently lost. There is no recovery mechanism.


🚀 Quick Start

Prerequisites

  • Node.js 22+ • Docker & Docker Compose • npm

DNS Configuration (Required before installation)

IMPORTANT: Before running the installer, you must configure your DNS records to point to your VPS IP address.

Create the following A records pointing to your server's IP:

Subdomain Record Type Value
tracker.your-domain.com A Your VPS IP
announce.your-domain.com A Your VPS IP
monitoring.your-domain.com A Your VPS IP

Note: DNS propagation can take up to 24-48 hours, but usually completes within a few minutes. The installer will fail to obtain SSL certificates if DNS is not properly configured.

Option 1: Automated Installation (Recommended)

Best for production deployments. Handles dependencies, secrets, SSL, and systemd automatically.

# Download and run the installer
curl -fsSL https://raw.githubusercontent.com/florianjs/trackarr/main/scripts/install.sh -o install.sh
chmod +x install.sh
sudo ./install.sh

The installer will:

  • Install Docker and dependencies
  • Generate cryptographic secrets
  • Configure firewall rules
  • Set up TLS/SSL with Let's Encrypt
  • Create systemd service for auto-restart
  • Configure PostgreSQL, Redis, Caddy, and monitoring
  • Set up Prometheus + Grafana monitoring

Monitoring: After installation, Grafana is accessible at https://monitoring.your-domain.com/grafana

Credentials: user admin, with a random password generated by the installer (shown at the end of the install, saved in CREDENTIALS.txt and as GRAFANA_ADMIN_PASSWORD in .env). Lost it? Reset it with:

cd /opt/trackarr
docker exec -it trackarr-grafana grafana cli admin reset-admin-password <new-password>

Grafana Dashboard

Option 2: Development with Docker

PostgreSQL is published on 127.0.0.1:5432 only (not reachable from other machines). Without IP_HASH_SECRET, development uses a random secret per process.

# Clone repository
git clone https://github.com/florianjs/trackarr.git && cd trackarr
cp .env.example .env

# Start all services (app + postgres + redis)
docker compose up -d

# View logs
docker compose logs -f app

Open http://localhost:3000

Torrent list with category filters Torrent page with swarm stats and comments


🔒 Security

For production, always use the install script to ensure proper secret generation and security configuration.

Key Security Features

Layer Protection
Authentication ZKE, PoW anti-abuse, sealed sessions (7 days), SameSite cookies, roles checked in DB on every request
Database SCRAM-SHA-256 auth, TLS, prepared statements, pool limits
Redis Password auth, command restrictions, memory limits
Network Rate limiting, auto IP bans, proxy headers trusted only from TRUSTED_PROXIES
Content Sanitized Markdown & rich text, sandboxed uploads, passkey-protected RSS & scrape
Privacy Hashed IPs in peer stats & logs, passkeys stripped from access logs. The last login IP is kept per user for IP bans, and peer IPs live in Redis while a peer is active.

Rate Limits

Endpoint Limit Ban on Abuse
Public API 100/min 100+ req/10s → auto-block
Mutations 10/min Progressive penalties
Login 10/5min Temporary block, no blacklist
Registration 5/5min IP blacklisted after violations
Panic restore 5/5min IP blacklisted after violations
Tracker 200/min Distributed sliding window

Production Security Checklist

Use install.sh — it handles security automatically:

  • Generates cryptographic secrets (32-64 chars)
  • Configures TLS for all connections
  • Sets up Caddy reverse proxy with HTTPS
  • Configures firewall (ports 80, 443 only)
  • Network isolation (databases not exposed)

Manual steps after install:

  • Set up automated PostgreSQL backups

🏗️ Tech Stack

Layer Technology Purpose
Frontend Nuxt 4, Vue 3, Tailwind CSS SSR, Composition API
i18n @nuxtjs/i18n English & French UI
Backend Nitro Server Engine API routes, middleware
Database PostgreSQL 16 + Drizzle ORM Data persistence, full-text search
Cache Redis 7 Peer lists, sessions, rate limiting
P2P bittorrent-tracker HTTP announces (UDP & WebSocket disabled)
Crypto Web Crypto API, scrypt, AES-256-GCM ZKE auth, Panic encryption
Monitor Prometheus + Grafana Metrics, dashboards, alerting

🐳 Docker Commands

docker compose up -d              # Start services
docker compose down               # Stop services
docker compose logs -f            # View logs
docker compose down -v            # Stop + remove volumes

Health Checks

docker exec trackarr-db pg_isready           # PostgreSQL
docker exec trackarr-redis redis-cli ping    # Redis

Updating

To update your Trackarr installation to the latest version:

cd /opt/trackarr
git checkout main
git pull origin main
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d --build

Note: This will rebuild the containers with the latest code. Your data (PostgreSQL, Redis) is persisted in Docker volumes and will not be affected. Schema changes are applied automatically at startup.

Upgrading from 0.5.x to 0.6.x? Follow the steps in the v0.6.0 release notes (rotate NUXT_SESSION_PASSWORD, new required secrets, RSS now needs a passkey).

Troubleshooting

Full restart (stop and start all services):

cd /opt/trackarr
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d

Full restart with rebuild (if you suspect issues with cached images):

cd /opt/trackarr
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d --build --force-recreate

View logs to debug issues:

docker compose -f docker-compose.prod.yml logs -f
docker compose -f docker-compose.prod.yml logs -f app  # App only

🧪 Development

npm run dev              # Start dev server (HMR)
npm run build            # Production build
npm test                 # Unit & security tests (Vitest)
npx drizzle-kit push     # Push schema changes
npx drizzle-kit studio   # Database GUI

Translations: strings live in i18n/locales/<code>/*.json. To add a language, copy i18n/locales/en/, translate it, and declare the locale in nuxt.config.ts (i18n.locales).

Private messages Admin dashboard
Private messages Admin dashboard

User profile


🤝 Contributing

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

🙏 Acknowledgements

Trackarr is built on the shoulders of giants. We'd like to thank the following open source projects:

Project Role
Nuxt Fullstack Vue framework
Vue.js Reactive frontend framework
bittorrent-tracker BitTorrent tracker library
Drizzle ORM TypeScript ORM
PostgreSQL Database
Redis In-memory cache
ioredis Redis client for Node.js
Tailwind CSS Utility-first CSS
Chart.js Charts & visualizations
Prometheus Metrics collection
Grafana Monitoring dashboards
VitePress Documentation framework
Vitest Testing framework
Pinia State management
Zod Schema validation
Nuxt i18n Internationalization
DOMPurify HTML sanitization
sanitize-html Server-side HTML sanitization

👥 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

About

Run your own private tracker in less than 5min

Topics

Resources

Stars

189 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages