rationale Link to heading

  • accessing transmission-daemon via web/cli
  • do port forwarding via vpn provider to secure transmission
  • using a dedicated lightweight distro
  • centralize all your torrents from public/private trackers
  • not using docker and it’s *arr stack but why ???
  • be a good 24/7 seeder

requirements Link to heading

  • own router/firewall or ability to open/nat ports
  • (optional) using proxmox’s lxc for better performances, less overhead for storage and network
  • an alpine box but any gnu/linux or even bsd will do ( commands differ just a bit )
  • (optional) wireguard as it’s easy to setup and really convenient
  • (optional) airvpn account to have all options ( this is not sponsored )
  • a storage attached to your box
warnings Link to heading
  • some trackers prohibit vpn use read the rulez
  • some trackers need you to disable dht and pex or utp
scenarii Link to heading
  • seedbox on lan
  • seedbox on vps
seedbox on lan Link to heading

topology

  • firewall pfsense : 192.168.66.1 :51442 NAT > 192.168.66.51 + rules on wan
  • lxc alpinebox : 192.168.66.51 :51442 transmission-daemon + rclone

on alpine

/etc/network/interfaces

auto eth0
iface eth0 inet static
        address 192.168.66.51/24
        gateway 192.168.66.1

check routes and neighbours

ip r
ip n

ping the great whole internet

ping 9.9.9.9
vpn client Link to heading
  • skip all vpn and port forwarding steps if no vpn is needed

setup wireguard in alpine

apk add wireguard-tools wireguard-tools-wg-quick

airvpn is a good vpn for port forwarding

generate wireguard conf via client area:

https://airvpn.org/client/

paste the .conf in /etc/wireguard/airvpn.conf

up the interface

wg-quick up airvpn

check vpn ip and status

ip -br a
wg show

if everything work as intended create an openrc initd to start at boot

/etc/init.d/wg-airvpn

#!/sbin/openrc-run

description="WireGuard Quick Airvpn"

depend() {
    need localmount
    need net
}

start() {
    wg-quick up airvpn
}

stop() {
    wg-quick down airvpn
}

enable it

chmod +x /etc/init.d/wg-airvpn
rc-update add wg-airvpn default
vpn port forwarding Link to heading

setup the name of the vm in airvpn client area

you can adjust the local port or not you deal whith what’s behind the firewall

for example in airvpn client area port forwarding

for you device only

  • 51442
  • device alpinebox
  • protocol tcp/udp
  • ipv4 only
  • local port 51442

example pfsense :

  • NAT : wan tcp/udp * :* wan_address :51442 > nat_ip 192.168.66.51:51442
  • WAN rule: ipv4 tcp/udp * :* > 192.168.66.51:51442

in pfsense you’ll need to setup outbound nat to hybrid

later in transmission you’ll setup the listen port to :51442

storage Link to heading
  • following commands refer to proxmox if you have another system go to directories

on proxmox mount usb or whatever media storage

for example

mount /dev/usbpool/mediacenter /mnt/media/

enter alpine container

pct enter 651

we need a user dlman without sudo prvileges and with specific uid

adduser -s /bin/sh -u 1000 dlman

get back to proxmox’s shell

Ctrl + O

setup directory’s permissions recuresively

chown -R 101000:101000 /mnt/mediacenter/

1000 is the uid of dlman

configure the mountpoint

prohibit dangerous options and disable backup on this mp

/etc/pve/lxc/651.conf

mp0: /mnt/mediacenter,mp=/mnt/media,mountoptions=nodev;noexec;nosuid,backup=0

restart the container to check everything is ok

pct reboot 651
pct enter 651
directories Link to heading

create the needed directories :

mkdir -p /mnt/media/downloads/{complete,incomplete,torrents}

adapt the permission so transimission can write inside

transmission setup Link to heading

some packages are needed

apk update && apk upgrade
apk add rclone transmission-daemon-openrc transmission-daemon transmission-cli
  • stop the service otherwise it’ll flush conf
rc-service transmission-daemon stop

configure transmission to require encryption for peers

/etc/conf.d/transmission-daemon

TRANSMISSION_OPTIONS="--encryption-required"

then configure transmission to

  • dl stuff in downloads and incomplete
  • watch new torrent files added automatically in torrents
  • disable dht pex and utp for some private trackers
  • provide a user/password for rpc remote access
  • limit access via rpc to localhost only on :9091
  • setup the listening port for peers :51442
  • force encryption with peers

here is an example conf

  • warning : some private trackers ask to disable dht pex and utp

  • the first time you setup a password it detect it’s not a hash and will hash it once transmission is started

/var/lib/transmission/config/settings.json

{
    "alt-speed-down": 50,
    "alt-speed-time-begin": 540,
    "alt-speed-time-day": 127,
    "alt-speed-time-enabled": false,
    "alt-speed-time-end": 1020,
    "alt-speed-up": 50,
    "announce-ip": "",
    "announce-ip-enabled": false,
    "anti-brute-force-enabled": false,
    "anti-brute-force-threshold": 100,
    "bind-address-ipv4": "0.0.0.0",
    "bind-address-ipv6": "::",
    "blocklist-enabled": false,
    "blocklist-url": "http://www.example.com/blocklist",
    "cache-size-mb": 128,
    "default-trackers": "",
    "dht-enabled": false,
    "download-dir": "/mnt/media/downloads/complete",
    "download-queue-enabled": true,
    "download-queue-size": 20,
    "encryption": 2,
    "idle-seeding-limit": 30,
    "idle-seeding-limit-enabled": false,
    "incomplete-dir": "/mnt/media/downloads/incomplete",
    "incomplete-dir-enabled": true,
    "lpd-enabled": true,
    "message-level": 2,
    "peer-congestion-algorithm": "",
    "peer-limit-global": 240,
    "peer-limit-per-torrent": 50,
    "peer-port": 51442,
    "peer-port-random-high": 65535,
    "peer-port-random-low": 49152,
    "peer-port-random-on-start": false,
    "peer-socket-tos": "le",
    "pex-enabled": false,
    "pidfile": "/var/run/transmission/transmission.pid",
    "port-forwarding-enabled": true,
    "preallocation": 1,
    "prefetch-enabled": true,
    "queue-stalled-enabled": true,
    "queue-stalled-minutes": 30,
    "ratio-limit": 2,
    "ratio-limit-enabled": false,
    "rename-partial-files": true,
    "rpc-authentication-required": true,
    "rpc-bind-address": "0.0.0.0",
    "rpc-enabled": true,
    "rpc-host-whitelist": "",
    "rpc-host-whitelist-enabled": false,
    "rpc-password": "REDACTEDPASS",
    "rpc-port": 9091,
    "rpc-socket-mode": "0750",
    "rpc-url": "/transmission/",
    "rpc-username": "btman",
    "rpc-whitelist": "127.0.0.1,::1",
    "rpc-whitelist-enabled": true,
    "scrape-paused-torrents-enabled": true,
    "script-torrent-added-enabled": false,
    "script-torrent-added-filename": "",
    "script-torrent-done-enabled": false,
    "script-torrent-done-filename": "",
    "script-torrent-done-seeding-enabled": false,
    "script-torrent-done-seeding-filename": "",
    "seed-queue-enabled": false,
    "seed-queue-size": 10,
    "speed-limit-down": 100,
    "speed-limit-down-enabled": false,
    "speed-limit-up": 100,
    "speed-limit-up-enabled": false,
    "start-added-torrents": true,
    "tcp-enabled": true,
    "torrent-added-verify-mode": "fast",
    "trash-original-torrent-files": false,
    "umask": "002",
    "upload-slots-per-torrent": 8,
    "utp-enabled": true,
    "watch-dir": "/mnt/media/downloads/torrents",
    "watch-dir-enabled": true
}

now you can start your service

service transmission-daemon start
accessing transmission remotely Link to heading
  • via rpc web interface
  • via rpc in ssh tunnel

be carefull do not expose your rpc on the internet !

  • on the lan you may not need protection

/var/lib/transmission/config/settings.json

    "rpc-whitelist-enabled": false,

http://192.168.66.51:9091/transmission/web/

  • via secure ssh tunnel

you’ll need to bind the 9091 port to a local port on your remote client

add this option to alpine’s ssh conf

/etc/sshd_config

AllowTcpForwarding local

reload sshd

rc-service sshd restart

then from a client

ssh -i .ssh/seedbox_key -L 9091:localhost:9091 user@yourseedbox -p22

then you can use web browser to access the webui

http://localhost:9091

  • you can also use transmission-remote on the seedbox or via the tunnel
port stuff Link to heading

check the port via edit preferences > network > listen port is :51442

check your listening sockets

netstat -tulpn

double check your firewall

now enable your service at start

rc-update add transmission-daemon

transmission-remote Link to heading

you can use cli to do some funny stuff and exploit scripts

create a .netrc file containing your credentials

machine localhost
login btman
password REDACTED
account REDACTED

eventualy use a bash_alias as follows

alias transmission-remote='transmission-remote -N ~/.secrets/.netrc'

get informations and perform actions :

transmission-remote -N .netrc -st
transmission-remote -N .netrc -l

now the fun begins you can add a torrent and label it :

transmission-remote -a Downloads/debian-12.5.0-amd64-netinst.iso.torrent -L iso
transmission-remote -a Downloads/Rocky-9.3-x86_64-dvd.torrent -L iso

then you can list all your torrents with labels

transmission-remote -F l:iso -l
    ID   Done       Have  ETA           Up    Down  Ratio  Status       Name
    25   100%   10.51 GB  Done       382.0     0.0   0.06  Seeding      Rocky-9.3-x86_64-dvd
   214   100%   659.6 MB  Done         0.0     0.0   0.00  Idle         debian-12.5.0-amd64-netinst.iso
   228   100%    8.80 GB  Done         0.0     0.0   0.00  Idle         Rocky-9.3-aarch64-dvd
Sum:            19.98 GB             382.0     0.0

then you can perform action like moving :

  • torrent should be in idle or stopped to be moved
transmission-remote -F l:iso -l --move /mnt/media/iso/

if you need some more fun you can use this simple script to move torrents with label to specific directories

this stop moves and restart only active torrents with defined label located in /mnt/mediacenter/downloads/complete to specific directories matching labels.

#!/bin/bash

# Define the directory mappings for each label
declare -A LABEL_DIRS=(
    ["iso"]="/mnt/media/iso"
    ["software"]="/mnt/media/software"
    ["serie"]="/mnt/media/series"
    ["doc"]="/mnt/media/documentaries"
    ["music"]="/mnt/media/music"
    ["movie"]="/mnt/media/films"
    ["junk"]="/mnt/media/junk"
)

# Define the complete download directory
COMPLETE_DOWNLOAD_DIR="/mnt/media/downloads/complete"

# Function to handle transmission-remote command with credentials
transmission_remote() {
    transmission-remote -N ~/.secrets/.netrc "$@"
}

# Function to get the location of a torrent
get_torrent_location() {
    transmission_remote -t "$1" -i | awk '/Location: / {print $NF}'
}

# Function to get the status of a torrent
get_torrent_status() {
    transmission_remote -t "$1" -i | awk '/State: / {print $NF}'
}

# Loop through each label and process its torrents
for label in "${!LABEL_DIRS[@]}"; do
    label_dir="${LABEL_DIRS[$label]}"
    echo "Moving torrents with label $label to $label_dir directory..."
    # Retrieve torrent IDs with the specified label
    torrent_ids=$(transmission_remote -F "l:$label" -l | awk '$1 ~ /^[0-9]+$/ {print $1}')
    if [[ -n "$torrent_ids" ]]; then
        echo "Torrent IDs for label $label: $torrent_ids"
        # Loop through each torrent ID and perform the necessary actions
        for id in $torrent_ids; do
            # Get the location of the torrent
            torrent_location=$(get_torrent_location "$id")
            # Check if the torrent is in the complete download directory
            if [[ "$torrent_location" == "$COMPLETE_DOWNLOAD_DIR" ]]; then
                # Get the status of the torrent
                torrent_status=$(get_torrent_status "$id")
                echo "Moving torrent $id with label $label..."
                transmission_remote -t "$id" -S --move "$label_dir"
                # Introduce a short delay before starting the torrent if its status was not "Stopped"
                if [[ "$torrent_status" != "Stopped" ]]; then
                    sleep 1
                    transmission_remote -t "$id" -s
                fi
            else
                echo "Skipping torrent $id with label $label as it's not in $COMPLETE_DOWNLOAD_DIR"
            fi
        done
    else
        echo "No torrents found with label $label"
    fi
done

cron is your friend

ddl and cloud services Link to heading

configure rclone to dl stuff in /mnt/media/downloads

su -l dlman
rclone config

you can go through the process for multiple providers

enjoy your local seedbox.

seedbox on vps Link to heading
  • reproduce ip address steps
  • as firewall you can use iptables/nftables/ufw/firewalld or whatsoever
  • use a ssh tunnel to bind rpc port to local ! (see above)

simple ufw example

ufw enable
ufw allow 22,51442/tcp
ufw allow 51442/udp

use netcat to can ports

nc -zv 192.168.66.51 51442

check in transmission-remote or via webui if everything is ok

troubleshooting : Link to heading

when using the client transmission-remote it may change your settings.json permissions

transmission-remote --list
-rw-------    1 transmis transmis    2.7K Feb 14 00:24 /var/lib/transmission/config/settings.json

you’ll have this error

/var/log/transmission/transmission.log

transmission-daemon Error loading config file -- exiting. (/home/buildozer/aports/community/transmission/src/transmission-4.0.4/daemon/daemon.cc:914)

because it changes the permission of your conf

-rw-------    1 transmis transmis    2.7K Feb 14 00:24 /var/lib/transmission/config/settings.json

correct the permissions

chmod g+rwx /var/lib/transmission/config/settings.json

ressources Link to heading

  • mount binding point

http://web.archive.org/web/20231105221134/https://itsembedded.com/sysadmin/proxmox_bind_unprivileged_lxc/

  • airvpn

https://airvpn.org/faq/port_forwarding/

  • rclone

https://rclone.org/

  • transmission conf

https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md

https://github.com/transmission/transmission/blob/main/docs/Port-Forwarding-Guide.md

https://superuser.com/questions/113649/how-do-you-set-a-password-for-transmission-daemon-the-bittorrent-client-server

https://wiki.archlinux.org/title/Transmission#