seed a path: make a torrent of files you already have, and share it - #184
Merged
Conversation
Every way into torlink starts from a torrent somebody else made — search finds
one, the watch folder is handed one, the API is posted one. None of that helps
when the content is yours and no torrent for it exists yet, which is the one
thing the client could not do.
torlnk seed ./album
Hashes the files, writes album.torrent beside them, prints the magnet, and
starts seeding.
The interesting part is not the creating, it is what gets added afterwards.
addInput turns a .torrent into a magnet and hands the queue that, and
startEngine passed item.magnet to webtorrent unconditionally — so the engine
had no piece hashes and could not verify a byte until the swarm sent metadata
back. For a torrent created from local content that swarm is empty by
definition: nobody has ever seen the info hash. It sat at zero per cent
forever, waiting on peers for data already on the disk underneath it. I watched
it do exactly that before fixing it.
The queue already knew the answer. startSeeding has always preferred the stored
.torrent over the magnet — "verifies the local file immediately, no swarm
needed" — for restoring a seed across a restart. startEngine now does the same,
which is a one-line change that follows an existing convention rather than
inventing one, and it helps every re-add of something already downloaded, not
just this feature. So `seed` writes the metadata to the store before adding,
and the engine verifies locally and completes at once.
serve gained the same flexibility on the way in: POST /add now takes an
uploaded .torrent as well as a magnet, base64 or a data: URI as FileReader
hands it over. A .torrent is tried first because it is strictly more
information. The bytes travel in the request rather than a path to them — the
existing refusal to let a network caller name a local file is worth keeping,
and "add this torrent" must not double as "read this file off your disk".
Verified by running it, not only by the suite: seeding a directory of real
files produced seeds.json with status "seeding" and an empty download queue.
Before the metadata fix the same run left a queue entry at downloading /
progress 0.
npm run typecheck clean, npm test 372 passing.
baairon
added a commit
that referenced
this pull request
Aug 29, 2026
Follow-up to #184. runSeed returned as soon as the torrent was added, so the process stayed alive only because webtorrent's handles did and queue.suspend() never ran on shutdown. watch and serve both hold themselves open and suspend on both signals; seed now does the same, and the CLI exits once that flush returns. POST /add accepts a base64 .torrent, which is one 20-byte hash per piece and grows by a third again in base64. The 64KB body cap was sized for magnets, so it fit every torrent small enough to test with and answered 413 on a large multi-file release. startEngine prefers the stored .torrent, which does not carry the announce URLs that mergeMagnetTrackers folds onto a row assembled from several sources. Metadata is saved as soon as it arrives, so resuming a partial download dropped them; the magnet's trackers are now passed as announce regardless of which source wins. The new path tests asserted POSIX literals, which resolve drive-qualified on Windows and failed the suite there. Also plainer seed wording in the README and --help, and readParsed re-indented after its extraction.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every way into torlink starts from a torrent somebody else made — search finds one, the watch folder is handed one, the API is posted one. None of that helps when the content is yours and no torrent for it exists yet, and that was the one thing the client could not do.
Hashes the files, writes
album.torrentbeside them, prints the magnet, and starts seeding. Takes--seed-time,--delete-filesand--daemonlike the other headless modes.The interesting part isn't the creating
It's what gets added afterwards, and it's a bug I only found by running the thing.
addInputturns a.torrentinto a magnet and hands the queue that, andstartEnginepasseditem.magnetto webtorrent unconditionally. So the engine had no piece hashes and could not verify a byte until the swarm sent metadata back — and for a torrent created from local content that swarm is empty by definition, because nobody has ever seen the info hash. It sat at zero per cent forever, waiting on peers for data already on the disk underneath it.The queue already knew the answer.
startSeedinghas always preferred the stored.torrentover the magnet — "verifies the local file immediately, no swarm needed" — for restoring a seed across a restart.startEnginenow does the same:One line, following a convention already in the file rather than inventing one, and it helps every re-add of something already downloaded — not just this feature.
seedwrites the metadata to the store before adding, so the engine verifies locally and completes at once.servetakes a .torrent tooSame flexibility on the way in:
POST /addnow accepts an uploaded.torrentas well as a magnet, base64 or adata:URI asFileReaderhands it over.A
.torrentis tried first because it is strictly more information. The bytes travel in the request rather than a path to them: the existing refusal to let a network caller name a local file (allowTorrentPath) is worth keeping, and "add this torrent" must not double as "read this file off your disk".Buffer.from(..., "base64")ignores what it cannot decode instead of throwing, so the leading bencodedis what turns garbage into a 400 rather than a short buffer.Verified by running it
Not only by the suite. Seeding a directory of real files produced
seeds.jsonwithstatus: "seeding"and an empty download queue. Before the metadata fix, the same run left a queue entry atdownloading / progress 0— that's the failure this fixes, observed directly.npm run typecheck— cleannpm test— 372 passing (11 new)Notes
create-torrentmoves from a transitive dependency of webtorrent to a direct one, with a local.d.tsmatching howparse-torrentandwebtorrentare already declared here.album/album, finds nothing, and downloads a second copy beside the first. That calculation has its own test.startEngineline into its own PR if you'd rather take that separately — it stands on its own as a fix.