Swarm Switch is a reversible public recovery session for a private torrent in TorrentNG's native engine. Use it to finish a slow download, repair missing pieces, or keep seeding when a private tracker has retired a torrent. It is available in the WebUI detail panel and Properties → Swarm.
- Open Swarm Switch on a private torrent. Choose a generated counterpart or
provide a known public
.torrent. - Select Compare swarms. Review the exact original and candidate hashes, tracker reports, DHT observations, timestamps and reachable-peer sample.
- Select Use public swarm. TorrentNG parks the private torrent, creates a linked public torrent at the same location, and checks the existing data. The public identity downloads missing pieces or seeds complete data.
- Refresh Compare swarms, then select Restore private swarm from either linked torrent. TorrentNG stops public transfers, removes the public session metadata, verifies the files against the retained original, then resumes private downloading/seeding.
- Alternatively, Delete retained private copy keeps the public torrent and payload files but permanently removes this session's restore action.
Both entries remain visible while linked: the original is parked and the public entry shows actual transfer progress and its own upload/download counters. This keeps automation that addresses the original infohash honest; starting the parked original is rejected with an instruction to use Restore private swarm. Standard file-preserving deletion of the original through any compatibility API also makes the public torrent independent. Deleting shared payload data, deleting the public entry, or moving/renaming shared files requires restoring first.
A torrent that was originally added as public has no private copy and no restore
action. TorrentNG does not invent a private identity, tracker URL, passkey, ratio,
or download history. Re-adding an original .torrent is a separate user action.
| Your goal | What to look for | What the result means |
|---|---|---|
| Finish a slow private download | A public candidate with reachable peers or observed seeds | The public session can request missing pieces only after TorrentNG proves the candidate matches the existing payload. |
| Keep seeding after a tracker retires the torrent | A compatible public candidate and the retained private entry | Leave the private entry in place. It stays parked and can be restored after a fresh file check. |
| Decide whether a candidate is worth trying | Compare tracker reports, discovered peers, estimates, sample reachability and timestamps | These are observations at one point in time. A count of zero or an unknown result does not prove that no matching peers exist. |
| Find a public counterpart when generated candidates have no activity | Supply the exact public .torrent from a known release |
Swarm Switch compares that infohash. It does not search by title or locate unrelated releases. |
Generated candidates only alter the private/source fields of the existing metainfo, so they can find peers only when another client uses that exact resulting infohash. Public trackers or releases may package the same bytes under a different name, file order, piece length, padding, or other info fields. A different v1 piece size cannot safely share a partial download through this workflow. See Identity and compatibility for the matching rules and the safe alternative.
Treat Use public swarm as a public-sharing decision. It activates a distinct public infohash and exposes the candidate's info metadata and your peer address. Restoring private participation does not retract information already shared. The original private hash is not announced to DHT during comparison or recovery.
The infohash covers the bencoded info dictionary. The private flag is inside
that dictionary. Changing it creates a different swarm, even when the files and
piece hashes are identical. Turning on DHT for the original private hash would
not find an independently packaged public torrent and would mix discovery
policies. Swarm Switch never does that.
Generated candidates preserve the content layout and offer four explicit forms:
| Candidate | Info changes |
|---|---|
| Remove private flag | Remove private |
| Set private flag to zero | Replace private=1 with private=0 |
| Remove private flag and tracker source | Remove private and source |
| Zero private flag and remove tracker source | Set private=0, remove source |
There is no universally correct public hash. The actual public release might use other names, file ordering, piece sizes, padding, or publisher fields. The comparison probes the selected exact candidate; it does not promise content search or join unrelated hashes. No peers found is a useful observation, not a proof that no compatible public release exists. A user can choose to seed an empty public counterpart intentionally.
For an uploaded public torrent, the exact info bytes are retained. All candidates
are packaged with only info and authenticated BEP 52 piece layers: private
tracker URLs, webseed URLs, DHT node lists, comments and other outer metadata are
excluded. Other info fields remain part of the selected identity and become
public with it. The UI states this before activation. Restoration stops public
participation; it cannot retract metadata or addresses already shared.
Shared write access requires a proof of identical content:
- V1: identical file paths, lengths, order, offsets and padding; identical piece length and SHA-1 piece hashes.
- V2: identical file paths, lengths, padding and authenticated per-file Merkle roots. Different v2 piece lengths are allowed because those roots identify the same bytes independently of piece length.
- Hybrid: both validations must pass. Cross-version substitutions are rejected.
- Renamed/remapped session files are rejected until the original layout is restored. Same names and sizes alone are insufficient.
- Pending magnets cannot switch until complete metainfo exists. Candidate input is capped at 8 MiB, with parser allocations charged to the metadata governor.
A v1 public release with different piece sizes cannot safely write into a partial private download through this feature. Use a separately downloaded public release and ordinary file verification/import for that case. This avoids overwriting verified private pieces with content that has only a matching filename or size.
Comparison is explicitly requested and does not announce either candidate as a new participant. The original private hash is sent only to its existing trackers. Public queries use only the candidate public hash. Tracker credentials stay on the server; responses show tracker origins with paths, queries and userinfo removed.
| Display | Meaning |
|---|---|
| Private seeds / leechers | Individual tracker HTTP or UDP scrape report |
| Private last known report | Stored announce/scrape counts when a fresh scrape fails; timestamp retained |
| Public discovered peers | Unique allowed endpoints returned by the bounded DHT lookup |
| Public estimated seeds / peers | BEP 33 bloom-filter estimates, only when provided by responding nodes |
| Reachable in sample | Successful matching-infohash TCP handshakes from up to 12 discovered peers |
| Observed seeds | Sampled peers advertising all pieces through bitfield/HaveAll; a lower bound |
| Unknown | Unsupported, unavailable, saturated estimate, timeout, or no usable report; never converted to zero |
DHT discovery is not a swarm census. Bloom filters count IP addresses and are estimates; NAT, stale announcements, unsupported extensions and incomplete DHT coverage affect them. The peer sample requests no blocks and uploads no payload. An unreachable or unclassified peer is not asserted to be a leecher. Encrypted- only and uTP-only peers may not answer the TCP sample. Private tracker counts are shown individually, never summed across overlapping populations.
Probes have separate concurrency limits (two comparisons, three DHT queries per lookup, four sampled peers), 64 queried nodes, 256 returned peers, 8 bootstrap hosts and an eight-second DHT deadline. Each peer sample has a two-second deadline. Tracker scrapes are bounded to eight tracker rows and five seconds each. They share configured outbound address restrictions, DNS admission and peer bans. Hybrid comparisons use the v1 discovery hash, matching the engine’s DHT registration. Pure v2 uses its truncated SHA-256 discovery key while retaining the full v2 session identity.
A comparison does not require globally enabling DHT; activation does. The UI requires refreshing a comparison older than one minute before switching in either direction. Interrupted transitions can always be restored without a comparison.
The original .torrent, tracker overrides, labels, original file choices and
accounting remain authoritative. Public transfer totals are independent. Public
activation copies file selection and transfer limits; it never attributes public
traffic to the private tracker. The new actor runs the existing data-durability
and completion gates.
The engine owns ordering; UI/API calls cannot independently pause, copy and
resume around concurrent commands. A schema-12 swarm_switches journal reserves
both identities before switching:
| Phase | Network/storage ownership | Recovery |
|---|---|---|
| Preparing | Original is drained; public remains paused until setup commits | Park both; allow explicit private restore |
| Public | Private is parked; public is the sole active identity | Restore public's persisted lifecycle; retain private fence |
| Restoring | Drain public and invalidate private fastresume | Park both; allow retry |
| No relation | Ordinary independent torrent lifecycle | Normal engine recovery |
The durable relation is installed before quiescence. The old actor drains writes, closes peers, sends its stopped event and is joined before a replacement starts. DHT registrations are removed during retirement. A failed drain never starts the other actor. Pending promotions are cancelled; inactive identities cannot be promoted by incoming peers, trackers, compatibility APIs or queue control.
On restore, deleting the public DB row, removing the link and setting the original to Checking are one transaction. Original fastresume is invalidated and the fresh actor receives Checking even if an older checkpoint survives a filesystem crash. The retained private artifact is validated before retiring the public session; a missing or corrupt file leaves the public session intact. The original cannot seed based on its old bitmap. Existing crash-safety barriers still govern completed/seeding claims.
Interrupted switch records are recovered before actors load. Stable public sessions retain their lifecycle state; every original stays parked. An interrupted transition parks both and remains visible for an explicit retry. No automatic fallback publishes a private hash to DHT.
This changes the native DB schema to 12. Older binaries reject the newer schema
instead of ignoring exclusive-ownership records. Use a pre-upgrade database
backup when downgrading binaries; do not manually decrement user_version.
All routes are under /api/v1/torrents/{hash}/swarm-switch:
| Method and suffix | Result |
|---|---|
| GET | Current relationship, restore availability, DHT state and active progress |
POST /preview |
Validate candidate and compare both swarms without switching |
POST /public |
Activate the explicitly expected candidate hash |
POST /restore |
Restore the retained private identity with verification |
Preview/public bodies accept variant and optional public_torrent_base64.
variant=supplied requires the file; generated variants reject an uploaded file.
Public activation additionally requires expected_public_hash from the preview.
Unknown JSON fields are rejected. Existing authentication, token permissions,
CSRF and request-body limits apply; comparison requires mutation authorization
because it initiates network probes. GET is read-only.
Repeated activation of the same active candidate is idempotent. A different candidate requires restore first. Restore through the original identity is idempotent after success. Restoring an independent public torrent fails explicitly.
The WebUI Bridge forwards these routes when connected to the TorrentNG engine. Other backend types report this capability as unavailable.
The feature is capability-gated with supports_swarm_switch. Third-party engines
must not expose a raw private-flag override in its place: they do not participate
in the native exclusivity, recovery or accounting contract.
Deterministic tests cover candidate identity and credential stripping, v1/v2/ hybrid content compatibility, unknown-versus-zero counts, authenticated probe responses, bloom estimates, journal recovery, private-copy deletion, shared-file fences, lifecycle reversal, and UI/API availability. Local fixtures should supply all content and discovery; no user's tracker credentials or real private torrent are needed. Live DHT results remain observations of the network at the displayed time, not a guaranteed performance improvement.
Protocol references: BEP 3, BEP 27, BEP 52, BEP 5, BEP 15, BEP 33.
- Native workspace: 2,423 tests passed with two existing process-inspection
tests excluded after confirming this runner cannot see its own PID through
their process probe. The excluded tests are
source_state::tests::an_explicit_lock_held_by_a_live_process_means_runningandsource_state::tests::this_process_is_seen_running_with_its_own_name. - Six runtime lifecycle tests cover actual public payload transfer and restart, private restoration, v2/hybrid verification, interrupted phases, private-copy deletion, missing original metainfo, standalone public torrents, and stale candidate rejection. Local protocol fixtures cover DHT transaction validation, zero/unknown observations, peer availability and authenticated tracker scrapes.
- Native workspace Clippy and full-compatibility daemon Clippy pass with warnings denied. Full-compatibility daemon tests: 32 passed.
- Bridge feature tests and all 122 API integration tests pass; Bridge Clippy
passes. Five existing rTorrent Unix-socket tests cannot bind sockets in this
runner (
Operation not permitted); their source is unchanged. - WebUI: 57 unit tests, lint and production build pass. The browser workspace checks pass on desktop and mobile (10 passed, four existing viewport-specific skips), including compare → public → compare → private. Candidate controls are checked against viewport bounds. Browser validation used Chromium 134 because the configured browser download was unavailable in this runner.
- OpenAPI, 61 repository Python tests, formatting, and the commit security scan pass; Semgrep reported zero findings across the changed source files.
These are fixture-based checks. They do not claim that a particular public counterpart exists or that a live tracker will permit scraping. The seven runner-limited checks above still need a host that supports their OS primitives.