Torrust-Actix Tracker is a lightweight but incredibly powerful and feature-rich BitTorrent Tracker made using Rust.
Currently, it's being actively used at https://www.gbitt.info/.
This project originated from Torrust-Tracker code originally developed by Mick van Dijke, further developed by Power2All as alternative for OpenTracker and other tracker code available on GitHub.
- Block array for TCP tracking (HTTP/HTTPS), UDP tracking and API (HTTP/HTTPS)
- Full IPv4 and IPv6 support
- Persistence saving supported using SQLite3, MySQL or PostgresSQL database
- Customize table and database structure in the configuration
- Whitelist system for private tracking
- Blacklist system for blocking unwelcome hashes
- Torrent key support for locking access to announcement through keys as info_hash with a timeout
- User account support, configurable for also database support
- Swagger UI built-in in the API (toggleable), useful both for testing API and documentation for API
- Sentry SaaS and self-hosted support
- Full Stand-Alone/Master/Slave cluster mode
- Optional Redis/Memcache Caching for peers data (can be used to show on a website for instance, to less burden SQL)
- Cloudflare's "Simple Proxy Protocol" support added (https://developers.cloudflare.com/spectrum/how-to/enable-proxy-protocol/#enable-simple-proxy-protocol-for-udp)
- RtcTorrent implementation (as alternative/replacement for WebTorrent)
- Configurable LZ4/Zstd compression for RTC SDP data (lz4 default, enabled by default)
- Adding UDP receive method with recvmmsg or io_uring
- BEP 3: The BitTorrent Protocol
- BEP 7: IPv6 Support
- BEP 15: UDP Tracker Protocol for BitTorrent
- BEP 23: Tracker Returns Compact Peer Lists
- BEP 41: UDP Tracker Protocol Extensions
- BEP 48: Tracker Protocol Extension: Scrape
You can get the latest binaries from releases or follow the installation from scratch instructions below.
- Clone the repository:
git clone https://github.com/Power2All/torrust-actix.git
cd torrust-actix- Build the source code using Rust 1.94.0 or newer (make sure you have installed rustup with stable branch)
cargo build --releaseA C compiler (for ring, zstd and the bundled SQLite) and curl (the Swagger UI bundle is downloaded at build time). TLS is rustls throughout, so no OpenSSL is needed:
# Debian / Ubuntu
sudo apt-get install build-essential curl
# Fedora / RHEL
sudo dnf install gcc make curl
# Arch
sudo pacman -S base-devel curl# Build the project
cargo build --releaseRun the code using --help argument for using in your environment:
./target/release/torrust-actix --helpBefore you can run the server, you need to either have persistence turned off, and when enabled, make sure your database is created and working. See the help argument above how to fix your setup as you wish.
Databases created with --create-database before v4.2.24 store the key expiry (keys.timeout) and the user's last update (users.updated) as 32-bit Unix timestamps, which cannot hold dates after 19 January 2038. Widen them to 64-bit once, with the tracker stopped:
./target/release/torrust-actix --migrate-databaseIt reads the table and column names from config.toml, is safe to run again, skips tables that do not exist and exits when done. SQLite databases need nothing: SQLite integers are already 64-bit.
Swagger UI is introduced, and when enabled in the configuration, is accessible through the API via /swagger-ui/.
Sentry.io support is introduced, you can enable it in the configuration and the URL where to push the data to.
To run this application in Docker, we provided a Docker Compose file that you can use to run it locally or remotely.
You can find it in the "docker" folder called "docker-compose.yml". The image is built from the checkout it sits in, and the compose file requires an API key from the environment:
cd docker
export TRACKER__API_KEY="$(head -c 32 /dev/urandom | base64)"
docker compose build
docker compose up -d
The API is published on 127.0.0.1:8080 only; publish it wider only behind TLS or a firewall. To build the image without compose, run docker build -f docker/Dockerfile . from the repository root. The container runs as an unprivileged tracker user.
Use environment variables to override the configuration settings.
LOG_LEVEL <off | trace | debug | info | warn | error>
LOG_CONSOLE_INTERVAL <UINT64>
TRACKER__API_KEY <STRING>
TRACKER__WHITELIST_ENABLED <true | false>
TRACKER__BLACKLIST_ENABLED <true | false>
TRACKER__KEYS_ENABLED <true | false>
TRACKER__USERS_ENABLED <true | false>
TRACKER__SWAGGER <true | false>
TRACKER__KEYS_CLEANUP_INTERVAL <UINT64>
TRACKER__REQUEST_INTERVAL <UINT64>
TRACKER__REQUEST_INTERVAL_MINIMUM <UINT64>
TRACKER__PEERS_TIMEOUT <UINT64>
TRACKER__PEERS_CLEANUP_INTERVAL <UINT64>
TRACKER__PEERS_CLEANUP_THREADS <UINT64>
TRACKER__PROMETHEUS_ID <STRING>
TRACKER__TLS_CONNECTION_RATE <UINT64>
TRACKER__RTC_INTERVAL <UINT64>
TRACKER__RTC_PEERS_TIMEOUT <UINT64>
TRACKER__TOTAL_DOWNLOADS <UINT64>
TRACKER__RTC_COMPRESSION_ENABLED <true | false>
TRACKER__RTC_COMPRESSION_ALGORITHM <lz4 | zstd>
TRACKER__RTC_COMPRESSION_LEVEL <UINT64>
TRACKER__CLUSTER <standalone | master | slave>
TRACKER__CLUSTER_ENCODING <binary | json | msgpack>
TRACKER__CLUSTER_TOKEN <STRING>
TRACKER__CLUSTER_BIND_ADDRESS <STRING>
TRACKER__CLUSTER_MASTER_ADDRESS <STRING>
TRACKER__CLUSTER_KEEP_ALIVE <UINT64>
TRACKER__CLUSTER_REQUEST_TIMEOUT <UINT64>
TRACKER__CLUSTER_DISCONNECT_TIMEOUT <UINT64>
TRACKER__CLUSTER_RECONNECT_INTERVAL <UINT64>
TRACKER__CLUSTER_MAX_CONNECTIONS <UINT64>
TRACKER__CLUSTER_THREADS <UINT64>
TRACKER__CLUSTER_SSL <true | false>
TRACKER__CLUSTER_SSL_KEY <STRING>
TRACKER__CLUSTER_SSL_CERT <STRING>
CACHE__ENABLED <true | false>
CACHE__ENGINE <redis | memcache>
CACHE__ADDRESS <STRING>
CACHE__PREFIX <STRING>
CACHE__TTL <UINT64>
CACHE__SPLIT_PEERS <true | false>
SENTRY__ENABLED <true | false>
SENTRY__DEBUG <true | false>
SENTRY__ATTACH_STACKTRACE <true | false>
SENTRY__SEND_DEFAULT_PII <true | false>
SENTRY__DSN <STRING>
SENTRY__MAX_BREADCRUMBS <UINT64>
SENTRY__SAMPLE_RATE <F32>
SENTRY__TRACES_SAMPLE_RATE <F32>
DATABASE__PERSISTENT <true | false>
DATABASE__INSERT_VACANT <true | false>
DATABASE__REMOVE_ACTION <true | false>
DATABASE__UPDATE_COMPLETED <true | false>
DATABASE__UPDATE_PEERS <true | false>
DATABASE__PATH <STRING>
DATABASE__ENGINE <sqlite3 | mysql | pgsql>
DATABASE__PERSISTENT_INTERVAL <UINT64>
DATABASE__CHUNK_SIZE <UINT64>
DATABASE_STRUCTURE__TORRENTS__BIN_TYPE_INFOHASH <true | false>
DATABASE_STRUCTURE__TORRENTS__TABLE_NAME <STRING>
DATABASE_STRUCTURE__TORRENTS__COLUMN_INFOHASH <STRING>
DATABASE_STRUCTURE__TORRENTS__COLUMN_SEEDS <STRING>
DATABASE_STRUCTURE__TORRENTS__COLUMN_PEERS <STRING>
DATABASE_STRUCTURE__TORRENTS__COLUMN_COMPLETED <STRING>
DATABASE_STRUCTURE__TORRENTS__PERSISTENT <true | false>
DATABASE_STRUCTURE__WHITELIST__BIN_TYPE_INFOHASH <true | false>
DATABASE_STRUCTURE__WHITELIST__TABLE_NAME <STRING>
DATABASE_STRUCTURE__WHITELIST__COLUMN_INFOHASH <STRING>
DATABASE_STRUCTURE__WHITELIST__PERSISTENT <true | false>
DATABASE_STRUCTURE__BLACKLIST__BIN_TYPE_INFOHASH <true | false>
DATABASE_STRUCTURE__BLACKLIST__TABLE_NAME <STRING>
DATABASE_STRUCTURE__BLACKLIST__COLUMN_INFOHASH <STRING>
DATABASE_STRUCTURE__BLACKLIST__PERSISTENT <true | false>
DATABASE_STRUCTURE__KEYS__BIN_TYPE_HASH <true | false>
DATABASE_STRUCTURE__KEYS__TABLE_NAME <STRING>
DATABASE_STRUCTURE__KEYS__COLUMN_HASH <STRING>
DATABASE_STRUCTURE__KEYS__COLUMN_TIMEOUT <STRING>
DATABASE_STRUCTURE__KEYS__PERSISTENT <true | false>
DATABASE_STRUCTURE__USERS__ID_UUID <true | false>
DATABASE_STRUCTURE__USERS__BIN_TYPE_KEY <true | false>
DATABASE_STRUCTURE__USERS__TABLE_NAME <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_UUID <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_ID <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_ACTIVE <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_KEY <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_UPLOADED <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_DOWNLOADED <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_COMPLETED <STRING>
DATABASE_STRUCTURE__USERS__COLUMN_UPDATED <STRING>
DATABASE_STRUCTURE__USERS__PERSISTENT <true | false>
API_0_ENABLED <true | false>
API_0_SSL <true | false>
API_0_BIND_ADDRESS <STRING>
API_0_REAL_IP <STRING>
API_0_SSL_KEY <STRING>
API_0_SSL_CERT <STRING>
API_0_KEEP_ALIVE <UINT64>
API_0_REQUEST_TIMEOUT <UINT64>
API_0_DISCONNECT_TIMEOUT <UINT64>
API_0_MAX_CONNECTIONS <UINT64>
API_0_THREADS <UINT64>
HTTP_0_ENABLED <true | false>
HTTP_0_SSL <true | false>
HTTP_0_BIND_ADDRESS <STRING>
HTTP_0_REAL_IP <STRING>
HTTP_0_SSL_KEY <STRING>
HTTP_0_SSL_CERT <STRING>
HTTP_0_KEEP_ALIVE <UINT64>
HTTP_0_REQUEST_TIMEOUT <UINT64>
HTTP_0_DISCONNECT_TIMEOUT <UINT64>
HTTP_0_MAX_CONNECTIONS <UINT64>
HTTP_0_THREADS <UINT64>
HTTP_0_RTCTORRENT = <true | false>
UDP_0_ENABLED <true | false>
UDP_0_BIND_ADDRESS <STRING>
UDP_0_UDP_THREADS <UINT64>
UDP_0_WORKER_THREADS <UINT64>
UDP_0_RECEIVE_BUFFER_SIZE <UINT64>
UDP_0_SEND_BUFFER_SIZE <UINT64>
UDP_0_REUSE_ADDRESS <true | false>
UDP_0_USE_PAYLOAD_IP <true | false>
UDP_0_SIMPLE_PROXY_PROTOCOL <true | false>
UDP_0_RECEIVE_METHOD <auto | recvmmsg | io_uring | rio>
UDP_0_PARSE_QUEUE_SIZE <UINT64>
database.chunk_size controls how many rows the tracker writes per SQL transaction when it flushes torrent/user updates to the database (once per persistent_interval). Instead of committing the whole sync batch in a single long-running transaction — which holds row/foreign-key locks for the entire batch and can block other applications writing to the same database (e.g. a website doing DELETE/UPDATE, leading to lock-wait timeouts) — the tracker commits every chunk_size rows, releasing locks frequently.
[database]
chunk_size = 1000 # rows per transaction (default: 1000; 0 = commit the whole batch in one transaction)Lower the value (e.g. 500 or 250) if another application sharing the database still experiences lock contention; raise it (or set 0) for maximum write throughput when the tracker owns the database exclusively. Applies to the high-volume torrents and users sync tables on MySQL, PostgreSQL, and SQLite.
Every /api/... endpoint (and /stats, /metrics) is protected by the API key configured in tracker.api_key (or the TRACKER__API_KEY environment variable). The key can be supplied in two ways:
-
Authorizationheader (recommended) — keeps the secret out of URLs, access logs and browser history:Authorization: Bearer <api_key>The
Bearerprefix is optional; a bareAuthorization: <api_key>is also accepted. -
tokenquery-string parameter (legacy) — kept for backwards compatibility:GET /api/torrents?token=<api_key>
If both are present, the Authorization header takes precedence. The key is compared in constant time. Example:
# Preferred — token in the header
curl -H "Authorization: Bearer <api_key>" http://127.0.0.1:8080/api/torrents
# Legacy — token in the query string
curl "http://127.0.0.1:8080/api/torrents?token=<api_key>"RtcTorrent is the built-in WebRTC peer-to-peer library that lets a browser (or Node.js process) act as a BitTorrent seeder or leecher without any browser plugin or native binary. It uses the standard HTTP announce endpoint with additional query parameters for WebRTC signalling.
A full protocol white paper is available in RtcTorrent.md.
Browser (leecher) ──announce + rtctorrent=1──► Tracker (Torrust-Actix)
◄── SDP offer from seeder ──
Browser ──answer + rtcanswerfor=<peer_id>────► Tracker
Seeder ──poll (announce) ───────────────────► Tracker
◄── SDP answer ──
WebRTC Data Channel established directly between Browser ↔ Seeder
cd lib/rtctorrent
npm install
npm run build # produces dist/rtctorrent.browser.js (minified)
npm run dev # watch mode for developmentThe build outputs two bundles:
| File | Target | Use |
|---|---|---|
dist/rtctorrent.browser.js |
Browser (<script>) |
Website player/downloader |
dist/rtctorrent.node.js |
Node.js (require) |
CLI seeder, server-side |
Copy dist/rtctorrent.browser.js to your web server's static assets, then include it in your HTML:
<script src="/assets/rtctorrent.browser.js"></script><!DOCTYPE html>
<html>
<head><title>RtcTorrent Demo</title></head>
<body>
<video id="player" controls autoplay style="width:100%"></video>
<script src="/assets/rtctorrent.browser.js"></script>
<script>
const client = new RtcTorrent({
trackerUrl: 'http://your-tracker.example.com/announce',
// Optional: override ICE/STUN servers
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
});
// Download via .torrent URL, magnet URI, or parsed torrent object
client.download('magnet:?xt=urn:btih:INFOHASH&dn=MyVideo&tr=http://your-tracker.example.com/announce')
.then(torrent => {
console.log('Started downloading:', torrent.name);
});
// Stream a video file directly into a <video> element
client.streamVideo('INFOHASH_HEX', 0, document.getElementById('player'));
</script>
</body>
</html><input type="file" id="filePicker">
<script src="/assets/rtctorrent.browser.js"></script>
<script>
const client = new RtcTorrent({
trackerUrl: 'http://your-tracker.example.com/announce'
});
document.getElementById('filePicker').addEventListener('change', async (e) => {
const file = e.target.files[0];
// Create a torrent from the selected file
const { torrent, magnetUri, infoHash } = await client.create([file], {
version: 'v1', // or 'v2' / 'hybrid'
name: file.name
});
console.log('Magnet URI:', magnetUri);
console.log('Info Hash:', infoHash);
// Start seeding — the tracker handles WebRTC signalling
await client.seed(torrent, [file]);
});
</script>| Option | Default | Description |
|---|---|---|
trackerUrl |
'' |
HTTP announce URL of the Torrust-Actix tracker |
announceInterval |
30000 |
Re-announce interval in milliseconds |
rtcInterval |
10000 |
WebRTC signalling poll interval in milliseconds |
maxPeers |
50 |
Maximum simultaneous WebRTC peers |
iceServers |
Google STUN | Array of ICE server objects |
| Method | Description |
|---|---|
create(files, options) |
Create a torrent from File objects (browser) or file paths (Node) |
download(torrentData) |
Download via magnet URI, .torrent URL, or parsed torrent object |
seed(torrentData, files) |
Seed an existing torrent |
streamVideo(infoHash, fileIndex, videoEl) |
Stream a video piece-by-piece into a <video> element |
stop() |
Stop all torrents and close connections |
parseMagnet(uri) |
Parse a magnet URI into a torrent object |
parseTorrentFile(buffer) |
Parse a .torrent file buffer |
calculateInfoHash(info) |
Calculate the SHA-1 info hash of a torrent info dictionary |
Enable RtcTorrent support on the HTTP listener in config.toml:
[[http_trackers]]
enabled = true
bind_address = "0.0.0.0:6969"
rtctorrent = true # Enable WebRTC signalling endpointOr via environment variable:
HTTP_0_RTCTORRENT=true
The bin/seed.js script seeds files from the command line:
# Install dependencies
cd lib/rtctorrent && npm install
# Single file
node bin/seed.js --tracker http://your-tracker.example.com/announce \
--name "My Movie" \
--out movie.torrent \
/path/to/movie.mp4
# Re-seed from an existing .torrent (no re-hashing)
node bin/seed.js --torrent-file movie.torrent /path/to/movie.mp4
# Seed from a magnet URI
node bin/seed.js --magnet "magnet:?xt=urn:btih:..." /path/to/movie.mp4
# Multi-torrent mode via YAML config
node bin/seed.js --torrents torrents.yamlYAML multi-torrent config example:
torrents:
- name: "My Movie"
file:
- "/data/movie.mp4"
trackers:
- "http://your-tracker.example.com/announce"
out: "/data/movie.torrent"
version: v1 # v1 | v2 | hybrid
webseed:
- "https://cdn.example.com/movie.mp4"Reload the YAML config without restarting by sending SIGHUP (Linux/macOS) or simply saving the file (polled every 2 seconds on all platforms).
Run the built-in demo server to test locally:
cd lib/rtctorrent
npm install && npm run build
npm run serve # serves demo at http://localhost:8080/demo/The tracker can optionally push live torrent peer statistics to a Redis or Memcache instance after every database sync cycle. This lets websites and dashboards read seeder/leecher counts at high speed without querying the SQL database directly.
Add a [cache] section to config.toml:
[cache]
enabled = true
engine = "redis" # "redis" or "memcache"
address = "127.0.0.1:6379" # or "user:pass@redis:6379", or "redis://:pass@redis:6379/0"
prefix = "tracker:" # key namespace prefix
ttl = 300 # seconds until a key expires (0 = no expiry)
split_peers = false # see "Key format" belowaddress takes a host name or IP and a port, optionally with credentials, or a whole URL with a database number; the password is masked in the logs.
The tracker connects to Redis/Memcache only when it writes (once per persistent_interval seconds). No persistent connection is kept open between sync cycles.
The cache is updated independently of
database.persistent. Settingpersistent = falsedisables SQL writes but the cache is still updated on every sync cycle.
Every torrent is stored under the key:
<prefix>t:<info_hash_hex>
For example, with the default prefix tracker: and a 40-character hex info hash:
tracker:t:a1b2c3d4e5f6...
The key is a Redis hash (or colon-delimited string in Memcache) with three fields:
| Field | Type | Description |
|---|---|---|
s |
u64 | Total seeders (BitTorrent IPv4 + IPv6 + WebRTC) |
p |
u64 | Total leechers (BitTorrent IPv4 + IPv6 + WebRTC) |
c |
u64 | Total completed downloads |
Redis — reading aggregated data:
HMGET tracker:t:<info_hash_hex> s p c
# returns: [seeds, peers, completed]Memcache — reading aggregated data:
GET tracker:t:<info_hash_hex>
# returns: "seeds:peers:completed" e.g. "42:100:500"
Each pool and IP version is stored as a separate field, giving a full breakdown:
| Field | Type | Description |
|---|---|---|
bt_seeds_ipv4 |
u64 | BitTorrent IPv4 seeders |
bt_seeds_ipv6 |
u64 | BitTorrent IPv6 seeders |
rtc_seeds |
u64 | WebRTC seeders |
bt_peers_ipv4 |
u64 | BitTorrent IPv4 leechers |
bt_peers_ipv6 |
u64 | BitTorrent IPv6 leechers |
rtc_peers |
u64 | WebRTC leechers |
c |
u64 | Total completed downloads |
Redis — reading split data:
HGETALL tracker:t:<info_hash_hex>
# returns all seven fields as a flat key/value list
# Or fetch specific fields:
HMGET tracker:t:<info_hash_hex> bt_seeds_ipv4 bt_seeds_ipv6 rtc_seeds bt_peers_ipv4 bt_peers_ipv6 rtc_peers cMemcache — reading split data:
GET tracker:t:<info_hash_hex>
# returns: "bt_seeds_ipv4:bt_seeds_ipv6:rtc_seeds:bt_peers_ipv4:bt_peers_ipv6:rtc_peers:completed"
# e.g. "30:5:7:80:15:5:500"
# bt4 bt6 rtc bt4 bt6 rtc completed
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$infoHash = bin2hex($rawInfoHashBytes); // 40-char hex
$key = 'tracker:t:' . $infoHash;
[$seeds, $peers, $completed] = $redis->hMGet($key, ['s', 'p', 'c']);
echo "Seeders: $seeds, Leechers: $peers, Completed: $completed";$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'tracker:t:' . $infoHash;
$data = $redis->hGetAll($key);
$totalSeeders = $data['bt_seeds_ipv4'] + $data['bt_seeds_ipv6'] + $data['rtc_seeds'];
$totalLeechers = $data['bt_peers_ipv4'] + $data['bt_peers_ipv6'] + $data['rtc_peers'];
echo "BT IPv4 seeds: {$data['bt_seeds_ipv4']}";
echo "BT IPv6 seeds: {$data['bt_seeds_ipv6']}";
echo "WebRTC seeds: {$data['rtc_seeds']}";- Fixed a whitelist with
persistent = falsenot being loaded at startup (regression in v4.2.24). v4.2.24 loaded a list only when its table was persistent, so a read-only whitelist, such as one pointed at the website's torrents table withpersistent = false, came up empty. Withwhitelist_enabledon, every announce for a torrent not added over the API since the restart gotunknown info_hash, and since those refusals are not counted as failures, the only sign was the seed and peer counts dropping. A list is loaded again whenever its mode is on, as before v4.2.24, and also when its table is persistent;persistent = falseonly stops it being written back. - Peer expiry no longer rewrites every idle torrent on every pass (regression in v4.2.24). With
persistenton, a torrent without announces for longer than the peer timeout stays expired, and each cleanup pass queued it again even when it had no peers left to remove. Every torrent loaded from the database was written to the database and to the cache everypeers_cleanup_interval, and Redis kept a zero-count key for each one. A torrent is now queued only when the pass actually removed peers from it.
- Bumped versions, including two breaking ones that needed no code changes:
utoipa-swagger-ui9 → 10 (andutoipa5 → 6 with it) andzstd0.13 → 0.14. - Minimum Rust version is now 1.94.0.
sqlx0.9 already required it, so the declared 1.88.0 was wrong;uuid1.28 andactix-server2.10 need 1.89. - Bumped
rtctorrent's npm dependencies within their major versions and rebuilt the shipped bundles;npm auditreports no vulnerabilities in the runtime dependencies.bencodestays on 2.x because 3.x and later are ESM-only and the library is CommonJS. - Added
version.sh, the Linux/macOS counterpart ofversion.bat. It updates the same files and leaves line endings and the final newline untouched. - Added a
.gitattributes(* text=auto) so a checkout on Windows and one on Linux no longer show every file as modified. - The Linux build requirements in this README listed
fontconfig, which nothing depends on anymore. They now list what the build actually needs: a C compiler andcurl. - Fixed the tracker aborting at boot while loading users or torrents from the database, and then failing the same way on every later start:
- SQLite: a numeric-id user row aborted the load (the INTEGER id was decoded as bytes), and so did the first user past 4 GiB uploaded or downloaded (the counters were decoded as
u32). - MySQL:
--create-databasemakes the counters and the idBIGINT UNSIGNED, which the signed decoders refuse. They are now read whether a column is signed orUNSIGNED, so tables created elsewhere (such as the website'storrust_actix_torrents, whosecompletedis a signedBIGINT) keep loading. - PgSQL: the key
timeoutcolumn isintegerbut was decoded asbigint. - Numeric user ids loaded from SQLite and MySQL are now hashed the way the API and PgSQL already hash them (
hash_id(id)), so a user loaded from the database and the same user posted over the API share one entry.
- SQLite: a numeric-id user row aborted the load (the INTEGER id was decoded as bytes), and so did the first user past 4 GiB uploaded or downloaded (the counters were decoded as
- Fixed announce keys added over the API getting the wrong expiry after a restart. Memory held
now + timeout, but the database was handed the baretimeoutand read it back as an absolute timestamp, so a one-hour key was expired after any restart. A timeout of0, documented as permanent, expired immediately. Memory and the database now store the same absolute expiry,0stays permanent, and very large timeouts saturate instead of wrapping.add_keytakes au64. Library API change. - SIGTERM now shuts the tracker down cleanly. The background tasks already stopped on it, but the main loop only waited for Ctrl-C, so
docker stopandsystemctl stopleft the process hanging until it was killed, without the final save to the database.docker/init.shnowexecs the binary so it receives the signal. - Shutdown no longer rewrites
config.toml. It re-serialised the running configuration, which wrote secrets set through environment variables (TRACKER__API_KEY, the cluster token, the database DSN) into the file in plain text, dropped its comments and made temporary overrides permanent. Its stated purpose, carrying the completed count over without a database, never worked: it wrote the value from startup.Configuration::save_from_configis removed. Library API change. - Added
--migrate-database, a one-off migration of the keytimeoutand userupdatedcolumns from 32-bit to 64-bit on MySQL and PostgreSQL (see Upgrading a MySQL or PostgreSQL database created before v4.2.24 above). Both columns hold Unix timestamps and would have overflowed on 19 January 2038; a key added with a long timeout could not be stored at all.--create-databasenow creates them asBIGINT, and the PostgreSQL loader reads either width, so the tracker keeps working before and after the migration. - Fixed every shutdown aborting before the final save to the database. The UDP parse pool owns a tokio runtime, and the pool was released at the end of an async receive loop, where dropping a runtime panics. Under the release profile's
panic = 'abort'the process died right there, after the listeners had stopped but beforeSaving final data to database.... The runtime is now disposed of withshutdown_background(). - Certificate hot reload now takes effect. The HTTP and API TLS listeners served a key cached once at startup, so
POST /api/certificate/reloadreported success while the old certificate stayed in use until a restart. Handshakes now read the current certificate from the store, and the cluster master's TLS listener goes through the same store instead of loading its PEM files once at startup, so a reload renews it too (filter with{"server_type": "websocket"}). The signing key is built when the files are loaded, so a reload with an unusable key now fails the reload request instead of breaking later handshakes.DynamicCertificateResolver::refresh_cacheis removed. Library API change.
- HTTP announces now honour
users_enabled./announce, and/{key}/announcewith keys enabled, carry no user key and were served anyway; they now getunknown user key, as UDP already answered. - A peer can no longer be stopped or moved by anyone who knows its peer id. Peer ids are no secret (non-compact responses list them, and every BitTorrent handshake reveals one), so
event=stoppedwith someone else's id emptied swarms, and a re-announce with it pointed the swarm at another address. A stored peer now only changes for a request from its own IP, or one carrying the same announcekey, the parameter BitTorrent has for exactly this; clients that change address and send a key (libtorrent-based ones, and every UDP client) keep moving freely. A peer that has missed two announce intervals is no longer defended, so a client without a key that changed address gets through on its second announce from there; a live peer, which re-announces every interval, cannot be taken over. - An empty
api_keyno longer opens the API.?token=matched it; an empty token is now always refused. - Removed HTTP/2 from the HTTP, API and cluster TLS listeners. actix offered it through ALPN, and the
h20.3 underneath has RUSTSEC-2026-0258 (unbounded empty DATA frames) with no 0.3 fix. Everything a tracker serves is HTTP/1.1. - One oversized request no longer disconnects a cluster slave. The slave forwarded any query string, and the master closed the whole cluster link on a frame over 64 KiB; slaves now apply the 8 KiB query cap before forwarding and the master accepts frames up to 1 MiB.
rtctorrent = falseis now honoured in cluster mode, where the slave used to forward WebRTC announces without checking it.- A failure on the cluster master now reaches the client as a
failure reason(HTTP) or no datagram (UDP), instead of an empty 200 or an empty datagram. - UDP: a datagram too short to carry a transaction id gets no reply (it used to get 19 bytes back for 1 in, to an unverified source), malformed requests are answered with their own transaction id instead of 0, a refused request keeps its reason (
unknown key,torrent not on whitelist, ...) instead of a genericbad request, and a UDP scrape is refused whenkeys_enabledis set, as an unkeyed HTTP scrape is. - The generated
config.tomlis created owner-only (0600); it holds the API key, the cluster token and the database DSN. - Database and cache connection strings are logged with the password masked.
cache.addressnow accepts credentials, a host name and a database (user:pass@host:port, or a URL such asredis://:pass@redis:6379/0), so the Redis the website reads no longer has to run without a password. The key format is unchanged.- Moved off
actix-web-actors, which is deprecated upstream, toactix-wsfor the cluster master's connections. - Dropped OpenSSL: Sentry's
transportfeature pulled innative-tlsbeside the rustls everything else uses. Sentry now sends over reqwest with rustls, and OpenSSL is no longer a build requirement. - Added
.cargo/audit.toml, which ignores four advisories that were checked and found not to apply (rsa's Marvin attack: sqlx only encrypts with the server's public key; three inzip, a build-only dependency whose affected functions are never called). The reasons sit next to each entry.
- The cache (and the website's peer counts) now follows peers leaving. Peers that expired, and a
stoppedthat emptied a torrent, never reached the update queue, so Redis kept showing them until its TTL (24 hours in a typical setup). Both now queue the new counts, or remove the key. - A big swarm no longer hands every client the same peers. The peer list always started at the front of the swarm's map, so in a large swarm everyone got the same 72 peers. It now starts at a uniformly random peer and wraps around. The peer maps are
IndexMaps instead ofHashMaps for this: access by position makes the random start O(1), so announce cost stays bounded bynumwantrather than by swarm size. Measured onannounce_into_populated_swarm: about 1.7 µs per announce at both 200 and 1000 peers on the test machine, which includes the new peer-identity check. - The RtcTorrent signalling response honours
numwant(1..=72) like a BitTorrent peer list. It used to send every offer it had, up to 128 of up to 8 KiB each, while the client dials at most 8 per round. - Queued key, user, whitelist and blacklist updates can no longer overwrite each other. They were keyed by the wall-clock nanosecond, which two updates can share (Windows ticks every 100 ns) and which can step backwards; they now use the same sequence counter as torrent updates.
- The user update queue holds one entry per user, instead of one full copy of the user per announce until the next flush.
torrents_activeentries for torrents a client left withoutevent=stoppednow expire after the peer timeout, instead of staying forever.- Bulk
POST/DELETE /api/torrentsandPOST /api/usersvalidate the whole batch first: a bad entry used to return 400 after part of the batch (in map order) had already been applied. - A UUID is now the same user however it is cased; it was hashed as sent but stored lowercased, so an uppercase UUID became a different user after a restart.
- Non-compact announce responses send
peer idas the raw 20 bytes BEP 3 specifies (it went out as 40 hex characters) and leave it out forno_peer_id. - io_uring: kernels without
IORING_FEAT_EXT_ARG(before 5.11) now fall back to recvmmsg instead of every receive thread exiting at its first wait, and the receive buffers are no longer freed while the kernel may still be writing into them. max_connectionsunder[[http_server]]and[[api_server]]is now applied; it was read, documented and ignored.- Whitelist, blacklist, key and user changes made over the API are saved even while that mode is off. The API accepted them and queued them for the database, but the queue was only written, and the table only loaded at startup, while
whitelist_enabled(orblacklist_enabled,keys_enabled,users_enabled) was on. With the mode off the queue grew without limit and the changes were lost at the next restart. Each table is now loaded and saved whenever it is persistent ([database] persistent, or the table's ownpersistent); the*_enabledswitches only decide whether announces are checked against it, as before. A list can be filled first and its mode switched on later, andDELETE /api/{whitelist,blacklist,keys,users}/clearno longer answers400 ... not enabledwhile the mode is off. A whitelist that fails to load still stops the tracker while whitelist mode is on, and is logged otherwise. - Warnings about invalid environment overrides are shown. They were logged before the logger was set up, so they never appeared; they now go to stderr, with the rest of the configuration checks.
cargo clippy --all-targetsis clean. The flush functions (save_torrents,save_keys,save_users, ... and their_updatesvariants) return thesqlx::Errorthey hit instead of(), and log it; the remote-IP lookups returnOption; request helpers box their ready-made error response. Library API change. The UDP scrape parser loses an error branch that could not be reached.
About 3,000 lines of Rust (14%) and four direct dependencies (uuid, sha1, async-trait, tokio-shutdown) are gone, with the API status codes, the Redis keys and the cluster wire format unchanged. What you can notice:
- An unparsable number or boolean in an environment override now keeps the configured value (with a warning on stderr) instead of resetting it to a built-in default, as
TRACKER__MAX_TORRENTSalready did.DATABASE_STRUCTURE__*__PERSISTENTstill falls back to unset. - Pending whitelist, blacklist and key updates hold one entry per hash, as user updates already did, so adding and removing the same hash before a flush writes only the last action. The
whitelist_updates,blacklist_updatesandkeys_updatesstatistics count pending hashes, not API calls. - The 400 bodies for a malformed hash are consistent across the API:
bad <field>for a wrong length andinvalid <field>for a non-hex character.GET/POST/DELETE /api/torrent/{info_hash}used to answerbad info_hashfor both, andDELETE /api/key/{key_hash}saidinvalid key. receive_method = "auto"is now an alias ofrecvmmsg(which it always behaved as) rather than a separate value.- A flush logs one
[SYNC ... UPDATES]line instead of two, and a cluster master that cannot bind its address logs the reason and exits instead of panicking.
Removed or changed in the library API (Library API change):
- Dead code: the UDP response parsers (
Response::from_bytes,parse_ipv4_peers,parse_ipv6_peers,parse_scrape_stats) andRequest::write; the clusterRequestType::ApiCall/ProtocolType::Api,process_api_calland the per-encoding encode/decode helpers (process_cluster_requestno longer takes an encoding);src/ssl/ssl.rs,reload_all,reload_certificate_with_paths;hex2bin(usehex_to_idorstr::parse::<InfoHash>()),validate_info_hash_hex,validate_peer_id_hex,convert_int_to_bytes/convert_bytes_to_int, and about twenty unused tracker methods and constants. - The
DatabaseBackendandCacheBackendtraits, each with one implementation, are gone; the connectors keep the same methods as inherent ones. - The
TorrentTracker::save_torrents/save_keys/save_users/save_whitelist/save_blacklistwrappers are removed; the_updatesflushes callDatabaseConnectordirectly, andTorrentTracker::save_all_updatesruns all of them (the periodic flush and the final save on shutdown use it).get_key_updatesandremove_key_updateare removed. udp_serviceandUdpServer::newtake theUdpTrackersConfiginstead of twelve arguments.trusted_proxy_ipsandproxy_addressesare parsed straight intoVec<IpAddr>, so the shadowtrusted_proxy_addrs/proxy_addrsfields are gone.api_parse_bodybecameapi_json_body::<T>()andparse_info_hashbecameparse_hex_id::<T>();DynamicCertificateResolver::server_configbuilds a listener's rustls config.InfoHash,PeerIdandUserIdimplementDeserializefrom their hex form, andCustomErrorderivesthiserror::Error.
- The image is built from the checkout it sits in (
docker build -f docker/Dockerfile .from the repository root) instead of cloning a git tag from GitHub, in two stages on pinnedrust:1.98-alpine3.24/alpine:3.24: 108 MB instead of an image carrying the Rust toolchain and the whole build tree. A.dockerignorekeepstarget/,config.tomland databases out of the build context. - The container runs as an unprivileged
trackeruser, andinit.shexecs the tracker sodocker stopreaches it. - The healthcheck works. The image copied
healthcheck.pyunder the name of its PyInstaller build but had no Python, so the container was always unhealthy; the PyInstaller build itself was a one-directory build missing its_internal/folder, so it could not run either. The image now installspython3and runs the script, and the broken binary is removed. docker-compose.ymltakes the API key from the environment (TRACKER__API_KEY, required) instead of a hard-coded one, and publishes the API on127.0.0.1only.- GitHub Actions: workflows are read-only by default with write access only for the release job, checkouts no longer leave the token on disk for build scripts to find, third-party actions are pinned to commit SHAs and moved to their current majors (checkout v7, upload-artifact v7, download-artifact v8, which now fails on a hash mismatch, action-gh-release v3; all Node 24, none with an input change this workflow uses), the shell steps pass actionlint and shellcheck, and UPX is pinned to 5.2.1 and checked against its SHA-256. Dependabot targeted a
developbranch that does not exist, so it never opened a PR; it now targets the default branch. version.bat/version.shno longer touch the Dockerfile, which has no version in it any more.
- Pieces from peers are now verified or rejected, never accepted unchecked. A piece was accepted when the torrent had no v1 hashes (every pure v2 torrent), when its index was out of range, or when hashing failed (always, in the browser bundle outside a secure context). Pure v2 torrents are now checked against their files'
piece layers; out-of-range or wrongly sized pieces are dropped; and without a hash function pieces are rejected. Behaviour change: a page serving the browser bundle over plainhttp://(other thanlocalhost) can no longer download, since it has nocrypto.subtle; serve it over HTTPS. - A peer no longer decides how much memory a client allocates: piece chunks are only buffered for pieces this client requested, at exactly the piece's size.
seed.js --torrent-filerefuses paths that leave the working directory (../, absolute paths), which a crafted torrent could use to have the seeder read and serve any file.- Failed and closed WebRTC connections are dropped, so a seeder that went away can be dialled again, and at most 8 new connections are opened per announce round.
- A stopped torrent clears its timers, so a Node.js process exits once its last torrent stops.
- Seeding from memory joins the files once instead of once per piece served.
- The bundles build without webpack's two "Critical dependency" warnings: the Node.js WebRTC packages are now loaded with plain
require()calls (externals in the Node bundle, stubbed out in the browser one) instead of passingrequirearound as a value. - The demo server (
npm run serve) streams files with HTTP range support (video seeking, and webseed fetches past the first piece, failed against it) and listens on127.0.0.1only. - Multi-file pure v2 torrents use the v2 layout. BEP 52 starts every file on a piece boundary, and its per-file hashes cover exactly that; this library laid the files out back to back as in v1, so their pieces matched no hash. Seeding (from disk, memory or browser
Files) and downloading now both use the piece-aligned layout. Behaviour change: for multi-file pure v2 torrents only, peers on this version and older ones no longer understand each other's piece numbers. - v2 torrents created by this library now follow BEP 52. A file's final 16 KiB block was zero-padded before hashing, which libtorrent and other clients do not do, so they disagreed with this library's
pieces rootand info-hash for most files. Torrents created before this release still verify.create()also returns the info dictionary it actually encoded, so seeding the object it returns lays the data out as the torrent describes. - Hybrid (v1 + v2) torrents created by this library are now valid for other clients. Their v1 file list was in the caller's order with the files back to back, while the v2 file tree is sorted and starts each file on a piece boundary, so libtorrent refused multi-file hybrids outright ("the v1 and v2 file metadata does not match") and found single-file ones inconsistent. The v1 list now follows the file tree with BEP 47 pad files, and the v1 piece hashes cover that layout. Checked with libtorrent 2.1.1: hybrid and v2, single- and multi-file torrents from this release load and verify fully; the same torrents from v4.2.23 were rejected or failed verification. Behaviour change: multi-file hybrid torrents made by older versions keep their old layout and still download between peers of this library.
- A seeder drops piece requests beyond twice what a leecher keeps in flight per connection, and ones outside the torrent, instead of queueing them without limit.
- Stopping a seeder while its first WebRTC offer is still being gathered now closes that connection; it used to stay open and keep a Node.js process running.
npm testnow runs a piece-verification suite (v1, hybrid, single- and multi-file v2 and hybrid with pad files, BEP 52 roots, the request cap) instead of failing with no tests.
- Bumped versions.
- Fixed
POST /api/torrent/{info_hash}/{completed}(and the bulk variant) wiping the swarm of an already known info-hash: it handedadd_torrenta freshly built, peer-less entry, which replaced the live peer maps with empty ones and flushed those zeroes on to the database and the cache. - Security: bumped
rustlsto 0.23.45 for RUSTSEC-2026-0285 (TLS 1.3 handshake messages accepted across encryption level boundaries). - Security: the global statistics counters now saturate instead of wrapping.
update_counterusedfetch_add/fetch_sub, so a client-supplied magnitude —completedoff the API path, a user'suploaded/downloadedoff an announce — could drive a counter negative;-valuealso overflowed oni64::MIN. set_torrent_completedreturns the counts to queue rather than the stored entry, so registering a hot torrent no longer clones its whole swarm under the shard write lock.- The Swagger page's reported API version no longer lags the release;
version.batnow bumps it too.
- Deleted the
rtctorrent_bridgemodule (9 files). It shelled out to the RtcTorrent node CLI to create, seed and download torrents, and was reachable only throughcreate_rtctorrent, which had no callers — as didseed_torrentanddownload_torrent. Library API change. - Deleted
QueryBuilder(187 lines). Sixteen of its seventeen methods had no callers at all and the seventeenth was only reached by its own tests; the database layer builds SQL withformat!+AssertSqlSafedirectly. Library API change. DatabaseConnectorandCacheConnectorare enums instead of oneOptionper engine plus anOption<engine>discriminant. Each had exactly one constructor filling exactly one engine, so "engine set but not connected", "two engines at once" and "no engine" were unreachable states that every method still answered for — 36 and 18 dead error arms between them.database_connector.rsdrops 554 lines to 292 andcache_connector_cache_backend.rs154 to 68, with the invalid combinations now unrepresentable. Library API change.- Deleted 13 public methods with no callers anywhere, including tests (214 lines):
batch_contains_peers,clean_user_active_torrents,get_peers_ref,get_multiple_torrents,add_torrent_updates,add_user_active_torrent,create_rtctorrent,iter_all_torrents,get_torrents,backend,get_shard_content,get_shardandhas_certificate. The#[allow(dead_code)]onimpl TorrentShardingthat had been hiding most of them is gone too. Library API change. - Deleted
ClusterMode::is_master/is_slave/is_standalone— every caller already used==ormatch— and theIpmarker trait, whose zero methods only bounded two structs that are never instantiated with anything butIpv4Addr/Ipv6Addr. bin20_to_hexhand-rolled a hex lookup table and loop eight lines belowbin2hex, which already usedhex::encode_to_slicefrom the same crate. It uses it now too.
- Announce is no longer O(swarm size).
AnnounceEntry::from_entrycloned all six peer maps — up to 768 wholeTorrentPeervalues — inside the shard write lock, while a response reads at most two of them and only wants each peer's address and id. The BitTorrent maps now carry a 56-byteResponsePeerin aVecinstead of a 104-byteTorrentPeerin a hash map (no per-peer hashing, one exact-size allocation), the caller says which half it needs, and the BitTorrent per-map cap drops from 128 to 73 becausenumwantis clamped to 72. The RtcTorrent maps keep their ownSNAPSHOT_RTC_PEER_CAPof 128: the signalling response never consultsnumwant, so the number of signalling partners a WebRTC peer can see is unchanged. Measured on the newannounce_into_populated_swarmbenchmark: about 7x faster at a 1000-peer swarm (7.96 µs → 1.10 µs) and 2.8x at 200. The more useful figure is the scaling: growing a swarm from 200 to 1000 peers used to take an announce from 3.02 µs to 7.96 µs and now takes it from 1.09 µs to 1.11 µs, because the work is finally bounded bynumwantrather than by swarm size. - The UDP parse queue no longer reserves 312 MB per listener.
parse_queue_sizeis now configurable under[[udp_server]](envUDP_n_PARSE_QUEUE_SIZE) and defaults to 65536 slots — about 20 MB, down from a preallocated 1,000,000 — freeing ~292 MB per UDP listener, doubled when IPv4 and IPv6 servers both run. - Reaching a shard no longer costs an atomic refcount round-trip:
TorrentSharding::shard_forborrows the shard instead of cloning itsArc, on the thirteen call sites that immediately lock and drop it. - The cluster announce builders used the cloning
get_peers, so a forwarded announce copied its peers a second time; they now read the snapshot directly. Their unusedtrackerparameter is gone. - Removed
RtcData::connection_statusandupdate_rtc_connection_status. The field held one of three literals, was allocated per RtcTorrent peer, reallocated on every state change and cloned on every announce — and nothing ever read it. Library API change. - Dropped the dead
is_rtc_requestbranches in the HTTP announce response builder; RtcTorrent requests return earlier and never reached them. - Fixed a bug in
set_torrent_completed's statistics delta: converting each side toi64separately meant twocompletedvalues both abovei64::MAXsaturated to the same number and reported no change at all, however far apart they were. The difference is now taken inu64and only its magnitude clamped before the sign is applied. parse_querymoved off SipHash to the project'sAHashMapand skips percent-decoding for values holding no escape: about 11% off an announce query parse (1.49 µs → 1.32 µs). What remains is per-key allocation, which would need a borrowed key type to remove. Safe despite the keys coming off the wire — see below.- Tried and reverted: giving each statistics counter its own cache line showed no measurable difference on an 8-thread announce benchmark (p = 0.91), so the ~3.2 KB and the extra indirection buy nothing at this core count. An earlier run of that benchmark suggested it was 6.5% slower, but the benchmark itself was at fault — every task cycled the whole info-hash set, so shard-lock contention swamped the counter traffic it was supposed to isolate. Fixed to give each task a disjoint slice, which is what produced the honest result.
tls_connection_ratenow actually does something. It was plumbed through config, documented and defaulted, but never read by anything. It is the cap on concurrent TLS handshakes per worker thread — the expensive half of a TLS connection — and is now passed toHttpServer::max_connection_rateon every TLS listener (HTTP, API and cluster master). Config change:actix-tlsstores this in a single process-global, so three separate keys could never each take effect;tracker_config.cluster_tls_connection_rateis nowtracker_config.tls_connection_rate(envTRACKER__TLS_CONNECTION_RATE) and the unreachable[[http_server]]/[[api_server]]keys of the same name are removed. Existing config files keep parsing — unknown keys are ignored — and pick up the 256 default, which is what they effectively had all along.- Investigated and found to be a non-issue:
AHashMapisBuildHasherDefault<AHasher>, which ahash's documentation calls "fixed keys" — but with thestd/runtime-rngfeatures (both on by default) those keys come fromgetrandomon first use and live in a process-wideOnceBox. "Fixed" means identical across every map within one run, not hardcoded: three runs of the test binary hash the same 20-byte value to three different results. Info-hash and peer-id collisions therefore cannot be precomputed, and no hasher change is needed.
- Bumped versions.
- Security: moved off the yanked
chacha200.10.1 thatrand,sqlxandtungstenitepulled in. - Bumped
rtctorrent's npm dependencies and rebuilt the shipped bundles.
- Bumped versions.
- Dropped the unmaintained
rustls-pemfile(RUSTSEC-2025-0134) for the PEM support already inrustls-pki-types; TLS key files may now be PKCS#8, PKCS#1 or SEC1 everywhere, including the cluster master. - Security:
/scrapewithout a key is now refused whenkeys_enabledis set, matching/announce. - Security: cluster WebSocket connections that never complete the handshake are dropped after 30 seconds instead of holding a connection slot forever.
- Fixed two lifetime bugs and an alignment bug in the Windows RIO receive backend.
- The UDP hot path no longer builds a Sentry transaction per datagram when tracing is off.
hex2binrejects over-long input instead of silently truncating it to the first 20 bytes, and no longer reports malformed client input to Sentry.- The announce path no longer aborts the process when the system clock sits before the UNIX epoch; it now uses the shared
current_time()helper. - Added the official Discord server: https://discord.gg/rC7aEtpwMX
- Applied full audit and security scan on code, and applied recommended solutions.
- Optimization of over-engineered code, applied recommended alternatives.
- Bumped versions.
- Fixed an exploit issue with fast-uri on the RtcTorrent library
- Bugfix: A database connection loss or error, could freeze and lock up the update thread, should be fixed now.
- Full code auditing, and found some possible security issues in the future.
- Applied a README fix about library installation on Linux (Thanks https://github.com/diederikdehaas).
- Some small version bumps, nothing special.
- Added clearer error reporting for invalid hex-based identifiers.
- Tightened UUID and database identifier validation rules.
- Improved BEP 15 UDP request/response encoding and decoding for consistent big-endian behavior.
- Updated application/desktop/container version to 4.2.16 (including Docker image tags) and refreshed bundled dependency versions.
- Updated UDP parsing tests to match the revised packet encoding.
- Did some further performance tweaking
- Bumped versions
- Added full doc blocks
- Hotfix in regard to database handling
- A full audit and security scan of the code, shown some possible vulnerabilities
- Small little performance tweaks
- Adding a better token usage by recommending a header bearer, legacy still works
- Applied a fix for Windows, implemented RIO for Windows
- Further optimization and some refactor work to improve performance and efficiency
- Minimum Rust version is 1.88.0
- Applied UDP traffic control performance and improvements
- Introduced a new optional config for further optimization: receive_method. This can be "auto", "recvmmsg" or "io_uring". By default, "recvmmsg" is used, but io_uring can be used when you received a lot of traffic
- Fixing a database (PostgreSQL) implementation code (Thanks zmiguel)
- Bumping version of SQLx, needed to modify the code to use the newer SQLx functions properly
- Various audit findings and performance fixes
- Bumping versions
- Locking versions, cause of chain attacks, making sure we use "safe" libs
- Hotfix for the loading of data in memory, a SQL query issue
- Added optional "persistent" override for each table
- Did another full audit scan and fixed possible issues
- Hot Fix for the redis/memcache system, instead of having a persistent connection, only connect when needed
- Updated the readme for some information how to use the Cache system
- Fixing the caching mechanism, it wasn't working as expected
- Added an option (it's optional, so it won't break anything) to either split up the seeds/peers list as 2 numbers or as separate numbers in BitTorrent IPv4, IPv6 and RtcTorrent.
- Fixing a TLS/SSL bug where more than 1 provider was available, and server didn't know what to use
- Changed some informative data
- Bug-fix needed for the peer/torrent cleanup thread
- Fixing versioning, added back healthcheck for Docker and fixed some other small bugs
- Added
//////!doc comments throughout the codebase for docs.rs / crates.io publishing - Made a large set of
[tracker_config],[sentry_config], and[database_structure.*]keys optional inconfig.toml— sensible defaults are applied automatically when the keys are absent- Optional tracker fields:
whitelist_enabled,blacklist_enabled,keys_enabled,keys_cleanup_interval,users_enabled,swagger,prometheus_id, allcluster_*fields, and allrtc_*fields - The entire
[sentry_config]section can now be omitted (defaults to disable) - All
[database_structure.*]subsections can be omitted (default table/column names are used)
- Optional tracker fields:
--create-confignow annotates every optional key with an inline# Optional: defaults to …comment so new users can see at a glance what can be left out- Refactored
impl RtcDataout of the struct file intosrc/tracker/impls/rtc_data.rs, consistent with the rest of the project structure
- Adding LZ4/ZSTD compression for memory objects for the RtcTorrent peer data, enabled by default, disablable through config
- Memory optimization for the peer data
- Some small bug fixes
- Refactoring the whole project structure
- Adding security and anti-malicious validation and checks
- Adding RtcTorrent library and support to the tracker, for web streaming and downloading of BitTorrent. This is a major change from v4.1.0. It uses the default HTTP BitTorrent announce endpoint, with additional parameters
- Added hot reloading of SSL certificates for renewal
- API has an extra endpoint to run the hot reloading
- Some more code optimizations
- Added a full Cluster first version through WebSockets
- Option to run the app in Stand-Alone (which is default, as single server), or using the cluster mode
- When set to Master, it still functions as if being Stand-Alone, but with WebSocket support for clustering
- When set to Slave, it will forward all the requests to the Master server to be handled
- Added configurations to be applied in config.toml and the environment variables
- Added statistics, also showing cluster statistics next to the rest. Slave will only show active requests
- WebSocket data can be sent in 3 different ways, but Master server is the leading what way to talk
- This is a very early version of the WebSocket cluster implementation, and needs thorough testing
- Moved the more database engines additions to another version (and MeiliSearch/ElasticSearch support for v4.2)
- Refactored the database engine to be less massive, more logical and less redundancy
- Implemented a Redis and Memcache cache optionally to push peer data to, for usage on websites (without burdening SQL)
- Added UDP support for Cloudflare's "Simple Proxy Protocol" (https://developers.cloudflare.com/spectrum/how-to/enable-proxy-protocol/#enable-simple-proxy-protocol-for-udp)
- Another little overhaul, changing some memory tools for enhancement and performance
- Less CPU cycles applied when requests are handled
- Preparing for v4.1 to add some more database engines to the mix apart from SQLx
- Small debug and hotfixes
- Fixed the unit test that somehow failed
- Updated this readme file
- More code optimizations thanks to AI scanning further
- UDP performance tweaks (some new config options added)
- Added an initial unit testing for checking if all functions and features work as expected
- Code optimizations thanks to AI scanning
- Huge memory and CPU consumption improvement for UDP, using offloading
- Added further UDP improvement by adding customization, also added to the config:
- Receive Buffer Size
- Send Buffer Size
- Reuse of Address
- Updating libraries and cleanup code
- Updating libraries
- Adding healthcheck for Docker through Python check script
- Updating libraries
- Adding full environment support to override configurations (Thanks tachyon3000 for the idea)
- Updating libraries (Actix 4.9 to 4.10)
- Some critical exploit in ZIP fixed
- Some faulty v4.0.8 deployments fixed with GitHub
- Updating libraries
- Fixing threading for UDP
- Removed a feature from Parking Lot, cause of an unfixed vulnerability
- Update Swagger UI to version v4.0.0 and OpenAPI version v3.1.1
- Cleanup was still broken, did a big rewrite, after testing it works now as expected
- Did some tokio threading correctly for core threads
- Added a new configuration key, to set the threads, default for each shard (256), but can be changed
- Fixed some clippy issues
- Found a performance issue on peers cleanup
- Switched peers cleanup from Tokio spawn to Thread spawn for speedup
- Bumped version of Tokio
- Library bump
- Further implementation of Sentry (trace logging)
- Fixing announce and scrape paths, since it is the wrong way.
- Fixing various smaller bugs that isn't app-breaking, but should be handled better.
- Added support for Sentry.io SaaS and self-hosted setup.
- Preparing work for version v4.1.0, which will introduce LUA support for middleware.
- Added option that the system will remove data from database.
- Added updates variables for the white/black list and keys tables.
- Renaming the "database" naming which should be "tables".
- A lot of fixes and bugs I stumbled upon.
- Completely rebuilt of the tracker code, for readability.
- Moved to Actix v4, thus versioning this software to v4.0.0 as well.
- Rebuilt and remade the way configuration file is created (you need to give the command as argument for it).
- Redone the whole database system, is tested with the latest versions available at this time.
- API has gone through a lot of work and tested.
- Introduced Swagger UI as testing and documentation.
- A lot of improvements in speed and performance applied further.
- Import and Export function added, will dump or import from JSON files, handy for when making a backup from your existing database, or when migrating to an other database engine.
- Removed WebGUI, was outdated and not really useful.
- Bumped library versions significantly, including security patches.
- Fixed changes in libraries to work properly.
- Tuned the non-persistence code to use less memory.
- Bumped library versions, including security patches.
- Fixed a bug in the PostgreSQL handler.
- Some forgotten naming from Torrust-Axum to Torrust-Actix.
- Bumped library versions.
- Modified the way scheduling was done through threads, it could lock up and slow down public trackers with heavy activity.
- Tweaking the SQLite3 database usage and database space consumption.
- Full overhaul on how torrents and peers are used in memory. Using crossbeam skipmap for thread safe non-locking memory sharing.
- Some various improvement on coding performance, readability and linting the files.
- Replaced Tokio Axum web framework for Actix, reason: Missing critical things like a timeout on connect, disconnect, read and write, and support was lackluster.
- Renamed the GitHub repository from torrust-axum to torrust-actix.
- Adding user tracking support with an extra key.
- Bumped library versions.
- Added a Code of Conduct file, as some open source projects need this.
- Added a Maintenance toggle function to API and WebGUI.
- Configuration file is not generated when it doesn't exist, or has invalid data, unless forced with a '--create-config' argument.
- Fixed various small bugs.
- Bumped library versions.
- Database for SQLite3, MySQL and PostgreSQL now works properly with all the tables, and will be used if enabled.
- UDP had a problem in IPv4, fixed the code for correctly parsing byte array.
- Cleanup and refactoring of some redundant code.
- Added some small checks where needed to prevent errors.
- Whitelist System: You can enable this to only allow torrent hashes to be used you specify in the database, or add them through the API.
- Blacklist System: You can enable this to disallow torrent hashes to be used you specify in the database, or add them through the API.
- Keys System: You can enable this to only allow tracking when an activated "key" hash (same as an info_hash, 20 bytes or 40 characters hex) is given. Keys with a timeout of zero "0" will be permanent and won't be purged by the cleanup.
- WebGUI: The API has an available web interface, which can be accessed through https://your.api:8080/webgui/ and giving the correct API Key, which you configure in the configuration file.
- Customizable database structure can be given in the configuration file.
- The system is also now available through Docker Hub at https://hub.docker.com/r/power2all/torrust-axum
- Bugfixes
- SQLite3 support added
- MySQL support added
- PostgresSQL support added
Initial version of Torrust-Axum.
This Torrust-Tracker was a joint effort by Nautilus Cyberneering GmbH, Dutch Bits and Power2All. Also thanks to Naim A. and greatest-ape for some parts in the Torrust-Tracker code. This project (Torrust-Actix) is built from scratch by Power2All.