GDM (Go Download Manager) is a single-binary Go download manager for terminal and headless environments. It supports segmented HTTP downloads, BitTorrent magnets and .torrent files, pause/resume, crash-safe state, and media organization.
- Go 1.24.4 or newer
- Writable download and state directories
- Network access for HTTP downloads and torrent metadata/peers
- TMDb credentials only for movie organization
- TV organization uses TVMaze
go build -o gdm ./cmd/gdm
./gdm # terminal dashboard
./gdm https://example.com/file.iso # queue HTTP download
./gdm 'magnet:?xt=urn:btih:...' # queue torrent
./gdm ./linux-distro.torrent # queue local torrent
./gdm --headless # process unfinished queueFlags can appear before or after URLs:
| Flag | Default | Purpose |
|---|---|---|
-dir |
~/Downloads |
Finished-file directory |
-state |
~/.gdm/state |
Per-download JSON state |
-connections |
4 |
HTTP connections per download |
-max-active |
3 |
Concurrent active downloads |
-interval |
2s |
Headless progress refresh |
-public-trackers |
false |
Add fallback trackers before magnet metadata; reveals info hash |
--headless |
false |
Run without TUI and return non-zero on failure |
Headless examples:
./gdm --headless -dir /data/downloads 'magnet:?xt=urn:btih:...'
./gdm --headless -interval 500msNo TTY automatically selects headless mode. A completed torrent is not accepted from piece state alone: gdm checks every final file, expected size, and piece hash before returning success. Full .part data is locally rehashed and promoted when possible; corrupt or incomplete data returns to the normal swarm path.
a add URL, magnet, or .torrent path · p pause · r resume · x remove and delete downloaded data · ↑/↓ select · q quit.
Organizer commands default to dry-run. Add --apply only after reviewing planned paths. Existing files and collisions are not overwritten.
# Preview TV organization
./gdm organize tv --input /downloads --output /library/TV --query "The Simpsons"
# Apply movie organization
TMDB_API_KEY=YOUR_KEY ./gdm organize movie \
--input "/downloads/Arrival (2016).mkv" \
--output /library/Movies --applyTV matching reads SxxEyy and can use TVMaze. Movie matching uses TMDb and requires TMDB_API_KEY or TMDB_READ_ACCESS_TOKEN. Supported media extensions: mkv, mp4, m4v, avi, mov, mpeg, mpg, ts, webm, wmv.
State is stored as one JSON file per download under ~/.gdm/state. Writes use a temporary file and atomic rename. Interrupted active downloads load as paused; gdm does not silently spend bandwidth after restart.
The old ~/.idm/state directory migrates to ~/.gdm/state once. Override with -state when using a custom location. Torrent metainfo cache lives beside state at ~/.gdm/torrents by default.
Torrent recovery rules:
- Final files are authoritative only after size and piece-hash verification.
- A completed torrent with leftover
.partdata loads aspaused. - Resuming a full
.partfile hashes local data first and avoids re-download. - A promotion failure is surfaced as an error, not reported as completed.
- Zero-byte files are invalid for non-empty torrent files and are rechecked by size/hash.
If a queue entry says completed but disk data is missing, stop gdm, verify -dir matches the original destination, rebuild the binary, and restart:
go build -o gdm ./cmd/gdm
./gdm --headlessThe repository uses ports-and-adapters boundaries:
cmd/gdm composition root, CLI, headless mode
internal/domain Download and Segment entities; no I/O
internal/manager queue, lifecycle, concurrency, progress, ports
internal/engine segmented HTTP adapter
internal/torrentengine anacrolix/torrent adapter and disk verification
internal/store atomic JSON persistence adapter
internal/mediaorg TV/movie discovery and safe copy planning
internal/tui Bubble Tea dashboard adapter
third_party narrow module stubs for legacy transitive tooling
skills maintainer workflow notes
internal/manager depends on interfaces declared in internal/manager/ports.go, not concrete transport or persistence adapters. Keep new integrations behind those ports. Keep filesystem and network validation at adapter boundaries. Do not bypass manager lifecycle methods by mutating stored JSON manually while gdm is running.
Concurrency rules:
- Manager owns lifecycle transitions and limits active downloads with a semaphore.
- Each download worker owns its mutable download state.
Manager.ListandManager.Getreturn copies for readers such as TUI.- HTTP segments write disjoint ranges with
WriteAt. - Store writes and deletes are serialized and reject unsafe IDs.
Run focused checks during changes:
gofmt -w ./cmd ./internal
go test ./...
go test -race ./...
go vet ./...
golangci-lint run ./...Run one package while iterating:
go test ./internal/torrentengine -run 'TestStart|TestEndToEnd' -count=1 -v
go test ./internal/manager -run 'Test.*' -count=1 -vTests use local httptest servers and offline in-process torrent seeders. They do not require public trackers. Avoid tests that depend on real external media APIs or network timing.
Before submitting changes:
- Keep changes inside the relevant package boundary.
- Add a focused regression test for non-trivial behavior.
- Run formatting, unit tests, race tests, vet, and lint.
- Check
git difffor generated binaries, credentials, state files, and accidental path changes.
- No bandwidth throttling or recurring schedule.
- No browser integration or clipboard monitoring.
- HTTP segment count is fixed when a download is created.
- Paused torrents retain state in the current client; explicit removal drops the torrent.
- No seeding-after-complete toggle.
- Public fallback trackers are opt-in before metadata and disclose the torrent info hash.
See LICENSE.