Skip to content

Repository files navigation

TorrentDownloader

A lightweight, self-hosted torrent downloader designed for a headless Linux PC.

Table of Contents

  1. Why does this exist?

  2. Features

  3. Requirements

  4. Directory structure

  5. Creating a torrent instance

  6. Adding a magnet link

  7. Running the downloader manually

  8. Systemd

  9. Automatic startup with .runner

  10. Why individual torrent services are disabled

  11. Enabling boot startup

  12. Progress monitor

  13. Understanding progress

  1. Logs
  1. Recovery after reboot
  2. Existing files
  3. Disk usage
  4. Adding a second torrent
  5. Checking all torrent services
  6. Troubleshooting
  1. Git
  2. System architecture
  1. Systemd architecture
  2. Boot sequence
  3. Design philosophy
  4. Typical daily usage
  5. Quick setup
  6. At Last

Why does this exist?

TorrentDownloader is designed for an unattended server/home-server workflow where you want more control over how files inside torrents are downloaded and recovered.

The main goals are:

  • Download files sequentially within each torrent.
  • Run multiple torrent instances in parallel.
  • Preserve partial files after interruptions.
  • Resume downloads after reboot or service restart.
  • Verify existing files before treating them as valid.
  • Detect corrupt files and re-download them.
  • Keep persistent per-file logs and recovery state.
  • Monitor torrents from a terminal dashboard.
  • Automatically start selected torrent instances at boot using a .runner file.

The intended workflow is:

Torrent
   ↓
Sequential file download
   ↓
Integrity verification
   ↓
Mark file VERIFIED
   ↓
Next file

This is particularly useful on a headless Debian/Linux machine where reliability and unattended operation are more important than having a graphical torrent client.


Features

Sequential downloads

Files within a torrent are processed one at a time:

File 1 → verify
           ↓
File 2 → verify
           ↓
File 3 → verify
           ↓
...

Multiple independent torrent instances can still run at the same time.

For example:

torrent1 → downloading file 7
torrent2 → downloading file 2
torrent3 → stopped

Resume support

If a download is interrupted, the partial file is preserved.

For example:

Expected:       10 GiB
Downloaded:      4 GiB
Power failure
        ↓
System boots
        ↓
Resume from existing partial data

The goal is to avoid unnecessarily downloading the same data again.

Persistent per-file state

Each torrent keeps state under:

lib/sequential-torrent/

Per-file logs are stored as:

lib/sequential-torrent/files/1.log
lib/sequential-torrent/files/2.log
lib/sequential-torrent/files/3.log

The logs record important events such as:

DOWNLOADING
RESUMING
DOWNLOADED
VERIFYING
VERIFIED
CORRUPT
RETRYING
INTERRUPTED
FILE_MISSING

Integrity verification

A completed aria2 download is not automatically considered valid.

The intended lifecycle is:

aria2 download completes
        ↓
integrity verification
        ↓
VERIFIED
        ↓
next file

This helps protect against incomplete or corrupt files.

Terminal monitoring

torrent-progress.sh provides a terminal dashboard with information such as:

  • Service status
  • Download status
  • Completed files
  • Current file
  • Torrent progress
  • Current-file progress
  • Downloaded bytes
  • Total bytes
  • Speed
  • ETA
  • Disk usage
  • Last update time

Requirements

Designed for Debian/Linux.

Required software:

  • Bash
  • aria2
  • systemd
  • standard Linux utilities such as awk, sed, grep, find, stat, and date

Install aria2:

sudo apt update
sudo apt install aria2

Verify:

aria2c --version

Directory structure

The project uses one directory per torrent instance.

Example:

TorrentDownloader/
├── .runner
├── torrent-runner.sh
├── torrent-downloader.sh
├── torrent-progress.sh
│
├── torrent1/
│   ├── .torrent_magnets
│   ├── metadata/
│   ├── torrents/
│   └── lib/
│       └── sequential-torrent/
│           ├── files/
│           ├── progress
│           └── current.state
│
└── torrent2/
    ├── .torrent_magnets
    ├── metadata/
    ├── torrents/
    └── lib/
        └── sequential-torrent/
            ├── files/
            ├── progress
            └── current.state

The main project directory in the current setup is:

/home/debian/Projects/TorrentDownloader/

Creating a torrent instance

Create an instance directory:

cd /home/debian/Projects/TorrentDownloader

mkdir -p torrent1

For another torrent:

mkdir -p torrent2

The downloader creates the required internal directories when it runs.


Adding a magnet link

Each instance uses:

.torrent_magnets

For torrent1:

nano torrent1/.torrent_magnets

Put the magnet URI on the first line:

magnet:?xt=urn:btih:...

Save and exit.

The downloader reads the first line of this file.


Running the downloader manually

Run torrent1:

./torrent-downloader.sh /home/debian/Projects/TorrentDownloader/torrent1

Run torrent2:

./torrent-downloader.sh /home/debian/Projects/TorrentDownloader/torrent2

The downloader will:

  1. Resolve the magnet metadata.
  2. Locate the generated .torrent file.
  3. Read the torrent file list.
  4. Inspect existing files and recovery logs.
  5. Determine what needs to be downloaded.
  6. Resume partial files where possible.
  7. Download the current file.
  8. Verify the completed file.
  9. Mark it as verified.
  10. Continue to the next file.

Systemd

The project uses a systemd template service:

/etc/systemd/system/torrent-downloader@.service

The instance name becomes %i.

Therefore:

torrent-downloader@torrent1.service

operates on:

/home/debian/Projects/TorrentDownloader/torrent1

and:

torrent-downloader@torrent2.service

operates on:

/home/debian/Projects/TorrentDownloader/torrent2

After changing a systemd unit:

sudo systemctl daemon-reload

Start a torrent

sudo systemctl start torrent-downloader@torrent1.service

Stop a torrent

sudo systemctl stop torrent-downloader@torrent1.service

Restart a torrent

sudo systemctl restart torrent-downloader@torrent1.service

Check status

systemctl status torrent-downloader@torrent1.service

Automatic startup with .runner

The .runner file controls which torrent instances should start automatically after boot.

Example:

torrent1
torrent2

This means:

System boot
    ↓
torrent-downloader-runner.service
    ↓
read .runner
    ↓
start torrent1
    ↓
start torrent2

Add a torrent to automatic startup

Edit:

nano /home/debian/Projects/TorrentDownloader/.runner

Example:

torrent1
torrent2
torrent3

One instance name goes on each line.

Comments can be used:

# Main torrents
torrent1
torrent2

# Additional torrent
torrent3

Blank lines are ignored.

Remove a torrent from automatic startup

Simply remove its line.

Change:

torrent1
torrent2
torrent3

to:

torrent1
torrent3

This does not delete torrent2 or its downloaded files. It only means torrent2 will no longer be selected by the boot runner.


Why individual torrent services are disabled

With the .runner architecture, the individual services do not need to be enabled independently.

For example:

systemctl is-enabled torrent-downloader@torrent2.service

may return:

disabled

That is normal.

The boot-enabled service is:

torrent-downloader-runner.service

Check it:

systemctl is-enabled torrent-downloader-runner.service

Expected:

enabled

The architecture is:

systemd
   │
   ▼
torrent-downloader-runner.service
   │
   ▼
.runner
   │
   ├── torrent1
   │      ↓
   │   torrent-downloader@torrent1.service
   │
   ├── torrent2
   │      ↓
   │   torrent-downloader@torrent2.service
   │
   └── torrent3
          ↓
      torrent-downloader@torrent3.service

Enabling boot startup

Reload systemd:

sudo systemctl daemon-reload

Enable the runner:

sudo systemctl enable torrent-downloader-runner.service

Start it immediately for testing:

sudo systemctl start torrent-downloader-runner.service

Check:

systemctl status torrent-downloader-runner.service

A successful runner normally shows:

active (exited)

This is expected. The runner starts the individual torrent services and then exits.

The actual downloads continue under:

torrent-downloader@torrent1.service
torrent-downloader@torrent2.service
...

Progress monitor

The project includes:

torrent-progress.sh

Show default torrent

./torrent-progress.sh

Show one torrent

./torrent-progress.sh torrent1

Show multiple torrents

./torrent-progress.sh torrent1 torrent2

Show all torrents

./torrent-progress.sh -a

or:

./torrent-progress.sh --all

Hide filenames

./torrent-progress.sh torrent1 -n

The monitor is intended to be used while the downloader services continue running in the background.


Understanding progress

There are two different progress measurements.

Current-file progress

Example:

File progress    : [##########------------------------------] 25.00%
Downloaded       : 2.50 GiB
Total size       : 10.00 GiB

This describes the file currently being downloaded or verified.

Torrent-wide progress

Example:

Torrent progress : [####################--------------------] 50.00%
Downloaded       : 500 GiB
Total size       : 1.00 TiB

This describes the entire torrent.

Torrent-wide downloaded bytes are based on the physical files present in the download directory.


Logs

systemd journal

For torrent1:

sudo journalctl -u torrent-downloader@torrent1.service

Show the last 100 lines:

sudo journalctl -u torrent-downloader@torrent1.service -n 100 --no-pager

Follow live:

sudo journalctl -u torrent-downloader@torrent1.service -f

For torrent2:

sudo journalctl -u torrent-downloader@torrent2.service -f

Per-file logs

Example:

cat torrent1/lib/sequential-torrent/files/1.log

Find the latest state:

grep '^STATE' torrent1/lib/sequential-torrent/files/1.log |
tail -n 1

Recovery after reboot

A typical recovery sequence is:

Download file 7
       ↓
Power failure / reboot
       ↓
Partial file remains
       ↓
System boots
       ↓
runner reads .runner
       ↓
torrent service starts
       ↓
downloader reads persistent state
       ↓
partial file detected
       ↓
aria2 resumes
       ↓
download completes
       ↓
integrity verification
       ↓
file marked VERIFIED
       ↓
next file

This is one of the primary reasons persistent state and per-file logs exist.


Existing files

The downloader distinguishes between different situations.

Previously verified file

If the file exists and its state says VERIFIED, it can be skipped when it still matches the expected state.

Existing file with no log

The downloader can verify it before considering it complete.

Partial file

A smaller-than-expected file is treated as potentially resumable data rather than automatically being considered corrupt.

Corrupt file

A file that fails integrity verification is treated as corrupt and can be removed so it can be downloaded again.

The intended principle is:

Valid file
   ↓
Keep it

Partial file
   ↓
Preserve and resume

Corrupt file
   ↓
Delete and redownload

Disk usage

Downloads are stored under:

<instance>/torrents/

For example:

torrent1/torrents/
torrent2/torrents/

The monitor displays disk information including:

Disk free
Disk used
Disk total
Disk usage

Always ensure enough free disk space is available for the torrents you intend to download.


Adding a second torrent

Create the instance:

mkdir -p torrent2

Create its magnet file:

nano torrent2/.torrent_magnets

Add the magnet link.

Test it manually:

./torrent-downloader.sh /home/debian/Projects/TorrentDownloader/torrent2

Then add it to .runner:

torrent1
torrent2

For immediate startup without reboot:

sudo systemctl start torrent-downloader@torrent2.service

Checking all torrent services

List active torrent services:

systemctl list-units 'torrent-downloader@*.service'

Check a specific instance:

systemctl status torrent-downloader@torrent1.service

Troubleshooting

The service is not running

Check:

systemctl status torrent-downloader@torrent1.service

Then:

sudo journalctl -u torrent-downloader@torrent1.service -n 100 --no-pager

The runner is enabled but a torrent does not start

Check the runner:

cat .runner

Make sure every name corresponds to an actual directory.

For example:

torrent1
torrent2

must correspond to:

torrent1/
torrent2/

Check the runner:

systemctl status torrent-downloader-runner.service

Manually test the runner

sudo systemctl start torrent-downloader-runner.service

Then check:

systemctl status torrent-downloader@torrent1.service
systemctl status torrent-downloader@torrent2.service

Check aria2 processes

ps -ef | grep '[a]ria2c'

Check the downloader journal

sudo journalctl -u torrent-downloader@torrent1.service -n 100 --no-pager

Git

The project can be maintained as a Git repository.

Check status:

git status

Add changes:

git add .

Commit:

git commit -m "Update torrent downloader"

Push:

git push

Avoid committing downloaded data and generated state unless intentionally required.

Use .gitignore for generated download/state files.


System architecture

The project has three main application components.

torrent-downloader.sh

Responsible for:

Magnet
 ↓
Torrent metadata
 ↓
File list
 ↓
Recovery
 ↓
Sequential download
 ↓
Integrity verification
 ↓
Persistent state

torrent-progress.sh

Responsible for:

Reading state
 ↓
Calculating/displaying progress
 ↓
Showing service and disk information

It does not perform the torrent download itself.

torrent-runner.sh

Responsible for:

.runner
 ↓
Read selected instances
 ↓
systemctl start torrent-downloader@INSTANCE.service

Systemd architecture

The template unit:

torrent-downloader@.service

allows the same service definition to be used for many instances.

For example:

torrent-downloader@torrent1.service
torrent-downloader@torrent2.service
torrent-downloader@torrent3.service

Each instance runs against its own directory.

The runner adds another layer:

                  .runner
                     │
                     ▼
          torrent-downloader-runner
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
       torrent1   torrent2   torrent3
          │          │          │
          ▼          ▼          ▼
        systemd    systemd    systemd
          │          │          │
          ▼          ▼          ▼
        aria2c      aria2c      aria2c

Boot sequence

The complete boot sequence is:

Linux boots
    │
    ▼
network-online.target
    │
    ▼
torrent-downloader-runner.service
    │
    ▼
.runner
    │
    ├───────────────┐
    ▼               ▼
 torrent1         torrent2
    │               │
    ▼               ▼
systemd           systemd
 service           service
    │               │
    ▼               ▼
torrent-downloader.sh
    │
    ▼
aria2c
    │
    ▼
Sequential download
    │
    ▼
Integrity verification

Design philosophy

The project intentionally separates responsibilities:

torrent-downloader.sh
    = download, recovery, verification

torrent-progress.sh
    = monitoring

torrent-runner.sh
    = boot selection

systemd
    = process supervision

aria2
    = torrent transfer

This separation makes the system easier to operate and troubleshoot on a headless machine.


Typical daily usage

Once configured, normal operation is simple.

Check running torrents:

systemctl list-units 'torrent-downloader@*.service'

Open the monitor:

./torrent-progress.sh --all

Check one service:

systemctl status torrent-downloader@torrent1.service

Follow its logs:

sudo journalctl -u torrent-downloader@torrent1.service -f

Add another torrent to boot startup:

nano .runner

Then add:

torrentN

Quick setup

For a new installation:

# 1. Install aria2
sudo apt update
sudo apt install aria2

# 2. Enter the project
cd /home/debian/Projects/TorrentDownloader

# 3. Create an instance
mkdir -p torrent1

# 4. Add the magnet
nano torrent1/.torrent_magnets

# 5. Test manually
./torrent-downloader.sh /home/debian/Projects/TorrentDownloader/torrent1

# 6. Configure boot startup
nano .runner

# Example:
# torrent1
# torrent2

# 7. Reload systemd
sudo systemctl daemon-reload

# 8. Enable the runner
sudo systemctl enable torrent-downloader-runner.service

# 9. Start the runner
sudo systemctl start torrent-downloader-runner.service

# 10. Check the torrent
systemctl status torrent-downloader@torrent1.service

# 11. Monitor it
./torrent-progress.sh torrent1

At Last

TorrentDownloader exists to provide a reliable, unattended torrent workflow for a headless Linux machine.

Its core principles are:

  1. One torrent instance per directory.
  2. Sequential files within each torrent.
  3. Multiple torrents can run in parallel.
  4. Partial files are preserved.
  5. Downloads can resume after interruption.
  6. Existing files are verified rather than blindly replaced.
  7. Completed files are integrity-verified.
  8. Per-file state is persisted.
  9. Progress can be monitored from the terminal.
  10. .runner controls which instances start at boot.
  11. systemd supervises individual torrent instances.
  12. aria2 performs the actual torrent transfer.

The result is a downloader that can run unattended for long periods while retaining enough persistent state to recover from normal interruptions and reboots.

About

TorrentDownloader is designed for unattended home-server and Debian/Linux environments where reliability, resumability, file integrity, and persistent recovery state are more important than a graphical interface.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages