haul

module
v0.0.0-...-945563d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: MIT

README

Haul

A self-hosted BitTorrent client for home servers and the Beacon media stack.

License Go 1.25

Website · Bug Reports


Haul is a BitTorrent client with a React web UI and a REST API. Run it on its own as a modern qBittorrent / Transmission alternative, or alongside Pulse, Pilot, and Prism — the Beacon Stack — where it picks up rename-on-complete, stall blocklisting, and the rest of the integrated *arr pipeline. It's built on anacrolix/torrent, runs as a single Go binary, stores state in Postgres, and is configured from the UI or through environment variables.

Is this for you?

Haul is built to be approachable by default and capable when you want it to be. The out-of-the-box defaults are tuned so you can docker run it, open the UI, and be downloading inside of a minute — sensible save paths, a working rate tracker, stall detection, and VPN awareness all on from the start. The deeper features are there too: full REST and WebSocket APIs, configurable stall thresholds, per-category save-path templating, webhook event routing, sequential download and piece-priority modes, custom rename formats. They stay out of your way until you turn them on.

You'll probably like Haul if you:

  • Run a homelab and want a torrent client with a modern web UI that doesn't look dated
  • Use or plan to use Pilot or Prism for TV and movie management
  • Want accurate ETAs and reliable dead-torrent handling without manually babysitting grabs
  • Appreciate sensible defaults now and the option to grow into advanced features later

Features

  • Modern React UI, live-updated over WebSocket — no polling, no stale progress bars
  • Accurate ETAs. Rates and time-remaining are computed from a short moving average rather than cumulative totals, so numbers track reality instead of flickering
  • Categories and tags with per-category save paths and tag-based filtering
  • Sequential download mode for streaming before the torrent finishes
  • First-and-last-piece priority for media players that peek at file headers
  • Rename-on-complete — when Pilot or Prism grab a torrent and pass through metadata, Haul renames the output into Show/Season 02/Show - S02E05.mkv format automatically
  • Stall detection with three classification levels. Dead torrents (no peers ever, or gone silent past the timeout) are published to /api/v1/stalls so Pilot's stallwatcher can blocklist them before they waste another retry
  • 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 become a problem
  • Webhooks filtered by event type (added, completed, stalled, speed update)
  • Per-torrent and global rate limits
  • Magnet URIs, DHT, PEX, µTP, and crash-safe resume via a persistent piece-completion store
  • Full REST API (OpenAPI docs at /api/docs) and a WebSocket event stream at /ws
  • Postgres-backed state for torrents, categories, tags, and settings
  • Zero telemetry. No analytics, no crash reporting, no phoning home

Getting started

Standalone

A single-service compose that runs Haul with its own dedicated Postgres lives at docker/docker-compose.yml. Edit the two /path/to/... lines and the placeholder password, then:

docker compose -f docker/docker-compose.yml up -d

The web UI is at http://localhost:8484. Haul generates an API key on first run; find it in Settings → System.

As part of the Beacon Stack

For the full setup — Pulse, Pilot, Prism, FlareSolverr, and Haul behind a VPN container — see beacon-stack/deploy. Standalone Haul works on its own; run it with the stack and rename-on-complete, stall blocklisting, and centralized indexer management light up.

Build from source

Requires Go 1.25+ and Node 22+.

git clone https://github.com/beacon-stack/haul
cd haul
cd web/ui && npm ci && npm run build && cd ../..
make build
./bin/haul

Configuration

Most settings live in the web UI. For the ones you'll want at container-start time, use environment variables or a YAML config file at /config/config.yaml (also searched at ~/.config/haul/config.yaml and ./config.yaml).

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 as soon as they finish (for ratio-sensitive trackers)
HAUL_TORRENT_STALL_TIMEOUT 120 Seconds of inactivity before a torrent is classified as stalled

Where Haul fits in the Beacon stack

┌──────────┐     ┌──────────┐     ┌──────────┐
│  Pilot   │     │  Prism   │     │  Pulse   │
│   (TV)   │     │ (movies) │     │ (control │
│          │     │          │     │  plane)  │
└────┬─────┘     └────┬─────┘     └────┬─────┘
     │                │                │
     │ grab torrent   │ grab torrent   │
     ▼                ▼                │
┌───────────────────────┐              │
│        Haul           │◄─────────────┘
│   (BitTorrent)        │  optional:
│                       │  stall events, webhooks
└───────────┬───────────┘
            │
            ▼
        downloads/

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.

You can run Haul standalone and ignore the rest — the media-manager integration is opt-in via the rename_on_complete setting and the upstream service passing metadata.

Power user notes

A few things worth knowing if you want to go deeper than the UI:

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. Gaps over 30 seconds reset the tracker to avoid extrapolating from stale data. The math lives in internal/core/torrent/session.go — tweak the time constant there if the default feels too slow or too twitchy for your connection.

Stall classification. Three-level state machine in internal/core/torrent/stall.go:

  • no_peers_ever — torrent has never seen a peer after the grace period
  • activity_lost — bytes were flowing, but nothing has changed past the stall timeout
  • complete_but_no_activity — finished but hasn't uploaded anything recently (useful for ratio-enforcing trackers)

Anything above level 1 shows up on /api/v1/stalls.

Regression suite. Haul has been bitten by dead-torrent bugs often enough that there's a locked-in test suite covering the failure modes. make test runs it in under two seconds. If you're editing the session wiring, stall detection, or the rate tracker, the suite will catch regressions before they ship. See CLAUDE.md for the guarded files.

Webhooks. Configure HTTP callbacks filtered by event type. Payloads are the same shape as the WebSocket events, so you can reuse your event handler code.

API surface. The REST API is complete — anything the UI does is available over HTTP. Interactive docs at /api/docs. The Go client lives in pkg/sdk if you want to integrate from another Go service.

Privacy

Haul makes outbound connections only to peers, trackers, and the optional Pulse URL you configure. No telemetry, no analytics, no crash reporting, no update checks. API keys and credentials stay in your local database.

Built with Claude

Haul was built by one person with extensive help from Claude (Anthropic). Architecture, design decisions, bug triage, and this README are mine. Many of the keystrokes are not. If something in the code or the docs doesn't make sense, that's a bug worth reporting — open an issue.

Development

make build    # compile to bin/haul
make run      # build + run
make dev      # hot reload (requires air)
make test     # go test ./...
make check    # golangci-lint + tsc --noEmit
make sqlc     # regenerate sqlc code

Contributing

Bug reports, feature requests, and pull requests are welcome. Please open an issue before starting anything large.

License

MIT — see LICENSE.

Directories

Path Synopsis
cmd
haul command
internal
api
core/activity
Package activity persists bus lifecycle events into the torrent_events table so the Activity page can render a full history (across restarts) instead of just the in-memory live stream.
Package activity persists bus lifecycle events into the torrent_events table so the Activity page can render a full history (across restarts) instead of just the in-memory live stream.
core/renamer
Package renamer applies naming format templates to produce filesystem-safe filenames for downloaded media files.
Package renamer applies naming format templates to produce filesystem-safe filenames for downloaded media files.
core/torrent
Package torrent owns the anacrolix/torrent client lifecycle and the Session type that exposes it to the rest of haul.
Package torrent owns the anacrolix/torrent client lifecycle and the Session type that exposes it to the rest of haul.
db
db/admin
Package admin owns the diagnostic registry and the soft-delete shim (cleanup_history) used by the Settings → System → Diagnostics tab.
Package admin owns the diagnostic registry and the soft-delete shim (cleanup_history) used by the Settings → System → Diagnostics tab.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL