A self-hosted torrent indexer. Search once, ask eight torrent sites at the same time, and keep every magnet link they hand back.
You type a term. Magnetissimo answers from its own index straight away, and at the same moment asks every source's own search for that term. Each site's answer is indexed as it lands, and the new rows slide into the page while you watch. The next person to search for the same thing gets it from the index.
Sites are asked when someone searches, so the index grows into a record of what you looked for.
- Eight sources: YTS, Nyaa.si, Sukebei, TorrentDownloads, AudioBookBay, The Pirate Bay, EZTV and OxTorrent.
- The BitTorrent DHT, if you want it. Turn on the DHT crawler and
Magnetissimo finds torrents on its own, all the time, straight from the
peer-to-peer network, file lists included. Watch them arrive on
/dht. See The DHT crawler. - Torznab categories. Every torrent is filed under the standard Newznab/Torznab category list that Sonarr, Radarr, Prowlarr and Jackett use, whatever the source called it.
- Search that looks inside torrents. Names, descriptions, and the file names of torrents the DHT found.
- Search that copes with real names.
spider-manfindsSpider.Man.2002,toy story 4matches the4, and a fragment of a Japanese title matches without any spaces to split on. Better-seeded copies rank first among near-identical names. - Search operators that no source has: exact text, exclusions, alternatives and wildcards, plus size, seeder and age limits. See Searching.
- Works without JavaScript. Searching, filtering, sorting and paging are all links and plain GET forms. With JavaScript on, results stream in live.
- A Torznab indexer for Sonarr, Radarr, Lidarr, Prowlarr and the book managers, and a JSON API for scripts.
- One SQLite database. To back it up, stop the app and copy the data folder.
This is the easiest way, and works on anything that runs Docker: a
homelab server, a NAS, or a Raspberry Pi (the image is built for amd64 and
arm64). Save this as compose.yaml:
services:
magnetissimo:
image: ghcr.io/sergiotapia/magnetissimo:latest
container_name: magnetissimo
restart: unless-stopped
ports:
- "4000:4000"
volumes:
- magnetissimo-data:/data
volumes:
magnetissimo-data:Then start it:
docker compose up -dOpen http://<your server>:4000. That's the whole setup. The database and
a generated secret key are kept in the magnetissimo-data volume, and every
start runs any pending migrations. To upgrade, run
docker compose pull && docker compose up -d.
To keep the data in a folder instead of a volume, mount the folder at
/data and run the container as the folder's owner:
user: "1000:1000"
volumes:
- ./data:/dataIf you've cloned the repository, the compose.yaml there builds the image
from source, so docker compose up -d works straight from the checkout. To upgrade a checkout, run
git pull && docker compose up -d --build.
:latest is the newest release. :edge follows every commit to master,
and :3, :3.1 or :3.1.1 pin a version.
A release is a self-contained directory with the Erlang runtime included. Build it on the same OS and CPU architecture as the server it will run on:
mise install # Elixir 1.20.4 / OTP 29.1.1
MIX_ENV=prod mix deps.get --only prod
MIX_ENV=prod mix assets.setup
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix releaseCopy _build/prod/rel/magnetissimo to the server and start it:
mkdir -p /var/lib/magnetissimo
DATABASE_PATH=/var/lib/magnetissimo/magnetissimo.db bin/serverbin/server runs it in the foreground, which is what a systemd unit or a
process supervisor wants. To run it in the background, start it as a daemon
with PHX_SERVER=true:
PHX_SERVER=true DATABASE_PATH=... bin/magnetissimo daemon
bin/magnetissimo remote # a console inside the running app
bin/magnetissimo stopStarting the app runs any pending migrations. bin/migrate runs them on
their own.
Only DATABASE_PATH is required, and the Docker image already sets it.
| Variable | Default | What it does |
|---|---|---|
DATABASE_PATH |
/data/magnetissimo.db in Docker |
The SQLite file. Its directory must exist and be writable. |
PORT |
4000 |
The HTTP port. |
SECRET_KEY_BASE |
generated | Signs cookies. When unset, a random one is generated on first start and saved as secret_key_base next to the database. |
PHX_HOST |
none | Your public hostname, if the app is behind an HTTPS reverse proxy. Leave it unset for plain http://<ip>:4000. |
POOL_SIZE |
5 |
Database connections. |
DHT_PORT |
any free port | The local UDP port the DHT crawler sends from. Nothing needs forwarding. |
Magnetissimo speaks plain HTTP. To serve it over HTTPS, put it behind a
reverse proxy such as Caddy, Traefik or nginx, and forward WebSockets, which
is what makes results stream in. The app answers on any host name or IP it is
reached at, and the live-update socket accepts pages served from that same
host. The proxy has to pass the Host header through and send
X-Forwarded-Proto, so the app knows the page was served over HTTPS. Caddy
and Traefik do both by default. In nginx, add:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";git clone https://github.com/sergiotapia/magnetissimo
cd magnetissimo
mise install
mix setup # dependencies, database, sources, assets
mix phx.serverThen open localhost:4000. In development the database starts with a few dozen demo torrents to look at. Search for something and the real sources start filling the index.
Type one or more words in the search box. Magnetissimo shows the torrents that have all of the words. You can also use these symbols:
| You type | You get |
|---|---|
dune part two |
Torrents with all three words. The words can be in any order and in any part of the name, the description or the file names. |
"[ToonsHub]" |
Torrents with this exact text, with the brackets and the spaces. Capital letters and accents have no effect. |
dune -cam |
Dune torrents, but not the ones with the word cam. The minus removes only the full word, so torrents with El Camino or HDCAM are still in the results. |
dune -*cam |
Dune torrents, but not the ones with cam in any word. This also removes HDCAM. |
dune 2160p|4k |
Dune torrents with 2160p or 4k. |
seinfeld s05e1* |
Seinfeld torrents with a word that starts with s05e1, for example s05e10 or s05e14. |
seinfeld s0*e14 |
Seinfeld torrents with a word like s01e14 or s05e14. A star in a word matches any characters. |
Some things to know:
- Quotes find the exact characters.
"part two"does not findDune.Part.Two, because a dot is not a space. To find it, type the words without quotes. - The bar joins only the two words next to it.
dune 2160p|4k remuxfinds torrents withdune, with2160por4k, and withremux. - A minus removes a result only when it is at the start of a word. In
spider-man, the dash is part of the word.
Refine, under the search box, gives more filters:
- Names only: look for the words in the torrent names only.
- Size: a smallest and a largest size, for example
700 MBor2 GB. A number with no unit is in GB. Sizes use binary units (1 GB is 1,024 MB), as the sites do. - Seeders: a smallest and a largest number of seeders.
- Age: newer than or older than, for example
12h,7d,2w,3moor1y. A number with no unit is in days.
The filters also work when the search box is empty. Then they filter the list of the latest torrents.
What the sites get. The torrent sites understand only plain words, so
Magnetissimo does not send them the symbols. It sends the words you typed,
without the minus words and the stars. For each | choice, it sends one more
search (dune 2160p and dune 4k), to a maximum of three searches.
Magnetissimo then applies the symbols to the results. When you change a filter
or a minus word, Magnetissimo does not ask the sites again.
Torznab and the JSON API. These searches use plain text only. A - or a
" is part of the title, not a symbol.
Every BitTorrent client is part of the DHT, the peer-to-peer network that
makes magnet links work. Open /dht (the DHT link in the header),
flip the switch, and Magnetissimo walks it in the background: it asks peers for samples of the torrents they know, fetches
each torrent's name and file list from the peers sharing it, files it under
a category, and estimates its seeders and leechers.
- The same page shows what it finds as it arrives: the latest hundred, with live seeder counts, and how hard the crawler is working. The switch and the rate are saved in the database and apply at once. It starts off.
- Searches find DHT torrents like any other, including by the names of the
files inside them. They stay out of "Fresh magnets", the Torznab RSS poll
and
/api/latest, which they would flood. - Nothing needs forwarding. The crawler is a read-only DHT node, so it works behind NAT and in Docker as is.
Your router. A crawler talks to thousands of different addresses, and
a home router keeps a table entry for each. Fill that table and everyone's
browsing stalls, whatever the bandwidth. The rate slider on /dht sets
how hard the crawler works. Each stop was measured on a home connection:
| Rate | Router table entries it adds | Bandwidth out / in | Torrents found |
|---|---|---|---|
gentle (default) |
about 600, at most ~900 | 10 / 25 kbit/s | ~900 an hour |
normal |
about 2,000, at most ~3,000 | 35 / 85 kbit/s | ~2,600 an hour |
max |
about 6,500, at most ~9,000 | 110 / 260 kbit/s | ~7,000 an hour |
gentle is safe on any router, and normal on most. max fills the
4,000-entry table of a cheap router, so keep it for a VPS, a VPN container
or a router with room for 16,000 connections or more. If browsing slows
down, slide it down or route the container through a VPN. docs/dht.md has the details and
the measurements.
Privacy. Fetching a torrent's file list is a real BitTorrent connection, so the peers in a swarm, and anyone watching it, see your IP. No content is downloaded. Use a VPN if that matters to you. The DHT holds everything people share. Adult torrents are filed under XXX, and a list of banned keywords keeps abuse material out of the index.
Magnetissimo is a Torznab indexer at /api. In each app, add an indexer of
type Torznab (in Prowlarr, Generic Torznab, which then syncs it to
every app it manages) with:
- URL:
http://<ip>:4000, the address the app reaches Magnetissimo on. - API path:
/api, the default. - API key: anything, or blank. There is no key to check. Listenarr won't save a blank one, so type any word.
- Categories: the defaults work. Asking for a subcategory includes torrents a site only filed under its parent, so Sonarr's default TV/SD and TV/HD also get everything EZTV files under TV.
| App | Searches with | Categories to pick |
|---|---|---|
| Sonarr | Title and S01E02, S01, or an air date |
TV/SD, TV/HD; anime under TV/Anime |
| Radarr | Title and year | Movies and its subcategories |
| Lidarr | Artist and album | Audio |
| Chaptarr, Bookshelf, Readarr | Author and title | Audio/Audiobook, Books/EBook |
| Listenarr | Title, author and series | 3030 (Audio/Audiobook) |
| LazyLibrarian | Author and title | found by name |
What to expect:
- A search takes 5 to 20 seconds. Every site is asked live, as in the web UI. The apps allow far longer.
- Searches are by text, not by IMDb or TVDB id. The sites only search by text, so Magnetissimo tells the apps it takes text, and they send the title.
- RSS syncs and the Test button never reach the sites. They are answered from the index, newest first. On a fresh install with nothing in the chosen categories, Test sees one placeholder item that cannot be grabbed, rather than failing.
- Big backfills go slowly on purpose. Each site gets at most two searches at a time, a second apart, however many apps are asking. A "search all missing" over a large library takes a while, and no site gets hammered.
| Endpoint | Returns |
|---|---|
GET /api/latest |
The 50 most recently published torrents in the index. |
GET /api/search/:term |
The best 50 matches for term. |
A search gives the sources up to twenty seconds to answer before it reads
the index. A term that was searched in the last fifteen minutes is answered
straight from the index. Both endpoints return {"data": [...]}, where each
torrent looks like this:
{
"id": 11673,
"name": "Dune Part Two (2024) [1080p] [WEBRip] 88",
"description": null,
"canonical_url": "https://thepiratebay.org/description.php?id=75005578",
"published_at": "2024-04-06T04:39:43.000000Z",
"seeders": 924,
"leechers": 147,
"size_in_bytes": 2968337547,
"magnet_hash": "2770fe270845674966e184be60ed1be0fe494f3a",
"magnet_url": "magnet:?xt=urn:btih:2770fe270845674966e184be60ed1be0fe494f3a&dn=Dune+Part+Two…&tr=…",
"category": "Movies/HD",
"category_id": 2040,
"source": "The Pirate Bay"
}category is the Torznab category name and category_id its number.
Phoenix 1.8 and LiveView 1.2 on Bandit, with Ecto on SQLite. Search runs
on two FTS5 indexes: one on trigrams for substrings in any script, and one on
whole words for short tokens like the 4 in toy story 4. All HTTP goes through
Req. The DHT crawler is plain OTP: :gen_udp,
:gen_tcp and its own bencode, with no dependency. The interface is hand-written Tailwind. Its
look, a corporate Y2K glass atrium, is described in
docs/design.md.
Read CONTRIBUTING.md. It covers setup, the checks every commit has to pass, how to add a new source, and the design decisions behind the code.
