Search by

marque / hound

lomsoftware

Public BitTorrent tracker (announce/scrape) for Marque platform

v4.0.0 2026-10-06 04:19 UTC

This package is auto-updated.

Last update: 2026-10-06 04:35:44 UTC


README

Public BitTorrent tracker for the Marque platform. Open announce and scrape — no accounts, no announce keys, no ratio.

Hound is the deliberately small half of the tracker pair. Where marque/bloodhound knows who every peer is and holds them to a ratio, hound knows nothing about anybody. That is not a missing feature — a public tracker has no accountable user, so most of what bloodhound does has nothing to attach itself to.

Starting from scratch?

Hound is the tracker half only — no frontend, no upload UI. For a complete public tracker, install the set:

composer require marque/trove marque/hound marque/disguise

That resolves marque/threepio and marque/deck for you.

Running a private tracker with accounts and ratio instead? Use marque/bloodhound and marque/guise in place of hound and disguise.

⚠️ Never install hound alongside bloodhound. Hound registers an open announce route with no key and no authentication. A private tracker that merely has hound in vendor/ is carrying a keyless announce endpoint beside its authenticated one, and anyone who finds it can transfer without ever touching ratio accounting. Pick one tracker package.

Installation

Requires marque/trove and marque/threepio, which supplies the protocol primitives and Redis peer storage.

composer require marque/hound

Publish the config:

php artisan vendor:publish --tag=hound-config
php artisan vendor:publish --tag=threepio-config

Hound ships no migrations of its own — it reads and writes trove's torrents table and keeps live peers in Redis.

Endpoints

Endpoint Name Purpose
GET /announce tracker.announce Peer announces (started, completed, stopped)
GET /scrape tracker.scrape Swarm statistics

No announce key in the URL. That is the whole difference from bloodhound, whose routes are /announce/{announce_key}. Anyone holding the .torrent can announce.

The paths are configurable, for migrations — a public tracker moving onto Marque cannot change the URL its circulating .torrent files announce to:

HOUND_ANNOUNCE_PATH=announce.php
HOUND_SCRAPE_PATH=scrape.php

Route names stay tracker.announce and tracker.scrape whatever you set. There are no key options here — hound is keyless by design; if you need announce keys you want bloodhound, which has the full set.

Both routes run outside the web middleware group — no session, no CSRF, no cookies — and behind threepio's BlockBrowsers middleware, which rejects anything that looks like a web browser rather than a BitTorrent client.

Announce flow

  1. Required parameters validated (info_hash, peer_id, port, uploaded, downloaded, left)
  2. Torrent looked up by info_hash — an unregistered hash is refused
  3. Port checked against threepio's blacklist
  4. IP peer count checked against the limit
  5. Peer upserted in Redis with no user (userId: null), or removed on a stopped announce
  6. Swarm counts projected onto the torrents row when they've changed
  7. Bencoded peer list returned

There is no user lookup, no anti-cheat pass, no byte accounting and no ledger. A public announce touches the database only to identify the torrent, to increment times_completed on a completed event, and, when the swarm has actually moved, to update its counts.

Scrape

Accepts one or more info_hash parameters and returns complete / downloaded / incomplete per torrent. Capped at 50 hashes per request; extras are dropped rather than erroring. Unknown hashes are silently omitted from the response, per convention.

What hound does not do

Worth stating plainly, because the absences are design decisions rather than gaps:

Not here Why
Announce keys / user identity Public tracker; peers are anonymous by definition
Ratio, upload/download accounting Nothing to attribute bytes to
The announce ledger See bloodhound's README — it is a private-tracker feature
Anti-cheat, client whitelisting Cheating presumes a user with something to gain
Snatch records No user to record a snatch against

times_completed means something different here

Hound increments times_completed on every completed event it sees. It is a blind increment, and it has to be: with no user attached to an announce, a client restarting mid-download is indistinguishable from a second person finishing.

So on a public tracker the number means "completed events seen", not "distinct completions". Bloodhound's figure for the same column means the latter. The two are not comparable across the packages — that is the honest best a tracker with no accountable user can do.

Swarm counts on the torrent row

Live peers are in Redis, but a catalogue needs to filter and sort on swarm state — "hide dead torrents", "sort by seeders" — and SQL cannot query Redis. So each announce also writes seeders and leechers onto the torrents row, skipping the write entirely when neither has changed (which is most announces).

A stopped announce removes the peer, so a client that leaves properly is counted out straight away. A peer that vanishes without sending stopped (client killed, machine off) is caught by hound:sync-swarm-counts, which hound schedules hourly. It sweeps each torrent's expired peers out of Redis and writes the settled counts back to the row. That needs Laravel's scheduler running. A vanished peer counts as expired once peer_expiry has passed since its last announce (an hour by default), and the next hourly sweep removes it. So a torrent whose swarm vanished keeps its last-known counts for up to about two hours. The scheduler is a hard requirement: only the sweep removes a peer that vanished without saying stopped, so without it the counts never fall. A missed run only delays the correction.

php artisan hound:sync-swarm-counts

Configuration

Hound's own config is deliberately thin — the shared protocol settings (intervals, peer expiry, Redis, port blacklist, response format) live in threepio.

Published to config/hound.php:

Uploads: the private flag

Key Default Description
uploads.private_flag disallow disallow, require, warn_if_private, warn_if_public or allow

Hound binds trove's TorrentFilePolicyInterface. With disallow, a torrent carrying the private flag is refused, and the uploader is told to recreate it with "private" unticked: the flag stops clients using DHT and peer exchange, which a public swarm relies on. An unrecognised value is treated as disallow.

Downloads, for guests and members alike, get hound's own announce URL and a comment linking the torrent's page, around the untouched info dictionary. Everything else the uploader's client wrote, announce-list included, is dropped.

IP limiting

The primary abuse prevention for a public tracker, and with no accounts it is close to the only one available.

Key Default Description
ip_limiting.enabled true Enable the per-IP peer cap
ip_limiting.max_per_ip 50 Max distinct peer IDs from one IP
HOUND_IP_LIMITING=true
HOUND_MAX_PER_IP=50

At the cap, every announce from that IP gets a bencoded Too many connections from your IP failure, including re-announces from peers already in the swarm and stopped (so those peers leave only when they expire). The count is of distinct peer IDs across all torrents, so a client that uses one peer ID for every torrent counts once. Raise it if you expect legitimate NAT'd or institutional traffic. Before threepio 3.2.1, dead peers could stay in the IP count for good, locking a busy IP out. Hound accepts any threepio 3.x, so make sure you're on 3.2.1 or later for the fix (#10804).

Logging — planned, not yet built

These keys ship in config/hound.php but nothing reads them yet. Setting them does nothing today (#10809).

Key Default Description
logging.enabled false Announce logging, once built
logging.channel stack Laravel log channel, once built
HOUND_LOGGING=false
HOUND_LOG_CHANNEL=stack

This is Laravel logging, not bloodhound's announce ledger — there is no queryable history table and no reconciliation. Off by default because announce volume makes for large logs fast.

Inherited from threepio

Set these in config/threepio.php, not here:

Key Default Description
announce_interval 1800 Seconds between announces (sent to clients)
min_announce_interval 300 Minimum allowed interval
peer_expiry 3600 Seconds before inactive peers are dropped
max_peers_per_announce 50 Ceiling on peers returned, regardless of numwant
peer_response_format auto auto, compact, or dictionary
blacklisted_ports (see config) Direct Connect, Kazaa, eMule, Gnutella, WinMX, legacy BT range
redis.connection default Laravel Redis connection name
redis.prefix marque: Key namespace

Behind a proxy

Hound rejects private and reserved IP ranges in production only — under any other environment they pass, so local and staging work without special handling.

If you run behind Cloudflare, a load balancer or any reverse proxy, configure Laravel's trusted proxies before going live. Without it every peer registers with your proxy's address, which collapses them into one IP and trips the IP limit immediately.

Requirements

License

MIT. See LICENSE.