From 26cab783192e6accd681402076279d65f71c8f60 Mon Sep 17 00:00:00 2001 From: Florian <52180080+florianjs@users.noreply.github.com> Date: Wed, 7 Jan 2026 19:51:58 +0100 Subject: [PATCH] Rebranding to Trackarr - Updated all references from OpenTracker to Trackarr --- .env.example | 10 +- .github/workflows/deploy-docs.yml | 68 +++++++ Dockerfile | 2 +- app/components/admin/Branding.vue | 20 +-- app/components/admin/HomepageContent.vue | 4 +- app/components/admin/System.vue | 2 +- app/layouts/default.vue | 6 +- app/pages/auth/login.vue | 2 +- app/pages/auth/register.vue | 2 +- app/pages/search.vue | 2 +- doc/.vitepress/config.ts | 8 +- doc/guide/backup-restore.md | 44 ++--- doc/guide/configuration.md | 28 +-- doc/guide/getting-started.md | 8 +- doc/guide/load-testing.md | 45 ++--- doc/guide/local-production.md | 14 +- doc/guide/troubleshooting.md | 30 ++-- doc/package.json | 2 +- doc/public/.nojekyll | 0 doc/reference/env.md | 88 +++++----- docker-compose.local.yml | 2 +- docker-compose.prod.yml | 4 +- docker/caddy/Caddyfile | 6 +- .../provisioning/dashboards/dashboards.yml | 6 +- ...r-overview.json => trackarr-overview.json} | 10 +- .../provisioning/datasources/datasources.yml | 2 +- docker/pgbouncer/pgbouncer.ini | 4 +- docker/prometheus/prometheus.yml | 16 +- drizzle.config.ts | 10 +- package.json | 2 +- scripts/db-push.sh | 6 +- scripts/setup-local-prod.sh | 2 +- scripts/update-local.sh | 4 +- server/api/admin/system/update.get.ts | 4 +- server/api/admin/system/version.get.ts | 7 +- server/api/rss/category/[slug].get.ts | 6 +- server/api/rss/latest.get.ts | 6 +- server/db/index.ts | 2 +- server/utils/alerts.ts | 4 +- server/utils/settings.ts | 4 +- tests/load/load-test.ts | 166 +++++++++++------- 41 files changed, 386 insertions(+), 272 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 doc/public/.nojekyll rename docker/grafana/provisioning/dashboards/json/{opentracker-overview.json => trackarr-overview.json} (98%) diff --git a/.env.example b/.env.example index 40327e5..cec2312 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # ============================================================================= -# OpenTracker Environment Configuration +# Trackarr Environment Configuration # ============================================================================= # This file works for both development and production. # Copy to .env and adjust values as needed. @@ -24,16 +24,16 @@ NODE_ENV=development # Database (PostgreSQL) # ----------------------------------------------------------------------------- # Development (SSL disabled) -DATABASE_URL=postgres://tracker:tracker@localhost:5432/opentracker +DATABASE_URL=postgres://tracker:tracker@localhost:5432/trackarr # Production (SSL enforced - sslmode=require automatically added in nuxt.config.ts) -# DATABASE_URL=postgres://tracker:STRONG_PASSWORD@pgbouncer:6432/opentracker +# DATABASE_URL=postgres://tracker:STRONG_PASSWORD@pgbouncer:6432/trackarr # Production database settings DB_USER=tracker # ⚠️ PRODUCTION: Generate with: openssl rand -base64 32 DB_PASSWORD=tracker -DB_NAME=opentracker +DB_NAME=trackarr DB_PORT=5432 # Database connection pool @@ -94,7 +94,7 @@ NUXT_SESSION_SECRET= ADMIN_API_KEY= # IP hashing secret (REQUIRED for production - generate with: openssl rand -hex 32) -IP_HASH_SECRET=opentracker-default-secret-change-me +IP_HASH_SECRET=trackarr-default-secret-change-me # ----------------------------------------------------------------------------- # Monitoring (Production only - Grafana) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..50ee40e --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,68 @@ +# Deploy VitePress documentation to GitHub Pages +name: Deploy Documentation + +on: + push: + branches: + - main + paths: + - 'doc/**' + - '.github/workflows/deploy-docs.yml' + workflow_dispatch: + +# Allow only one concurrent deployment +concurrency: + group: pages + cancel-in-progress: false + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./doc + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: './doc/package-lock.json' + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Install dependencies + run: npm ci + + - name: Build documentation + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: doc/.vitepress/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/Dockerfile b/Dockerfile index 58f61fd..7963722 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Production Dockerfile for OpenTracker +# Production Dockerfile for Trackarr FROM node:20-alpine AS base # Install dependencies only when needed diff --git a/app/components/admin/Branding.vue b/app/components/admin/Branding.vue index e9cdd1b..ed17f74 100644 --- a/app/components/admin/Branding.vue +++ b/app/components/admin/Branding.vue @@ -44,7 +44,7 @@ 'font-medium': !siteNameBold, }" :style="{ color: siteNameColor || '' }" - v-html="siteName || 'OpenTracker'" + v-html="siteName || 'Trackarr'" > {{ siteSubtitle || `v${useRuntimeConfig().public.appVersion}` @@ -60,7 +60,7 @@ > @@ -389,7 +389,7 @@ > @@ -405,11 +405,11 @@ type="text" maxlength="100" class="w-full bg-bg-tertiary border border-border rounded px-3 py-2 text-sm text-text-primary focus:outline-none focus:border-white/20" - :placeholder="`- ${siteName || 'OpenTracker'}`" + :placeholder="`- ${siteName || 'Trackarr'}`" />

Example: "Search Torrents{{ - pageTitleSuffix || ` - ${siteName || 'OpenTracker'}` + pageTitleSuffix || ` - ${siteName || 'Trackarr'}` }}"

@@ -486,7 +486,7 @@ > @@ -587,7 +587,7 @@ diff --git a/doc/.vitepress/config.ts b/doc/.vitepress/config.ts index 20873a9..d4c92e6 100644 --- a/doc/.vitepress/config.ts +++ b/doc/.vitepress/config.ts @@ -1,10 +1,10 @@ import { defineConfig } from 'vitepress'; export default defineConfig({ - title: 'OpenTracker', + title: 'Trackarr', description: 'A modern, high-performance private BitTorrent tracker', - base: '/opentracker/', + base: '/trackarr/', ignoreDeadLinks: [/^http:\/\/localhost/], @@ -88,13 +88,13 @@ export default defineConfig({ }, socialLinks: [ - { icon: 'github', link: 'https://github.com/florianjs/opentracker' }, + { icon: 'github', link: 'https://github.com/florianjs/trackarr' }, { icon: 'discord', link: 'https://discord.gg/GRFu35djvz' }, ], footer: { message: 'Released under the MIT License.', - copyright: '2025-present OpenTracker', + copyright: '2025-present Trackarr', }, search: { diff --git a/doc/guide/backup-restore.md b/doc/guide/backup-restore.md index 1b3c3b5..3b5b614 100644 --- a/doc/guide/backup-restore.md +++ b/doc/guide/backup-restore.md @@ -18,8 +18,8 @@ It is **strongly recommended** to back up your PostgreSQL database to **multiple ### Creating a Database Backup ```bash -cd /opt/opentracker -docker compose exec db pg_dump -U opentracker opentracker | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz +cd /opt/trackarr +docker compose exec db pg_dump -U tracker trackarr | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz ``` ### Backing Up Secrets @@ -31,17 +31,17 @@ Your `.env` file contains critical secrets that **cannot be regenerated**. If yo Always back up your `.env` file alongside your database: ```bash -cd /opt/opentracker +cd /opt/trackarr cp .env .env.backup_$(date +%Y%m%d_%H%M%S) ``` Key secrets to preserve: -| Variable | Purpose | -|----------|---------| -| `TRACKER_SECRET` | Generates user passkeys — losing this invalidates all `.torrent` files | -| `IP_HASH_SECRET` | Hashes peer IPs — losing this breaks peer tracking continuity | -| `NUXT_SESSION_PASSWORD` | Encrypts sessions — losing this logs out all users | +| Variable | Purpose | +| ----------------------- | ---------------------------------------------------------------------- | +| `TRACKER_SECRET` | Generates user passkeys — losing this invalidates all `.torrent` files | +| `IP_HASH_SECRET` | Hashes peer IPs — losing this breaks peer tracking continuity | +| `NUXT_SESSION_PASSWORD` | Encrypts sessions — losing this logs out all users | Store a copy of your `.env` file in a secure location (password manager, encrypted storage) separate from your database backups. @@ -54,7 +54,7 @@ To restore your database on a new VPS: Run the installer as usual: ```bash -curl -fsSL https://raw.githubusercontent.com/florianjs/opentracker/main/scripts/install.sh -o install.sh +curl -fsSL https://raw.githubusercontent.com/florianjs/trackarr/main/scripts/install.sh -o install.sh chmod +x install.sh sudo ./install.sh ``` @@ -62,7 +62,7 @@ sudo ./install.sh **2. Stop the application** ```bash -cd /opt/opentracker +cd /opt/trackarr docker compose -f docker-compose.prod.yml down ``` @@ -70,13 +70,13 @@ docker compose -f docker-compose.prod.yml down ```bash # From your old server or local machine -scp backup_20260102_120000.sql.gz user@new-server:/opt/opentracker/ +scp backup_20260102_120000.sql.gz user@new-server:/opt/trackarr/ ``` **4. Start only the database container** ```bash -cd /opt/opentracker +cd /opt/trackarr docker compose -f docker-compose.prod.yml up -d db ``` @@ -84,11 +84,11 @@ docker compose -f docker-compose.prod.yml up -d db ```bash # Drop and recreate the database -docker compose exec db dropdb -U opentracker opentracker -docker compose exec db createdb -U opentracker opentracker +docker compose exec db dropdb -U tracker trackarr +docker compose exec db createdb -U tracker trackarr # Restore from backup -gunzip -c backup_20260102_120000.sql.gz | docker compose exec -T db psql -U opentracker opentracker +gunzip -c backup_20260102_120000.sql.gz | docker compose exec -T db psql -U tracker trackarr ``` **6. Start all services** @@ -109,18 +109,18 @@ If you're migrating to a new domain (e.g., `old-tracker.com` → `new-tracker.co Create A records for your new domain pointing to your server's IP: -| Subdomain | Record Type | Value | -|-----------|-------------|-------| -| `tracker.new-domain.com` | A | Your VPS IP | -| `announce.new-domain.com` | A | Your VPS IP | -| `monitoring.new-domain.com` | A | Your VPS IP | +| Subdomain | Record Type | Value | +| --------------------------- | ----------- | ----------- | +| `tracker.new-domain.com` | A | Your VPS IP | +| `announce.new-domain.com` | A | Your VPS IP | +| `monitoring.new-domain.com` | A | Your VPS IP | **2. Update environment variables** Edit your `.env` file: ```bash -cd /opt/opentracker +cd /opt/trackarr nano .env ``` @@ -138,7 +138,7 @@ Delete the old Caddy data to force new certificate generation: ```bash docker compose -f docker-compose.prod.yml down -docker volume rm opentracker_caddy_data || true +docker volume rm caddy_data || true docker compose -f docker-compose.prod.yml up -d ``` diff --git a/doc/guide/configuration.md b/doc/guide/configuration.md index 4860cec..7a35451 100644 --- a/doc/guide/configuration.md +++ b/doc/guide/configuration.md @@ -6,20 +6,20 @@ Trackarr is configured primarily through environment variables. This page covers ### Core Settings -| Variable | Description | Default | -| ----------------------- | ---------------------------------- | ------------- | +| Variable | Description | Default | +| ----------------------- | ---------------------------------- | ---------- | | `NUXT_PUBLIC_SITE_NAME` | Your tracker's display name | `Trackarr` | -| `NUXT_PUBLIC_SITE_URL` | Public URL of your tracker | — | -| `NUXT_SESSION_PASSWORD` | Session encryption key (32+ chars) | — | +| `NUXT_PUBLIC_SITE_URL` | Public URL of your tracker | — | +| `NUXT_SESSION_PASSWORD` | Session encryption key (32+ chars) | — | ### Database -| Variable | Description | Default | -| ------------------- | ---------------------------- | ------------- | -| `DATABASE_URL` | PostgreSQL connection string | — | -| `POSTGRES_USER` | Database username | `opentracker` | -| `POSTGRES_PASSWORD` | Database password | — | -| `POSTGRES_DB` | Database name | `opentracker` | +| Variable | Description | Default | +| ------------------- | ---------------------------- | ---------- | +| `DATABASE_URL` | PostgreSQL connection string | — | +| `POSTGRES_USER` | Database username | `tracker` | +| `POSTGRES_PASSWORD` | Database password | — | +| `POSTGRES_DB` | Database name | `trackarr` | ### Redis @@ -84,7 +84,7 @@ services: ports: - '3000:3000' environment: - - DATABASE_URL=postgresql://opentracker:opentracker@db:5432/opentracker + - DATABASE_URL=postgresql://tracker:tracker@db:5432/trackarr - REDIS_URL=redis://redis:6379 depends_on: - db @@ -93,9 +93,9 @@ services: db: image: postgres:16-alpine environment: - - POSTGRES_USER=opentracker - - POSTGRES_PASSWORD=opentracker - - POSTGRES_DB=opentracker + - POSTGRES_USER=tracker + - POSTGRES_PASSWORD=tracker + - POSTGRES_DB=trackarr volumes: - postgres_data:/var/lib/postgresql/data diff --git a/doc/guide/getting-started.md b/doc/guide/getting-started.md index 24ae2fb..2a2a46d 100644 --- a/doc/guide/getting-started.md +++ b/doc/guide/getting-started.md @@ -36,7 +36,7 @@ Best for production deployments. Handles dependencies, secrets, SSL, and systemd ```bash # Download and run the installer -curl -fsSL https://raw.githubusercontent.com/florianjs/opentracker/main/scripts/install.sh -o install.sh +curl -fsSL https://raw.githubusercontent.com/florianjs/trackarr/main/scripts/install.sh -o install.sh chmod +x install.sh sudo ./install.sh ``` @@ -59,7 +59,7 @@ For local development or production deployment with guided setup: ```bash # Clone repository -git clone https://github.com/florianjs/opentracker.git && cd opentracker +git clone https://github.com/florianjs/trackarr.git && cd trackarr # Run interactive setup ./scripts/setup.sh @@ -89,7 +89,7 @@ For full manual control over the installation: ```bash # Clone repository -git clone https://github.com/florianjs/opentracker.git && cd opentracker +git clone https://github.com/florianjs/trackarr.git && cd trackarr # Copy and configure environment cp .env.example .env @@ -127,7 +127,7 @@ Open `https://your-domain.com` to access your tracker. To update your Trackarr installation to the latest version: ```bash -cd /opt/opentracker +cd /opt/trackarr git checkout main git pull origin main docker compose -f docker-compose.prod.yml down diff --git a/doc/guide/load-testing.md b/doc/guide/load-testing.md index e213b5c..4f7a7de 100644 --- a/doc/guide/load-testing.md +++ b/doc/guide/load-testing.md @@ -9,10 +9,10 @@ Trackarr includes a built-in load testing suite to measure tracker performance u docker compose -f docker-compose.loadtest.yml up -d --build # Wait for the tracker to be ready -docker logs -f opentracker-loadtest +docker logs -f trackarr-loadtest # Initialize the database (first time only) -docker exec opentracker-loadtest npm run db:push +docker exec trackarr-loadtest npm run db:push # Run load tests npm run test:load -- --peers 100 --duration 10 @@ -20,11 +20,11 @@ npm run test:load -- --peers 100 --duration 10 ## Test Scenarios -| Scenario | Peers | Duration | Purpose | -|----------|-------|----------|---------| -| Smoke | 100 | 10s | Validate setup | -| Normal | 1,000 | 30s | Production-like load | -| Stress | 5,000 | 60s | Find breaking points | +| Scenario | Peers | Duration | Purpose | +| -------- | ----- | -------- | -------------------- | +| Smoke | 100 | 10s | Validate setup | +| Normal | 1,000 | 30s | Production-like load | +| Stress | 5,000 | 60s | Find breaking points | ### Smoke Test @@ -52,14 +52,14 @@ npm run test:load -- --peers 5000 --duration 60 --torrents 100 ## Configuration Options -| Option | Default | Description | -|--------|---------|-------------| -| `--url` | `http://localhost:8080` | Tracker URL | -| `--peers` | `100` | Number of simulated peers | -| `--torrents` | `10` | Number of torrents | -| `--duration` | `10` | Test duration (seconds) | -| `--interval` | `100` | Announce interval (ms) | -| `--rampup` | `2000` | Ramp-up time (ms) | +| Option | Default | Description | +| ------------ | ----------------------- | ------------------------- | +| `--url` | `http://localhost:8080` | Tracker URL | +| `--peers` | `100` | Number of simulated peers | +| `--torrents` | `10` | Number of torrents | +| `--duration` | `10` | Test duration (seconds) | +| `--interval` | `100` | Announce interval (ms) | +| `--rampup` | `2000` | Ramp-up time (ms) | ## Understanding Results @@ -87,11 +87,11 @@ npm run test:load -- --peers 5000 --duration 60 --torrents 100 ### Key Metrics -| Metric | Target | Description | -|--------|--------|-------------| -| Error Rate | < 1% | Percentage of failed requests | -| P99 Latency | < 500ms | 99th percentile response time | -| Requests/sec | > 100 | Throughput capacity | +| Metric | Target | Description | +| ------------ | ------- | ----------------------------- | +| Error Rate | < 1% | Percentage of failed requests | +| P99 Latency | < 500ms | 99th percentile response time | +| Requests/sec | > 100 | Throughput capacity | ## Pass/Fail Criteria @@ -107,7 +107,7 @@ The test automatically passes or fails based on: The tracker isn't running or port 8080 isn't exposed. ```bash -docker logs opentracker-loadtest | grep "listening" +docker logs trackarr-loadtest | grep "listening" ``` ### High Error Rate @@ -115,12 +115,13 @@ docker logs opentracker-loadtest | grep "listening" Check if the database schema is initialized: ```bash -docker exec opentracker-loadtest npm run db:push +docker exec trackarr-loadtest npm run db:push ``` ### High Latency Possible causes: + - Redis memory pressure - PostgreSQL connection pool exhaustion - Insufficient container resources diff --git a/doc/guide/local-production.md b/doc/guide/local-production.md index b12e3cd..8180438 100644 --- a/doc/guide/local-production.md +++ b/doc/guide/local-production.md @@ -8,7 +8,7 @@ This guide explains how to set up a **production-like environment** on your loca ## Prerequisites - **Docker** (or OrbStack) must be installed and running. -- **OrbStack** is recommended for better performance and local domain names (e.g., `http://opentracker.orb.local`). +- **OrbStack** is recommended for better performance and local domain names (e.g., `http://trackarr.orb.local`). ## Automated Setup @@ -22,7 +22,7 @@ We provide a script to automate the entire process: generating secrets, creating 1. **Checks Prerequisites**: Verifies Docker/OrbStack availability. 2. **Generates Secrets**: Creates a `.env.local-prod` file with secure, random credentials for DB, Redis, and APIs. -3. **Clean Start**: Aggressively stops and removes any conflicting containers (`opentracker-*`). +3. **Clean Start**: Aggressively stops and removes any conflicting containers (`trackarr-*`). 4. **Builds & Starts**: Builds the production Docker image and starts the stack using `docker-compose.local.yml`. 5. **Health Checks**: Waits for PostgreSQL and Redis to be fully ready. 6. **Migrations**: Automatically pushes the latest database schema. @@ -43,10 +43,10 @@ Once the script completes, you can access the services at the following URLs: If you are using OrbStack, you can also use these convenient local domains: -| Service | URL | -| :----------- | :------------------------------------------ | -| **Frontend** | `http://opentracker.orb.local:3000` | -| **Grafana** | `http://opentracker-grafana.orb.local:3000` | +| Service | URL | +| :----------- | :--------------------------------------- | +| **Frontend** | `http://trackarr.orb.local:3000` | +| **Grafana** | `http://trackarr-grafana.orb.local:3000` | ## Manage the Environment @@ -75,7 +75,7 @@ docker compose -f docker-compose.local.yml --env-file .env.local-prod restart **Open Database (PSQL):** ```bash -docker compose -f docker-compose.local.yml --env-file .env.local-prod exec postgres psql -U tracker -d opentracker +docker compose -f docker-compose.local.yml --env-file .env.local-prod exec postgres psql -U tracker -d trackarr ``` ## Troubleshooting diff --git a/doc/guide/troubleshooting.md b/doc/guide/troubleshooting.md index e1ea93a..28c8d85 100644 --- a/doc/guide/troubleshooting.md +++ b/doc/guide/troubleshooting.md @@ -90,12 +90,12 @@ Common issues and how to resolve them. 3. Check database connectivity: ```bash - docker exec opentracker-db pg_isready + docker exec trackarr-db pg_isready ``` 4. Check Redis connectivity: ```bash - docker exec opentracker-redis redis-cli ping + docker exec trackarr-redis redis-cli ping ``` --- @@ -109,7 +109,7 @@ Common issues and how to resolve them. 1. Verify database container is running: ```bash - docker ps | grep opentracker-db + docker ps | grep trackarr-db ``` 2. Check database logs: @@ -144,7 +144,7 @@ The generated config shows something like: ```ini [databases] -e21V5@postgres:5432/opentracker = host=postgres port=5432 auth_user=tracker +e21V5@postgres:5432/trackarr = host=postgres port=5432 auth_user=tracker ``` **Cause:** Your `DB_PASSWORD` contains special characters (`@`, `:`, `/`, `#`) that break the `DATABASE_URL` parsing in the PgBouncer image. @@ -170,7 +170,7 @@ e21V5@postgres:5432/opentracker = host=postgres port=5432 auth_user=tracker 3. **Update the PostgreSQL user password**: ```bash - docker exec -it opentracker-db psql -U postgres -c "ALTER USER tracker WITH PASSWORD 'NEW_PASSWORD_HERE';" + docker exec -it trackarr-db psql -U postgres -c "ALTER USER tracker WITH PASSWORD 'NEW_PASSWORD_HERE';" ``` 4. **Restart affected services**: @@ -191,19 +191,19 @@ e21V5@postgres:5432/opentracker = host=postgres port=5432 auth_user=tracker 1. Verify Redis container is running: ```bash - docker ps | grep opentracker-redis + docker ps | grep trackarr-redis ``` 2. Test Redis connection: ```bash - docker exec opentracker-redis redis-cli ping + docker exec trackarr-redis redis-cli ping # Should return: PONG ``` 3. Check Redis password matches `.env`: ```bash - docker exec opentracker-redis redis-cli -a YOUR_PASSWORD ping + docker exec trackarr-redis redis-cli -a YOUR_PASSWORD ping ``` --- @@ -340,7 +340,7 @@ e21V5@postgres:5432/opentracker = host=postgres port=5432 auth_user=tracker Reset the admin password: ```bash -docker exec -it opentracker-grafana grafana-cli admin reset-admin-password +docker exec -it trackarr-grafana grafana-cli admin reset-admin-password ``` --- @@ -362,7 +362,7 @@ docker exec -it opentracker-grafana grafana-cli admin reset-admin-password backup.sql +docker exec trackarr-db pg_dump -U tracker trackarr > backup.sql ``` ### Database Restore ```bash -cat backup.sql | docker exec -i opentracker-db psql -U opentracker opentracker +cat backup.sql | docker exec -i trackarr-db psql -U tracker trackarr ``` ## Still Need Help? If you're still experiencing issues: -1. Check the [GitHub Issues](https://github.com/florianjs/opentracker/issues) for similar problems +1. Check the [GitHub Issues](https://github.com/florianjs/trackarr/issues) for similar problems 2. Open a new issue with: - Description of the problem - Relevant log output diff --git a/doc/package.json b/doc/package.json index 1b081c1..c3a1fdc 100644 --- a/doc/package.json +++ b/doc/package.json @@ -1,5 +1,5 @@ { - "name": "opentracker-docs", + "name": "trackarr-docs", "version": "1.0.0", "type": "module", "scripts": { diff --git a/doc/public/.nojekyll b/doc/public/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/doc/reference/env.md b/doc/reference/env.md index c0fc45b..07ed0a1 100644 --- a/doc/reference/env.md +++ b/doc/reference/env.md @@ -6,66 +6,66 @@ Complete reference for all environment variables used by Trackarr. These must be set for the application to run: -| Variable | Description | Example | -|----------|-------------|---------| -| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/db` | -| `REDIS_URL` | Redis connection string | `redis://:password@host:6379` | -| `NUXT_SESSION_PASSWORD` | Session encryption key (32+ chars) | Random string | -| `TRACKER_SECRET` | Secret for passkey generation | Random string | -| `IP_HASH_SECRET` | Secret for IP hashing | Random string | +| Variable | Description | Example | +| ----------------------- | ---------------------------------- | ------------------------------------- | +| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/db` | +| `REDIS_URL` | Redis connection string | `redis://:password@host:6379` | +| `NUXT_SESSION_PASSWORD` | Session encryption key (32+ chars) | Random string | +| `TRACKER_SECRET` | Secret for passkey generation | Random string | +| `IP_HASH_SECRET` | Secret for IP hashing | Random string | ## Application Settings -| Variable | Description | Default | -|----------|-------------|---------| -| `NUXT_PUBLIC_SITE_NAME` | Display name of your tracker | `Trackarr` | -| `NUXT_PUBLIC_SITE_URL` | Public URL | — | -| `NUXT_PUBLIC_ANNOUNCE_URL` | Announce URL for torrents | — | -| `NODE_ENV` | Environment mode | `development` | -| `PORT` | Application port | `3000` | +| Variable | Description | Default | +| -------------------------- | ---------------------------- | ------------- | +| `NUXT_PUBLIC_SITE_NAME` | Display name of your tracker | `Trackarr` | +| `NUXT_PUBLIC_SITE_URL` | Public URL | — | +| `NUXT_PUBLIC_ANNOUNCE_URL` | Announce URL for torrents | — | +| `NODE_ENV` | Environment mode | `development` | +| `PORT` | Application port | `3000` | ## Database -| Variable | Description | Default | -|----------|-------------|---------| -| `POSTGRES_USER` | PostgreSQL username | `opentracker` | -| `POSTGRES_PASSWORD` | PostgreSQL password | — | -| `POSTGRES_DB` | Database name | `opentracker` | -| `POSTGRES_HOST` | Database host | `localhost` | -| `POSTGRES_PORT` | Database port | `5432` | +| Variable | Description | Default | +| ------------------- | ------------------- | ----------- | +| `POSTGRES_USER` | PostgreSQL username | `tracker` | +| `POSTGRES_PASSWORD` | PostgreSQL password | — | +| `POSTGRES_DB` | Database name | `trackarr` | +| `POSTGRES_HOST` | Database host | `localhost` | +| `POSTGRES_PORT` | Database port | `5432` | ## Redis -| Variable | Description | Default | -|----------|-------------|---------| -| `REDIS_HOST` | Redis host | `localhost` | -| `REDIS_PORT` | Redis port | `6379` | -| `REDIS_PASSWORD` | Redis password | — | +| Variable | Description | Default | +| ---------------- | -------------- | ----------- | +| `REDIS_HOST` | Redis host | `localhost` | +| `REDIS_PORT` | Redis port | `6379` | +| `REDIS_PASSWORD` | Redis password | — | ## Security -| Variable | Description | Default | -|----------|-------------|---------| -| `POW_DIFFICULTY` | Proof of Work difficulty (1-10) | `5` | -| `RATE_LIMIT_WINDOW` | Rate limit window (seconds) | `60` | -| `RATE_LIMIT_MAX` | Max requests per window | `100` | -| `SESSION_MAX_AGE` | Session lifetime (seconds) | `604800` (7 days) | +| Variable | Description | Default | +| ------------------- | ------------------------------- | ----------------- | +| `POW_DIFFICULTY` | Proof of Work difficulty (1-10) | `5` | +| `RATE_LIMIT_WINDOW` | Rate limit window (seconds) | `60` | +| `RATE_LIMIT_MAX` | Max requests per window | `100` | +| `SESSION_MAX_AGE` | Session lifetime (seconds) | `604800` (7 days) | ## Tracker -| Variable | Description | Default | -|----------|-------------|---------| -| `TRACKER_INTERVAL` | Announce interval (seconds) | `1800` | -| `TRACKER_MIN_INTERVAL` | Minimum announce interval | `900` | -| `TRACKER_MAX_PEERS` | Max peers returned per announce | `50` | +| Variable | Description | Default | +| ---------------------- | ------------------------------- | ------- | +| `TRACKER_INTERVAL` | Announce interval (seconds) | `1800` | +| `TRACKER_MIN_INTERVAL` | Minimum announce interval | `900` | +| `TRACKER_MAX_PEERS` | Max peers returned per announce | `50` | ## Monitoring -| Variable | Description | Default | -|----------|-------------|---------| -| `GRAFANA_ADMIN_USER` | Grafana admin username | `admin` | +| Variable | Description | Default | +| ------------------------ | ---------------------- | ------- | +| `GRAFANA_ADMIN_USER` | Grafana admin username | `admin` | | `GRAFANA_ADMIN_PASSWORD` | Grafana admin password | `admin` | -| `GF_SERVER_ROOT_URL` | Grafana public URL | — | +| `GF_SERVER_ROOT_URL` | Grafana public URL | — | ## Example `.env` File @@ -77,10 +77,10 @@ NUXT_PUBLIC_ANNOUNCE_URL="https://announce.example.com/announce" NODE_ENV=production # Database -DATABASE_URL="postgresql://opentracker:secretpassword@db:5432/opentracker" -POSTGRES_USER=opentracker +DATABASE_URL="postgresql://tracker:secretpassword@db:5432/trackarr" +POSTGRES_USER=tracker POSTGRES_PASSWORD=secretpassword -POSTGRES_DB=opentracker +POSTGRES_DB=trackarr # Redis REDIS_URL="redis://:redispassword@redis:6379" diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 40e3e5e..5b06e6c 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -7,7 +7,7 @@ services: # ============================================================================= - # APPLICATION - Nuxt OpenTracker + # APPLICATION - Nuxt Trackarr # ============================================================================= app: image: trackarr:latest diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b86a134..a9f6240 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -40,7 +40,7 @@ services: memory: 64M # ============================================================================= - # APPLICATION - Nuxt OpenTracker + # APPLICATION - Nuxt Trackarr # ============================================================================= app: image: trackarr:latest @@ -412,7 +412,7 @@ services: # volumes: # - ./docker/redis/sentinel.conf:/etc/redis/sentinel.conf:ro # networks: - # - opentracker-internal + # - trackarr-internal # networks: trackarr-public: diff --git a/docker/caddy/Caddyfile b/docker/caddy/Caddyfile index a6d0a81..94402cb 100644 --- a/docker/caddy/Caddyfile +++ b/docker/caddy/Caddyfile @@ -1,4 +1,4 @@ -# OpenTracker Production Caddyfile +# Trackarr Production Caddyfile # Automatic HTTPS with Let's Encrypt { @@ -28,7 +28,7 @@ # For advanced rate limiting, use the app's built-in Redis-backed limiter # Reverse proxy to Nuxt app - reverse_proxy opentracker-app:3000 { + reverse_proxy trackarr-app:3000 { header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} @@ -65,7 +65,7 @@ } # Tracker endpoints - reverse_proxy opentracker-app:8080 { + reverse_proxy trackarr-app:8080 { header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} } diff --git a/docker/grafana/provisioning/dashboards/dashboards.yml b/docker/grafana/provisioning/dashboards/dashboards.yml index 4e620d9..d08509f 100644 --- a/docker/grafana/provisioning/dashboards/dashboards.yml +++ b/docker/grafana/provisioning/dashboards/dashboards.yml @@ -3,10 +3,10 @@ apiVersion: 1 providers: - - name: 'OpenTracker Dashboards' + - name: 'Trackarr Dashboards' orgId: 1 - folder: 'OpenTracker' - folderUid: 'opentracker' + folder: 'Trackarr' + folderUid: 'trackarr' type: file disableDeletion: false updateIntervalSeconds: 30 diff --git a/docker/grafana/provisioning/dashboards/json/opentracker-overview.json b/docker/grafana/provisioning/dashboards/json/trackarr-overview.json similarity index 98% rename from docker/grafana/provisioning/dashboards/json/opentracker-overview.json rename to docker/grafana/provisioning/dashboards/json/trackarr-overview.json index c3372dd..3258191 100644 --- a/docker/grafana/provisioning/dashboards/json/opentracker-overview.json +++ b/docker/grafana/provisioning/dashboards/json/trackarr-overview.json @@ -404,12 +404,12 @@ }, "targets": [ { - "expr": "rate(pg_stat_database_xact_commit{datname=\"opentracker\"}[5m])", + "expr": "rate(pg_stat_database_xact_commit{datname=\"trackarr\"}[5m])", "legendFormat": "Commits/sec", "refId": "A" }, { - "expr": "rate(pg_stat_database_xact_rollback{datname=\"opentracker\"}[5m])", + "expr": "rate(pg_stat_database_xact_rollback{datname=\"trackarr\"}[5m])", "legendFormat": "Rollbacks/sec", "refId": "B" } @@ -540,7 +540,7 @@ ], "refresh": "30s", "schemaVersion": 38, - "tags": ["opentracker", "overview"], + "tags": ["trackarr", "overview"], "templating": { "list": [ { @@ -566,7 +566,7 @@ "time": { "from": "now-1h", "to": "now" }, "timepicker": {}, "timezone": "browser", - "title": "OpenTracker Overview", - "uid": "opentracker-overview", + "title": "Trackarr Overview", + "uid": "trackarr-overview", "version": 1 } diff --git a/docker/grafana/provisioning/datasources/datasources.yml b/docker/grafana/provisioning/datasources/datasources.yml index 1afb22d..481fe76 100644 --- a/docker/grafana/provisioning/datasources/datasources.yml +++ b/docker/grafana/provisioning/datasources/datasources.yml @@ -16,7 +16,7 @@ datasources: - name: PostgreSQL type: postgres url: postgres:5432 - database: ${DB_NAME:-opentracker} + database: ${DB_NAME:-trackarr} user: ${DB_USER:-tracker} secureJsonData: password: ${DB_PASSWORD:-tracker} diff --git a/docker/pgbouncer/pgbouncer.ini b/docker/pgbouncer/pgbouncer.ini index 9d0bc61..5f1160b 100644 --- a/docker/pgbouncer/pgbouncer.ini +++ b/docker/pgbouncer/pgbouncer.ini @@ -1,10 +1,10 @@ -; PgBouncer Configuration for OpenTracker +; PgBouncer Configuration for Trackarr ; Connection pooling for high-concurrency PostgreSQL access [databases] ; Database connection string ; Format: dbname = host=hostname port=port dbname=database -opentracker = host=postgres port=5432 dbname=opentracker +trackarr = host=postgres port=5432 dbname=trackarr [pgbouncer] ; Listen on all interfaces diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml index 1623cfb..0ea0bb2 100644 --- a/docker/prometheus/prometheus.yml +++ b/docker/prometheus/prometheus.yml @@ -1,10 +1,10 @@ -# OpenTracker Prometheus Configuration +# Trackarr Prometheus Configuration global: scrape_interval: 15s evaluation_interval: 15s external_labels: - monitor: 'opentracker' + monitor: 'trackarr' # Alerting configuration (optional - configure Alertmanager separately) # alerting: @@ -31,7 +31,7 @@ scrape_configs: relabel_configs: - source_labels: [__address__] target_label: instance - replacement: 'opentracker-host' + replacement: 'trackarr-host' # PostgreSQL metrics via postgres_exporter - job_name: 'postgresql' @@ -40,7 +40,7 @@ scrape_configs: relabel_configs: - source_labels: [__address__] target_label: instance - replacement: 'opentracker-db' + replacement: 'trackarr-db' # Redis metrics via redis_exporter - job_name: 'redis' @@ -49,7 +49,7 @@ scrape_configs: relabel_configs: - source_labels: [__address__] target_label: instance - replacement: 'opentracker-redis' + replacement: 'trackarr-redis' # Caddy metrics - job_name: 'caddy' @@ -57,8 +57,8 @@ scrape_configs: - targets: ['caddy:2019'] metrics_path: /metrics - # OpenTracker application metrics (if exposed) - # - job_name: 'opentracker' + # Trackarr application metrics (if exposed) + # - job_name: 'trackarr' # static_configs: - # - targets: ['opentracker-app:3000'] + # - targets: ['trackarr-app:3000'] # metrics_path: /api/metrics diff --git a/drizzle.config.ts b/drizzle.config.ts index 51dd621..9e8dcf0 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,10 +1,12 @@ -import { defineConfig } from 'drizzle-kit' +import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './server/db/schema.ts', out: './server/db/migrations', dialect: 'postgresql', dbCredentials: { - url: process.env.DATABASE_URL || 'postgres://tracker:tracker@localhost:5432/opentracker' - } -}) + url: + process.env.DATABASE_URL || + 'postgres://tracker:tracker@localhost:5432/trackarr', + }, +}); diff --git a/package.json b/package.json index ed8522f..27b25ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trackarr", - "version": "0.4.1", + "version": "0.4.2", "type": "module", "private": true, "scripts": { diff --git a/scripts/db-push.sh b/scripts/db-push.sh index d8a5039..b17f4b1 100755 --- a/scripts/db-push.sh +++ b/scripts/db-push.sh @@ -12,12 +12,12 @@ if [ -z "$DATABASE_URL" ]; then fi # Default URL if not set -DATABASE_URL="${DATABASE_URL:-postgres://tracker:tracker@postgres:5432/opentracker}" +DATABASE_URL="${DATABASE_URL:-postgres://tracker:tracker@postgres:5432/trackarr}" # Only replace docker hostnames with localhost if NOT running in Docker # Check if we're in Docker by looking for /.dockerenv or running hostname check -if [ ! -f /.dockerenv ] && [ "$(hostname)" != "opentracker-app" ]; then - LOCAL_URL=$(echo "$DATABASE_URL" | sed -E 's/@(postgres|pgbouncer|db|opentracker-db):/@localhost:/g') +if [ ! -f /.dockerenv ] && [ "$(hostname)" != "trackarr-app" ]; then + LOCAL_URL=$(echo "$DATABASE_URL" | sed -E 's/@(postgres|pgbouncer|db|trackarr-db):/@localhost:/g') echo "🔄 Pushing schema to database (local mode)..." echo " Original: $DATABASE_URL" echo " Local: $LOCAL_URL" diff --git a/scripts/setup-local-prod.sh b/scripts/setup-local-prod.sh index 4980ef1..35503cf 100755 --- a/scripts/setup-local-prod.sh +++ b/scripts/setup-local-prod.sh @@ -166,7 +166,7 @@ cd "$PROJECT_DIR" docker compose -f docker-compose.local.yml --env-file "$ENV_FILE" down 2>/dev/null || true docker compose -f docker-compose.prod.yml down 2>/dev/null || true -# Also stop any containers with opentracker in the name +# Also stop any containers with trackarr in the name docker stop $(docker ps -q --filter "name=trackarr") 2>/dev/null || true docker rm $(docker ps -aq --filter "name=trackarr") 2>/dev/null || true diff --git a/scripts/update-local.sh b/scripts/update-local.sh index b6ea305..8c35b40 100755 --- a/scripts/update-local.sh +++ b/scripts/update-local.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================================= -# OpenTracker - Local Update Script +# Trackarr - Local Update Script # ============================================================================= # Updates the local environment from git and restarts services # Usage: ./scripts/update-local.sh @@ -34,7 +34,7 @@ fi echo -e "${GREEN}${BOLD}" echo " ┌──────────────────────────────────────────────────────────────┐" -echo " │ OpenTracker - Local Update │" +echo " │ Trackarr - Local Update │" echo " └──────────────────────────────────────────────────────────────┘" echo -e "${NC}" diff --git a/server/api/admin/system/update.get.ts b/server/api/admin/system/update.get.ts index 6d79501..202fcba 100644 --- a/server/api/admin/system/update.get.ts +++ b/server/api/admin/system/update.get.ts @@ -7,12 +7,12 @@ export default defineEventHandler(async (event) => { // We return helpful commands for the admin return { success: true, - message: 'Run these commands on your server to update OpenTracker:', + message: 'Run these commands on your server to update Trackarr:', commands: [ { step: 1, description: 'Navigate to the project directory', - command: 'cd /path/to/opentracker', + command: 'cd /path/to/trackarr', }, { step: 2, diff --git a/server/api/admin/system/version.get.ts b/server/api/admin/system/version.get.ts index 0dcbb98..9aea1de 100644 --- a/server/api/admin/system/version.get.ts +++ b/server/api/admin/system/version.get.ts @@ -19,11 +19,12 @@ export default defineEventHandler(async (event) => { try { // Check GitHub releases const response = await fetch( - 'https://api.github.com/repos/flormusic/opentracker/releases/latest', + 'https://api.github.com/repos/florianjs/trackarr/releases/latest', { + method: 'GET', headers: { - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': 'OpenTracker-Admin', + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'Trackarr-Admin', }, } ); diff --git a/server/api/rss/category/[slug].get.ts b/server/api/rss/category/[slug].get.ts index 7cb5068..e348579 100644 --- a/server/api/rss/category/[slug].get.ts +++ b/server/api/rss/category/[slug].get.ts @@ -56,9 +56,9 @@ export default defineEventHandler(async (event) => { // Build RSS XML const baseUrl = getRequestURL(event).origin; const rss = buildRSSFeed({ - title: `OpenTracker - ${category.name}`, - link: `${baseUrl}/torrents?categoryId=${category.id}`, - description: `Latest ${category.name} torrents on OpenTracker`, + title: `Trackarr - ${category.name}`, + link: baseUrl, + description: `Latest ${category.name} torrents on Trackarr`, feedUrl: `${baseUrl}/api/rss/category/${params.slug}`, items: enriched.map((t) => ({ title: t.name, diff --git a/server/api/rss/latest.get.ts b/server/api/rss/latest.get.ts index 6ccfbf1..307fcbc 100644 --- a/server/api/rss/latest.get.ts +++ b/server/api/rss/latest.get.ts @@ -37,9 +37,9 @@ export default defineEventHandler(async (event) => { // Build RSS XML const baseUrl = getRequestURL(event).origin; const rss = buildRSSFeed({ - title: 'OpenTracker - Latest Torrents', - link: `${baseUrl}/torrents`, - description: 'Latest torrent uploads on OpenTracker', + title: 'Trackarr - Latest Torrents', + link: baseUrl, + description: 'Latest torrent uploads on Trackarr', items: enriched.map((t) => ({ title: t.name, link: `${baseUrl}/torrents/${t.infoHash}`, diff --git a/server/db/index.ts b/server/db/index.ts index 9c96798..9b4bd89 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -9,7 +9,7 @@ import * as schema from './schema'; const connectionString = process.env.DATABASE_URL || - 'postgres://tracker:tracker@localhost:5432/opentracker'; + 'postgres://tracker:tracker@localhost:5432/trackarr'; const isProduction = process.env.NODE_ENV === 'production'; // Build secure connection options diff --git a/server/utils/alerts.ts b/server/utils/alerts.ts index 3bac292..5d96cce 100644 --- a/server/utils/alerts.ts +++ b/server/utils/alerts.ts @@ -213,7 +213,7 @@ function formatDiscordMessage(alert: SecurityAlert): any { : []), ], footer: { - text: 'OpenTracker Security System', + text: 'Trackarr Security System', }, timestamp: alert.timestamp, }, @@ -287,7 +287,7 @@ function formatSlackMessage(alert: SecurityAlert): any { elements: [ { type: 'mrkdwn', - text: `OpenTracker Security System | ${alert.timestamp}`, + text: `Trackarr Security System | ${alert.timestamp}`, }, ], }, diff --git a/server/utils/settings.ts b/server/utils/settings.ts index f6804f2..5248391 100644 --- a/server/utils/settings.ts +++ b/server/utils/settings.ts @@ -159,7 +159,7 @@ export async function getDefaultInvites(): Promise { */ export async function getSiteName(): Promise { const value = await getSetting(SETTINGS_KEYS.SITE_NAME); - return value || 'OpenTracker'; + return value || 'Trackarr'; } /** @@ -295,7 +295,7 @@ export async function getAnnouncementType(): Promise< */ export async function getHeroTitle(): Promise { const value = await getSetting(SETTINGS_KEYS.HERO_TITLE); - return value || 'OpenTracker'; + return value || 'Trackarr'; } /** diff --git a/tests/load/load-test.ts b/tests/load/load-test.ts index 936ee6f..d716af0 100644 --- a/tests/load/load-test.ts +++ b/tests/load/load-test.ts @@ -1,6 +1,6 @@ /** - * Load Test Runner for OpenTracker - * + * Load Test Runner for Trackarr + * * Stress tests the BitTorrent tracker with simulated peer announces. * Measures throughput, latency, and error rates. */ @@ -70,22 +70,26 @@ async function sendAnnounce( ): Promise<{ success: boolean; latencyMs: number; error?: string }> { const query = buildAnnounceQuery(peer, event); const url = `${trackerUrl}/announce?${query}`; - + const start = performance.now(); - + try { const response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(10000), // 10s timeout }); - + const latencyMs = performance.now() - start; - + if (response.ok) { return { success: true, latencyMs }; } else { const text = await response.text(); - return { success: false, latencyMs, error: `HTTP ${response.status}: ${text}` }; + return { + success: false, + latencyMs, + error: `HTTP ${response.status}: ${text}`, + }; } } catch (err) { const latencyMs = performance.now() - start; @@ -98,29 +102,41 @@ async function sendAnnounce( // ============================================================================ async function runLoadTest(config: LoadTestConfig): Promise { - console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - console.log(' OpenTracker Load Test'); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log( + '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + ); + console.log(' Trackarr Load Test'); + console.log( + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + ); console.log(` Tracker URL: ${config.trackerUrl}`); console.log(` Peers: ${config.numPeers}`); console.log(` Torrents: ${config.numTorrents}`); console.log(` Duration: ${config.durationMs / 1000}s`); - console.log(` Announce Rate: ${1000 / config.announceIntervalMs}/s per peer`); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + console.log( + ` Announce Rate: ${1000 / config.announceIntervalMs}/s per peer` + ); + console.log( + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + ); // Generate test data console.log('Generating test data...'); const passkey = generatePasskey(); - const torrents = Array.from({ length: config.numTorrents }, () => generateInfoHash()); - + const torrents = Array.from({ length: config.numTorrents }, () => + generateInfoHash() + ); + const peers: SimulatedPeer[] = []; for (let i = 0; i < config.numPeers; i++) { const torrent = torrents[i % torrents.length]; const isSeeder = Math.random() > 0.7; // 30% seeders peers.push(createPeer(torrent, passkey, isSeeder)); } - - console.log(` Created ${peers.length} peers across ${torrents.length} torrents\n`); + + console.log( + ` Created ${peers.length} peers across ${torrents.length} torrents\n` + ); // Stats let totalRequests = 0; @@ -137,8 +153,10 @@ async function runLoadTest(config: LoadTestConfig): Promise { console.log('Phase 1: Sending initial "started" announces...'); const startedPromises = peers.map(async (peer, index) => { // Ramp up: stagger the initial announces - await new Promise(resolve => setTimeout(resolve, (index / peers.length) * config.rampUpMs)); - + await new Promise((resolve) => + setTimeout(resolve, (index / peers.length) * config.rampUpMs) + ); + const result = await sendAnnounce(config.trackerUrl, peer, 'started'); totalRequests++; if (result.success) { @@ -151,24 +169,26 @@ async function runLoadTest(config: LoadTestConfig): Promise { } recordLatency(result.latencyMs); }); - + await Promise.all(startedPromises); - console.log(` Completed: ${successfulRequests}/${totalRequests} successful\n`); + console.log( + ` Completed: ${successfulRequests}/${totalRequests} successful\n` + ); // Main load test loop console.log('Phase 2: Sustained load test...'); - + const intervalId = setInterval(() => { const now = Date.now(); const elapsed = now - startTime; const progress = Math.min(100, (elapsed / config.durationMs) * 100); - + if (now - lastProgressUpdate > 2000) { const currentRps = totalRequests / (elapsed / 1000); console.log( ` [${progress.toFixed(0)}%] Requests: ${totalRequests} | ` + - `Success: ${successfulRequests} | Errors: ${failedRequests} | ` + - `RPS: ${currentRps.toFixed(0)}` + `Success: ${successfulRequests} | Errors: ${failedRequests} | ` + + `RPS: ${currentRps.toFixed(0)}` ); lastProgressUpdate = now; } @@ -179,22 +199,26 @@ async function runLoadTest(config: LoadTestConfig): Promise { while (Date.now() < endTime) { // Select random peers for this batch const batchSize = Math.min(50, peers.length); - const batch = Array.from({ length: batchSize }, () => - peers[Math.floor(Math.random() * peers.length)] + const batch = Array.from( + { length: batchSize }, + () => peers[Math.floor(Math.random() * peers.length)] ); - + const batchPromises = batch.map(async (peer) => { // Simulate progress if (!peer.isSeeder) { peer.downloaded += Math.floor(Math.random() * 1000000); - peer.left = Math.max(0, peer.left - Math.floor(Math.random() * 1000000)); + peer.left = Math.max( + 0, + peer.left - Math.floor(Math.random() * 1000000) + ); if (peer.left === 0) { peer.isSeeder = true; } } else { peer.uploaded += Math.floor(Math.random() * 1000000); } - + const result = await sendAnnounce(config.trackerUrl, peer, ''); totalRequests++; if (result.success) { @@ -204,33 +228,37 @@ async function runLoadTest(config: LoadTestConfig): Promise { } recordLatency(result.latencyMs); }); - + await Promise.all(batchPromises); - await new Promise(resolve => setTimeout(resolve, config.announceIntervalMs)); + await new Promise((resolve) => + setTimeout(resolve, config.announceIntervalMs) + ); } }; - + await announceLoop(); clearInterval(intervalId); // Send "stopped" announces console.log('\nPhase 3: Sending "stopped" announces...'); - const stoppedPromises = peers.slice(0, Math.min(100, peers.length)).map(async (peer) => { - const result = await sendAnnounce(config.trackerUrl, peer, 'stopped'); - totalRequests++; - if (result.success) { - successfulRequests++; - } else { - failedRequests++; - } - recordLatency(result.latencyMs); - }); - + const stoppedPromises = peers + .slice(0, Math.min(100, peers.length)) + .map(async (peer) => { + const result = await sendAnnounce(config.trackerUrl, peer, 'stopped'); + totalRequests++; + if (result.success) { + successfulRequests++; + } else { + failedRequests++; + } + recordLatency(result.latencyMs); + }); + await Promise.all(stoppedPromises); // Calculate results const actualDuration = Date.now() - startTime; - + const result: LoadTestResult = { totalRequests, successfulRequests, @@ -254,17 +282,21 @@ async function runLoadTest(config: LoadTestConfig): Promise { // ============================================================================ function displayResults(result: LoadTestResult): void { - console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log( + '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + ); console.log(' LOAD TEST RESULTS'); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - + console.log( + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + ); + console.log('\n Throughput:'); console.log(` Total Requests: ${result.totalRequests}`); console.log(` Successful: ${result.successfulRequests}`); console.log(` Failed: ${result.failedRequests}`); console.log(` Error Rate: ${result.errorRate.toFixed(2)}%`); console.log(` Requests/sec: ${result.requestsPerSecond.toFixed(2)}`); - + console.log('\n Latency (ms):'); console.log(` Min: ${result.latencyMin.toFixed(2)}`); console.log(` Avg: ${result.latencyAvg.toFixed(2)}`); @@ -272,12 +304,16 @@ function displayResults(result: LoadTestResult): void { console.log(` P95: ${result.latencyP95.toFixed(2)}`); console.log(` P99: ${result.latencyP99.toFixed(2)}`); console.log(` Max: ${result.latencyMax.toFixed(2)}`); - + console.log('\n Duration:'); - console.log(` Actual: ${(result.durationMs / 1000).toFixed(2)}s`); - - console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - + console.log( + ` Actual: ${(result.durationMs / 1000).toFixed(2)}s` + ); + + console.log( + '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + ); + // Assessment const passed = result.errorRate < 1 && result.latencyP99 < 500; if (passed) { @@ -285,13 +321,19 @@ function displayResults(result: LoadTestResult): void { } else { console.log(' ✗ FAILED - Performance issues detected'); if (result.errorRate >= 1) { - console.log(` - Error rate too high (${result.errorRate.toFixed(2)}% > 1%)`); + console.log( + ` - Error rate too high (${result.errorRate.toFixed(2)}% > 1%)` + ); } if (result.latencyP99 >= 500) { - console.log(` - P99 latency too high (${result.latencyP99.toFixed(2)}ms > 500ms)`); + console.log( + ` - P99 latency too high (${result.latencyP99.toFixed(2)}ms > 500ms)` + ); } } - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + console.log( + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + ); } // ============================================================================ @@ -301,13 +343,13 @@ function displayResults(result: LoadTestResult): void { export async function main(args: string[]): Promise { // Parse arguments const getArg = (name: string, defaultValue: string): string => { - const index = args.findIndex(a => a === `--${name}`); + const index = args.findIndex((a) => a === `--${name}`); if (index !== -1 && args[index + 1]) { return args[index + 1]; } return defaultValue; }; - + const config: LoadTestConfig = { trackerUrl: getArg('url', 'http://localhost:8080'), numPeers: parseInt(getArg('peers', '100'), 10), @@ -316,11 +358,11 @@ export async function main(args: string[]): Promise { announceIntervalMs: parseInt(getArg('interval', '100'), 10), rampUpMs: parseInt(getArg('rampup', '2000'), 10), }; - + // Help if (args.includes('--help') || args.includes('-h')) { console.log(` -OpenTracker Load Test +Trackarr Load Test Usage: tsx tests/load/load-test.ts [options] @@ -335,11 +377,11 @@ Options: `); return; } - + try { const result = await runLoadTest(config); displayResults(result); - + // Exit with error code if test failed if (result.errorRate >= 1 || result.latencyP99 >= 500) { process.exit(1);