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.
Features
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.
Interface
Screenshots
React 19 + TypeScript UI embedded in the Go binary. Live queue updates over WebSocket.
Technical
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.
Install
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 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.
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
HAUL_SERVER_PORT | 8484 | Web UI and API port |
HAUL_TORRENT_LISTEN_PORT | 6881 | Peer-wire listen port |
HAUL_TORRENT_DOWNLOADS_PATH | /downloads | Default save path |
HAUL_DATABASE_DSN | — | Postgres DSN (required) |
HAUL_AUTH_API_KEY | auto | API key; autogenerated on first run if unset |
HAUL_PULSE_URL | — | Pulse control-plane URL (optional) |
HAUL_TORRENT_RENAME_ON_COMPLETE | false | Rename completed downloads using media metadata |
HAUL_TORRENT_PAUSE_ON_COMPLETE | false | Pause torrents on finish (for ratio-sensitive trackers) |
HAUL_TORRENT_STALL_TIMEOUT | 120 | Seconds of inactivity before a torrent is classified stalled |