BitTorrent client

Haul

A self-hosted BitTorrent client built on anacrolix/torrent. Accurate ETAs, three-level stall classification, rename-on-complete using media metadata from Pilot or Prism, and VPN awareness out of the box.

Port :8484 Go 1.25 Postgres anacrolix/torrent MIT

What Haul does

Accurate ETAs

Rates and time-remaining are computed from a short exponential moving average (5-second time constant) rather than cumulative totals. Numbers track reality instead of flickering. Gaps over 30 seconds reset the tracker to avoid stale extrapolation.

Three-level stall detection

Dead torrents are classified at three levels: no_peers_ever (never saw a peer), activity_lost (was flowing, gone silent), and complete_but_no_activity. Published to /api/v1/stalls for Pilot's stallwatcher to blocklist automatically.

Rename-on-complete

When Pilot or Prism grab a torrent and pass through media metadata, Haul renames the output into Show/Season 02/Show - S02E05.mkv format automatically on completion. No post-processing scripts required.

VPN awareness

Haul detects whether it's running inside a VPN tunnel and surfaces the external IP in the dashboard — useful for catching VPN drops before they affect your downloads.

Modern React UI

Live-updated over WebSocket — no polling, no stale progress bars. Categories and tags with per-category save paths. Sequential download mode for streaming before the torrent finishes. First-and-last-piece priority for media players.

Webhooks and REST API

Webhooks filtered by event type (added, completed, stalled, speed update). Full REST API at /api/v1/ with OpenAPI docs at /api/docs. WebSocket event stream at /ws. Per-torrent and global rate limits.

Screenshots

React 19 + TypeScript UI embedded in the Go binary. Live queue updates over WebSocket.

Dashboard — active torrents with live ETAs
VPN status panel — external IP and tunnel state
Settings — stall timeout, rename, rate limits

Advanced features

Rate tracker

anacrolix/torrent exposes cumulative byte counters, not rates. Haul samples those counters on each API request and pushes them through an exponential moving average with a 5-second time constant. The math lives in internal/core/torrent/session.go.

Stall classification

Three-level state machine in internal/core/torrent/stall.go. Level 1: no_peers_ever after the grace period. Level 2: activity_lost past the stall timeout. Level 3: complete_but_no_activity. Anything above level 1 appears on /api/v1/stalls.

Pilot / Prism integration

Pilot and Prism POST to /api/v1/torrents when they grab a release, passing through media metadata so Haul can rename on completion. Haul fires webhooks on completion and publishes stall events to /api/v1/stalls, which Pilot polls to blocklist dead torrents automatically.

Per-category save paths

Categories support path templates with metadata variables. When a torrent arrives with media metadata from Pilot or Prism, the category's path template is expanded before the download starts. Combine with rename-on-complete for fully automatic library filing.

Getting started with Haul

Haul is designed to run behind a VPN container. See beacon-stack/stack for the full compose setup with VPN.

docker
docker run -d \
  --name haul \
  -p 8484:8484 \
  -v /path/to/config:/config \
  -v /path/to/downloads:/downloads \
  ghcr.io/beacon-stack/haul:latest

Open http://localhost:8484 — Haul generates an API key on first run. Find it in Settings → System.

Environment variables

VariableDefaultDescription
HAUL_SERVER_PORT8484Web UI and API port
HAUL_TORRENT_LISTEN_PORT6881Peer-wire listen port
HAUL_TORRENT_DOWNLOADS_PATH/downloadsDefault save path
HAUL_DATABASE_DSNPostgres DSN (required)
HAUL_AUTH_API_KEYautoAPI key; autogenerated on first run if unset
HAUL_PULSE_URLPulse control-plane URL (optional)
HAUL_TORRENT_RENAME_ON_COMPLETEfalseRename completed downloads using media metadata
HAUL_TORRENT_PAUSE_ON_COMPLETEfalsePause torrents on finish (for ratio-sensitive trackers)
HAUL_TORRENT_STALL_TIMEOUT120Seconds of inactivity before a torrent is classified stalled