Software Selections
Every piece of software here is one we commit to for years. The real cost of a choice isn't the time spent making it; it's the years spent living with it, the expense of tearing one out once it's load-bearing, and — for anything we ship — having already put it on other people's computers. So we spend the time up front.
These choices fall into three kinds, and the exposure grows across them. Service providers are the outside services we depend on but don't run — a domain registrar, a DNS host, a cloud platform like Amazon (which, as it happens, we don't use); we trust them to hold our data and honor their terms. Modules we run are the software we operate ourselves, on our own hardware, exposed to the internet around the clock — HAProxy is one. Modules we build in are the components compiled into the software we distribute, which then run on users' own machines, out of our reach — Tauri, a cross-platform desktop application framework, is one of these. A bad provider can drop us or change its terms; a bad module we run is ours to host and patch; a bad module we ship we've placed on thousands of other people's computers. The criteria below apply to all three, but they bite hardest on the last.
This isn't about right versus wrong choices. You can research a decision exhaustively and still watch it go wrong years later, because what fails is rarely the software as it was. It's what becomes of it. A maintainer burns out. An open-source team is acquired, and the new owner shelves the project. A dependency quietly rots. Incentives that lined up with ours at the start drift apart. Everyone in this stack works hard within the resources and constraints they have, so trouble, when it comes, usually traces to diverging incentives rather than carelessness. The aim isn't a perfect pick. It's to weigh what makes later trouble less likely, and to favor software whose incentives stay aligned with the people who depend on it. We write the reasoning down, in the open, because the durable thing isn't which software we picked. Software changes. What lasts is how we decided: the method is what carries to the next choice, and what a reader running their own infrastructure can reuse.
Methodology
The first question is always fitness for the specific job: the right tool for this deployment's shape and scale, not the most popular tool in the abstract, with enough coverage and headroom that we won't outgrow it. Popularity is a useful signal but a crude one. We look for software mainstream enough to be well-documented and well-supported — the kind of thing the community, and the tools we lean on, can actually help with — without being so niche that a five-figure download count marks a one-person hobby a step from abandonment. There is even a case for the runner-up: the most popular option often rests on being the default, while the second-place choice competes by being leaner and better. We treat that as a gentle default rather than a rule, and several choices here overrode it, because something else on the scale mattered more. Leaving a mainstream option takes both a push and a pull — a real reason to go (bloat, breakage on the simple cases, a deprecated path) and an alternative that has itself won serious adoption; without the pull, we stay put even when the push is real.
That instinct has a deeper version, and it may be the most consequential judgment we make. Survey almost any established corner of software and the options sort themselves along a lifecycle. The Champion is what everyone uses, and so the thing everyone complains about: React, with its longtime companion webpack, is the type — dominant and battle-proven, but encrusted with a decade of configuration, plugins, and backward compatibility, to the point that a fresh project can emit warnings before it has done anything at all. The Challenger is younger and leaner, built once the Champion's mistakes were visible and designed to avoid them. Vite is our example: where webpack bundles the whole application up front and expects a hand-tuned config, Vite serves source over the browser's native modules during development, so the dev server starts instantly and hot-reload is near-immediate, and a new project runs clean on sensible defaults. The Upstart is the one the enthusiasts love — newest, often the most elegant, the framework a developer reaches for on a weekend project while using the Champion at work. SvelteKit is the type: it compiles components down to lean vanilla JavaScript, but its ecosystem is younger, its libraries fewer, and its ground still shifts under breaking changes.
We aim, deliberately, for the Challenger. The Champion's gravity is real — its documentation, its answers, its hiring pool — but its age comes bundled with its stability, and hello world should not already be fighting you. The Upstart is a joy to write and a gamble to commit years to, with sharp edges still being filed off in public. The Challenger is the software that has grown up without yet growing old: mature enough that the obvious bugs are gone and the patterns have settled, lean enough to start fast and stay simple. That is why ftorrent lives in the Vue and Vite world rather than the React or Svelte ones, and why these docs run on VitePress, built on exactly that stack. Simplicity isn't an aesthetic preference here: simple tools ship fewer bugs, start faster, and are far easier to reason about when something breaks.
From there, a handful of properties earn their keep. We prefer turnkey daemons over libraries we would have to wrap, standard packaging over bespoke builds, and a small footprint over a kitchen sink — every line of glue we write is a line we then maintain. We value components that are observable: a service that can tell us plainly whether it is healthy, ideally in a single call, is worth a great deal at three in the morning. We weigh security posture by where it counts — memory-safe code where it faces untrusted input, a minimal attack surface, and attention to which code paths we actually exercise, since a flaw in a feature we never enable is a very different thing from one sitting on the path that faces the internet. We want projects that are actively maintained, not stranded in maintenance-only limbo ahead of a disruptive rewrite. And for the pieces we shape ourselves, we care how readily they bend — quick to theme, fast to iterate on.
Underneath all of this sits a question that is easy to skip and shouldn't be: who controls the software, and what could that control do? Every package is a standing trust relationship with whoever ships its updates — their code runs on our hardware, or through the desktop client on our users' machines, often with real privileges, every time it updates. So we treat provenance and governance as a first-class criterion, not a footnote. That weight only grows when the choice is one we make on someone else's behalf: a dependency we bundle into the client is a supply chain we are vouching for on behalf of everyone who installs it. Good governance has a recognizable shape: a broad contributor base rather than a single overcommitted maintainer; development that happens in the open; signed releases; and presence in the official repositories of the major Linux distributions, which means independent security teams have already vetted the package for inclusion. Best of all is a steward whose mission is aligned with the people who use the software. Certbot is one clean example, from the Electronic Frontier Foundation, a United States nonprofit whose entire reason for existing is users' security and privacy; deSEC, a German 🇩🇪 nonprofit dedicated to making DNSSEC free for everyone, is another. These are about as well-aligned as governance gets. No single criterion is decisive, though: strong governance is a heavy weight on the scale, not a trump card, and it did not by itself settle every choice below.
The inverse is just as legible. A lone maintainer with no structured way to report or disclose a vulnerability is a risk in itself; a security hole patched quietly, with no advisory, tells you there is no process behind the project even when the code is sound. Concentrated corporate control is its own kind of exposure — a single owner can relicense, change direction, be acquired, or simply develop interests that diverge from its users'. None of that is disqualifying on its own, but it is why we lean toward projects with distributed governance and a genuine possibility of forking if the stewardship ever goes sideways.
Because this software faces the internet, we also threat-model its supply chain the way any careful operator does, and that means acknowledging that control has a jurisdiction. Software substantially controlled by entities operating under states with both the capability and the demonstrated intent to compromise the infrastructure of Western democracies — Russia 🇷🇺 and China 🇨🇳 most prominently — is a supply-chain vector we avoid. That judgment follows ownership rather than letterhead: a nominally American company under such ownership or control inherits the same exposure. This is not a claim about people or places — capable, principled engineers work everywhere, and excellent software comes from everywhere. It is a claim about leverage, because the entity that controls the releases is the entity that could be compelled to ship something unwelcome in one.
For the same reason, the concern does not stop at adversaries. A project entangled too closely with any government — including friendly, Western ones 🇺🇸🇬🇧🇨🇦🇦🇺🇳🇿 — is exposed to a quieter version of the same lever: pressure to weaken a default, add an interface, or stay silent about it. Concentrated control is the hazard wherever it sits; independence is the property we are actually buying. The reassurance that open infrastructure offers here is structural rather than sentimental — development in the open, many eyes on the code, signed and reproducible builds, and the standing option to fork a project that drifts. That is the early-internet wager: that openness and distributed control outlast any single point of authority. It is the wager this whole deployment makes, and the one behind the choices that follow.
None of these properties is an absolute gate. They are weights, and each section below records how the scale tipped for one decision — what we chose, what we passed over, and why.
Distribution
Ubuntu Server 24.04 LTS. The host operating system is the foundation everything else runs on, and here we deliberately pick the Champion. The lifecycle framing above cuts both ways: most of the time the Challenger wins, but a server's base OS is exactly the layer where you want what everyone else runs. Ubuntu is the most widely deployed server Linux in the world, and that ubiquity pays off precisely as our methodology predicts — bugs surface and get fixed because millions of machines hit them first, security issues draw constant scrutiny we inherit for free, and any problem we run into already has years of threads and answers behind it.
There is a newer reason ubiquity matters, too. We increasingly build and operate this infrastructure alongside coding agents, and an agent is only as good as the material it was trained on. The most popular stack has the most training data by a wide margin, so an assistant's guidance on Ubuntu, systemd, or apt draws on an enormous body of real-world examples, where an esoteric distribution pulls from far thinner data and gives weaker, less reliable help. In an age of agentic operations, choosing the mainstream is also choosing the option your tools understand best.
Within Ubuntu we run the 24.04 LTS release rather than the newer 26.04, even though 26.04 is now out — the same instinct applied one level down. LTS releases get five years of standard support; 24.04's runs through April 2029, with Ubuntu Pro (free for personal use) extending security maintenance to 2036. A brand-new release is the upstart even inside a Champion distribution, so we let others find its early rough edges and run the proven, long-supported one beneath infrastructure meant to stay up for years.
Also considered. Debian is the closest call — rock-solid, minimal, and the base Ubuntu itself is built on; we would happily run it, and chose Ubuntu mainly for its larger ecosystem, more current hardware support, and an even deeper well of documentation and training data. The RHEL family and its rebuilds (Rocky, AlmaLinux) are excellent in the enterprise but lean on dnf and SELinux conventions that add friction for this kind of deployment without a matching payoff, and Fedora moves too fast for a set-and-forget server. Arch (rolling release) and NixOS (declarative, beloved by enthusiasts) are the upstarts here — powerful, but their churn and smaller communities are the wrong trade for a host that should be boring. Alpine is superb as a container base image, but its musl libc brings compatibility friction that isn't worth it for a general-purpose host.
Web Stack
In front of the tracker sits the reverse-proxy layer: it terminates TLS on port 443, routes each request to the right place, serves the dashboard's static files, and keeps the TLS certificates current. The first version did all of this with nginx and certbot — nginx as a capable general-purpose proxy and static host, certbot issuing and renewing Let's Encrypt certificates. Both cleared the bar comfortably, and neither was a mistake. We migrated anyway, for fit rather than fault.
The current stack is HAProxy for TLS termination and routing, Caddy for static hosting, and lego for certificates. The deciding factor was operational shape. HAProxy's runtime admin socket lets the circuit breaker push per-service changes to the live process every minute with no config reload — exactly the kind of fast-changing state we need to manage — and HAProxy terminates TLS faster under the handshake-heavy load a public tracker generates. Because HAProxy doesn't serve static files, Caddy sits behind it for the dashboard with a notably clean config, and lego replaced certbot as a lighter ACME client with tidier configuration. None of that is a knock on the originals; it is the intro's point made concrete — certbot's governance is exemplary, but governance is a weight, not a trump card, and here operational fit pointed elsewhere.
The earlier nginx-and-certbot setup still works, and the repository keeps it as a complete, frozen example for readers whose infrastructure suits it better. The durable part is the pattern — terminate TLS, route by path and protocol, gate per service — and the specific software that implements it is the replaceable part.
Containerization
Docker 🐳. Every internet-facing service in this deployment runs in a container, and as with the host OS, the base layer is where we pick the Champion. Docker is the default way the world packages and runs containers: the Dockerfile and Compose formats are what tutorials assume, what other projects ship, and what coding agents have seen most. The hardening this deployment leans on — dropped capabilities, read-only root filesystems, seccomp profiles, user namespaces, per-container resource limits — is all first-class and exhaustively documented in Docker, so the effort goes into applying the controls rather than discovering whether they exist.
Docker is also where our own caution about corporate control gets tested, and it is worth being precise. Docker, Inc. is a single commercial steward with a turbulent history: it sold off its enterprise business in 2019, and Docker Desktop's licensing changes have frustrated many teams. What keeps that from being a lock-in risk is open standards. The engine itself is open source (the Apache-licensed Moby project), images and the runtime follow the OCI specifications, and on a Linux server we run the open-source engine from the distribution's own repositories rather than the licensed Desktop product. If Docker the company drifted, our Dockerfiles, images, and Compose files would carry to a compatible runtime with little change — the Champion's gravity, with the open standard as a backstop.
Also considered. Podman is the strongest alternative — daemonless, rootless by default, and largely command-compatible with Docker — and it is the default in the RHEL world; we stayed on Docker for the larger ecosystem, smoother Compose story, and deeper documentation and agent familiarity, which matter more to us than Podman's daemonless design. LXC/LXD (now continued as Incus) offers system containers closer to lightweight virtual machines, a different abstraction than the single-process application containers we want. The lower-level runtimes that sit beneath Docker — containerd with nerdctl, or CRI-O — can be driven directly, but doing so trades away the conveniences for no payoff at our scale. And full orchestration like Kubernetes is simply the wrong size: it solves multi-node scheduling and fleet management we do not have, at a complexity cost no one should pay for a handful of containers on a single host.
Scripting Language
Python 🐍. The deployment needs small server-side scripts — the gauge that scrapes the trackers and writes page.json from inside a container, and the reachability probe that runs once a minute from cron on the bare-metal host. We write these in Python, which makes it the odd one out in a project that is otherwise JavaScript top to bottom — Vue in the browser, the same toolchain across the dashboard and the desktop client. On the server, in and out of containers, it is Python.
There are three realistic options for this kind of glue — the shell, Node, and Python — and Python sits in the sweet spot between the other two, for code that has to run unattended for years.
The shell (Bash) is always present and ideal for a few lines, but a script that grows past a screenful gets hard to read and harder to trust: quoting, error handling, and data structures all fight you, and these scripts do real work — walking a ring buffer, parsing metrics, retrying probes against multiple targets. Past a certain size, shell is a liability rather than a convenience.
Node is the engine our application code already runs on, and its event-driven, asynchronous model is genuinely wonderful for building applications. It is the wrong shape for a script that wakes once a minute, does one synchronous thing, and exits — and it carries two costs we felt directly. On bare metal it has to be installed, and any real Node script tends to pull in a tree of dependencies, potentially hundreds of transitive modules, each one supply-chain surface we would rather not stand up just to ping an address. In a container, V8's heap is the deeper problem: we first ran the gauge on Node 22, and over days and weeks its memory use crept steadily upward with no way for us to hold it down — exactly the slow, unattended drift you least want in a service meant to run for years. Rewriting the script in Python made the problem vanish.
Python is the middle path. Like the shell it is already there — Ubuntu ships python3, and our scripts are standard-library only, with no pip install and no dependency tree to audit or patch. Unlike Node it is synchronous by default and keeps a flat, predictable memory footprint, so a script reads top to bottom, is easy to audit, and stays stable over long unattended runs. For code whose whole job is to be boring and dependable, that is the trade we want — and it is why the server side of an otherwise JavaScript project speaks Python.
BitTorrent Tracker
Aquatic (Rust, Apache-2.0, by Joakim Frostegård). A public tracker that serves every kind of client needs three protocols: UDP (the default for desktop clients), HTTP (for clients behind restrictive firewalls), and WebSocket (the signaling path WebTorrent uses in the browser). Aquatic covers all three in a single Rust codebase, at production quality. It runs entirely in memory, uses io_uring on modern Linux, supports IPv4 and IPv6, and exposes Prometheus metrics. In production it powers some of the largest open trackers running today: explodie.org handles around 163,000 requests per second over UDP, and tracker.webtorrent.dev is among the most prominent public WebTorrent signaling servers. Two criteria settled it: it is written in a memory-safe language, which matters for software that parses raw packets from untrusted sources around the clock, and it has the headroom that means we won't outgrow it.
The trade-offs are real, and we took them with open eyes. Aquatic is largely a single-developer project with a few hundred stars, so its community is thin and its governance rests on one person — a maintenance risk we weighed against the quality of the code and the fact that it is open enough to fork. It runs as three separate binaries, there is no official container image, and there is no built-in stats dashboard; it builds from source, and metrics are wired up separately. Documenting exactly that is much of what the open.ftorrent.com guides are for.
Also considered. bittorrent-tracker (Node.js, MIT, from the WebTorrent project) is the canonical WebTorrent tracker and the simplest possible deployment: one process, all three protocols, a built-in stats page, running in a single install. It is the reference implementation that defines the WebTorrent signaling protocol. We passed on performance, since single-threaded Node tops out around 600 connections per second — ample for a modest tracker but not the ceiling we wanted — and the migration path to Aquatic is clean, as both speak the same protocols. opentracker (C, by erdgeist) is arguably the most battle-tested tracker in existence: eighteen years of development, v1.0 in 2024, chroots and drops privileges on startup, enormous throughput on minimal hardware. But it has no WebTorrent support and by design never will, which rules it out for serving browser peers; absent that requirement it would be the pick. Chihaya (Go, BSD-2) offers pluggable middleware and Prometheus metrics but lacks WebTorrent and has gone quiet. Torrust (Rust, AGPL-3.0, backed by Nautilus Cyberneering) is the most feature-rich — REST API, database persistence, private-tracker mode — but does not do WebTorrent yet. wt-tracker (TypeScript, by Novage) is fast, handling tens of thousands of secure WebSocket peers on a small VPS, but it is WebSocket-only and would still need a separate UDP/HTTP tracker beside it.
Mainline DHT Node
qBittorrent-nox (C++, GPL; created by Christophe Dumez in 2006, maintained since 2013 by the volunteer developer sledgehammer999). The DHT node is a separate piece of infrastructure from the tracker — the DHT bootstrap node guide explains why — and it gets its own selection. qBittorrent-nox is the headless build of qBittorrent, built on libtorrent-rasterbar, installable from the official package repositories, and configured here to run as a pure DHT participant with downloading, seeding, and the rest switched off.
What won it was observability. Its Web API returns the node's health — DHT routing-table size, connection status, transfer counters — as a single JSON object from one request. For a node whose entire job is to be a reachable, well-connected DHT participant, "how many nodes are in your routing table?" is the health check, and qBittorrent answers it in one call with no custom code. The network-facing code underneath is libtorrent-rasterbar, the most mature mainline-DHT implementation in the ecosystem and the engine behind many clients — the right thing to have on the one part of this that faces the open internet.
Provenance is a genuine strength. qBittorrent is packaged in the official repositories of Ubuntu, Debian, Fedora, Arch, and others, so independent distribution security teams have vetted it; its releases are signed; and the official container image is built from source under the project's own CI, with a compile-time software bill of materials. Governance is the honest weak spot, and worth stating plainly: it is a volunteer, effectively single-maintainer project with no formal vulnerability-disclosure process. qBittorrent has had a handful of CVEs over its twenty years, the most striking a flaw that silently ignored TLS certificate errors for fourteen years and was then fixed quietly, with no security advisory. That is exactly the governance red flag our methodology says to watch for. We accepted it for a specific reason: every one of those vulnerabilities lives in a code path this deployment never exercises. The download manager (nothing is downloaded), the external-program runner (disabled), and the Web UI's authentication surface (bound to an internal network, never internet-facing) are all dark here. The only thing exposed to the open internet is the DHT's UDP socket, handled by libtorrent's two-decade-old BEP 5 parser, and the container boundary limits the blast radius of anything that slips through. It is our methodology's security test in miniature: a flaw in a feature you never enable is not the same as one on the path that faces the internet.
The remaining trade-off is weight. A full torrent client carries features that go unused, which is more code than a purpose-built daemon; disabling everything but DHT, plus container isolation, keeps that contained.
Also considered. transmission-daemon is the leanest full client, DHT on by default, tiny footprint — genuinely viable. We passed because its RPC exposes no direct DHT node count, so health would be inferred indirectly rather than read in one call, undercutting the status surface that was the whole point. The rest of the field was purpose-built daemons and libraries. mldht (Java, by the8472) is the strongest pure-DHT implementation evaluated, with persistent routing tables and very high packet throughput, but it is a single-developer Java project with no web UI or container images, and nothing else in the stack runs a JVM; it would be the right tool only if the goal shifted to DHT analysis or indexing. anacrolix/dht (Go), pubky/mainline (Rust), and bittorrent-dht (Node) are libraries rather than daemons: choosing any of them means writing and maintaining a wrapper for process management and health reporting, custom code we would rather not own when a packaged client already provides it. The same objection rules out driving libtorrent directly through its Python bindings — the right engine without the unused client, but custom code to maintain and fiddly to package in a container. Deluge uses the same libtorrent DHT engine but adds a Python runtime for no advantage here, and a handful of older standalone daemons (nictuku/dht, equalitie/btdht, mwarning/dhtd) are too stale to trust for something meant to run for years.
Documentation Site
VitePress. This site is the requirement: Markdown in, static HTML out, served as plain files behind the reverse proxy, with our own typography (the Jura headings used across ftorrent) and the things technical docs need by default — syntax-highlighted code blocks with copy buttons, and fast client-side search. VitePress provides those out of the box, and two things settled it. It is built on Vite and Vue — the same toolchain as the tracker dashboard and the same framework as the desktop client's front end, one ecosystem to know across all three, with the immediate hot-reload that comes with it — and it bent to our branding on the first try, where dropping in the Jura font took a single line of CSS. Provenance is sound as well: it is maintained by the Vue team and is the engine behind Vue's own documentation.
Also considered. Starlight (on Astro) was the finalist and looks very close out of the box, but it resisted customization — the same font swap took three attempts and still wouldn't load — and its dev loop was less immediate. Docusaurus is capable but ships as a heavier React application, and fully client-side search needs a plugin rather than coming built in. Material for MkDocs has perhaps the best built-in search of the field, but it lives in the Python world rather than our JavaScript one, and its future is unsettled: MkDocs 1.x is in maintenance-only mode, 2.0 is still pre-release, and the Material team is building a separate successor, Zensical — exactly the uncertain-future signal our methodology says to avoid.
Desktop App Framework
Tauri. A desktop app with a web UI comes down to two real options: Electron or Tauri. Electron ships a full copy of Chromium and a Node.js runtime inside every app; Tauri uses the operating system's own webview with a small Rust core. That single architectural difference decides everything downstream. A minimal Tauri installer is a few megabytes, where the Electron equivalent runs around 85 MB and climbs past 100 MB once Chromium is bundled; when the API platform Hoppscotch migrated from Electron to Tauri, it went from a 165 MB download to 8 MB and cut memory use by roughly 70%. We ship less, use less memory at rest, and patch a smaller attack surface, because we are not bundling and updating an entire browser engine.
Tauri also matches the client's security needs: it serves only our own bundled assets rather than remote content, and its capabilities system grants every privileged operation explicitly — a deny-by-default line between the web UI and the Rust core that matters for software handling untrusted data from peers and torrents. The cost is that the UI renders on each platform's native webview instead of one pinned Chromium, so appearance can vary slightly across systems, a small price for the size, memory, and security it buys.
BitTorrent Engine
libtorrent. The client's ambition shapes this choice, and it is where the methodology stops being a checklist. The desktop client aims to be a hybrid — a full peer in both traditional BitTorrent swarms (TCP and uTP) and browser-based WebTorrent swarms (WebRTC), from a single engine with one shared piece store. That ambition narrows the field to almost nothing.
libtorrent-rasterbar, by Arvid Norberg, is the gold standard for BitTorrent itself: twenty years old, the engine inside qBittorrent and Deluge, carrying the whole modern protocol suite (DHT, PEX, magnet links, uTP, encryption, IPv6, web seeds) with a track record nothing else matches. Decisively, it is the only C/C++ engine with first-party WebTorrent support, which is what makes a single-engine hybrid possible at all.
Here is the tension, stated plainly. libtorrent is reference-grade for BitTorrent, but it is not the reference implementation of WebTorrent — that is the JavaScript webtorrent project, which defines the protocol. libtorrent's WebTorrent support is newer, experimental, and not yet in any stable release; to get it, we build libtorrent from its development branch with the feature enabled, which means carrying our own build pipeline and tracking upstream ourselves. So we are making a deliberate bet: take an experimental capability of an otherwise rock-solid engine in exchange for one unified codebase serving both worlds, rather than adopt the proven WebTorrent reference implementation and bolt a second, separate engine beside it for classic BitTorrent. A single mature engine with an immature edge struck us as the better risk than two engines and two piece stores stitched together — but it is a real bet, with real uncertainty, and worth naming as one.
The bridge to the engine is settled by necessity rather than taste. libtorrent's API is C++ built on Boost, impractical to call over Rust FFI; the one Rust binding is unmaintained and covers a sliver of the surface, and the Node bindings are long abandoned. libtorrent does maintain first-party Python bindings, built in the same step as the library and exposing nearly the whole API, WebTorrent features included. No equivalent exists for any other language, so Python here is not a preference but the only maintained, first-party path to the engine — which is why the client runs it as a sidecar process.
Also considered. The hybrid goal collapses the field before any feature-by-feature comparison begins: serving both WebRTC browser peers and TCP/uTP peers from one engine is something almost nothing else attempts. Pure-Rust engines such as librqbit are modern and clean but implement classic BitTorrent only, with no WebTorrent path. Transmission's own engine (its independent C implementation, not libtorrent) and general-purpose downloaders like aria2 are mature and lean, and a client could drive either as a sidecar CLI, but neither speaks WebTorrent — the same disqualifier. The one real alternative was architectural rather than another library: build the browser-peer half on the JavaScript webtorrent reference implementation and run a separate classic-BitTorrent engine beside it — the two-engine path weighed and set aside above. The list is shorter than the tracker and DHT roundups, and that brevity is the finding: the requirement does the eliminating.