tranmsission

rationale Link to heading

  • homeseedbox behind homerouter
  • modernizing old alpine native seedbox
  • using docker/podman for portability
  • compatible with truenas scale 25.10
  • static version of transmission for private trackers allowlists
  • vpn port forwarding to hide traffic (airvpn+gluetun)
  • no need to open ports on router/firewall
  • nice webui with flood
  • caddy reverse proxy for tls/dns

context Link to heading

i needed to modernize an old seedbox after migrating from alpine lxc with transmission to truenas scale’s docker

connect to home through a wireguard vpn on my router but it’s easy to implement a wg-easy in this stack like a previous blog post to be more “air gapped”

todo Link to heading

  • adding arr services step by step
  • adding wg-easy to limit access to seedbox
  • fully portable conf for easy migration
  • test on rhel host with podman
  • try implementaing warpgate as bastion

users files and permissions Link to heading

working on truenas scale (debian under the hood) is no so different so any gnu/linux will do.

i’m using apps user in truenas it’s id/gid:568

sudo mkdir -p /mnt/drum/lab/sharestack/{caddy,files,flood,gluetun,transmission} /mnt/drum/lab/sharestack/caddy/{config,data} /mnt/drum/lab/sharestack/files/{complete,downloads,incomplete,watch}

airvpn port forwarding Link to heading

you can use your own wireguard server to do port forwarding

generate wireguard conf by going to

  • Client area > Config Generator > wireguard > choose your countries/server

once the conf generated download it and extract

  • presharedkey
  • ptrivatekey
  • public ip

open a port forwarding for this device by going to

  • Client area > ports > manage

  • ports: 51822

  • enabled : yes

  • tcp+udp

  • ipv4/6 (your choice)

  • local port: 51822 (depending on your topology)

at the end once the stack is up you should be able to do test port and it’ll be open

  • very important without this you may expose your conntrack to your isp ans co

gluetun vpn/network Link to heading

gluetun does

  • vpn connection to airvpn
  • update airvpns’s list of server /24h
  • handle port forwarding (no need to open firewall/router)
  • use privacy respectful dns over tls servers for trackers
  • expose only caddy’s :443 for flood access

you can optimize wireguard or even use your own server

gluetun block

  gluetun:
    cap_add:
      - NET_ADMIN
    container_name: gluetun
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - TZ=Europe/Paris
      - PUID=568
      - PGID=568
      - VPN_SERVICE_PROVIDER=airvpn
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=redacted
      - WIREGUARD_PRESHARED_KEY=redacted
      - WIREGUARD_ADDRESSES=redacted
      - SERVER_COUNTRIES=redacted
      - UPDATER_VPN_SERVICE_PROVIDERS=airvpn
      - UPDATER_PERIOD=24h
      - FIREWALL_VPN_INPUT_PORTS=51822
      - DNS_SERVER=on
      - DNS_UPSTREAM_RESOLVER_TYPE=dot
      - DNS_UPSTREAM_RESOLVERS=quad9,libredns
      - DNS_UPDATE_PERIOD=24h
      - BLOCK_SURVEILLANCE=on
      - FIREWALL_ALLOW_LAN=true
      - FIREWALL_INPUT_PORTS=443
    image: qmcgaw/gluetun
    ports:
      - '443:443'
    restart: unless-stopped
    volumes:
      - /mnt/drum/lab/sharestack/gluetun:/gluetun
      - /mnt/drum/lab/sharestack/gluetun:/tmp/gluetun

transmission bittorrent Link to heading

a configuration is needed for

  • defining our port :51822
  • enabling rpc for flood access
  • many other option you should be aware of for private trackers
  • password is in clear text and at image restart will be hashed automatically
  • every torrent copied in directory watch will start automatically

/mnt/drum/lab/sharestack/transmission/settings.json

{
    "alt-speed-down": 50,
    "alt-speed-enabled": false,
    "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": 4,
    "default-trackers": "",
    "dht-enabled": false,
    "download-dir": "/downloads/complete",
    "download-queue-enabled": true,
    "download-queue-size": 5,
    "encryption": 1,
    "idle-seeding-limit": 30,
    "idle-seeding-limit-enabled": false,
    "incomplete-dir": "/downloads/incomplete",
    "incomplete-dir-enabled": true,
    "lpd-enabled": false,
    "message-level": 2,
    "peer-congestion-algorithm": "",
    "peer-id-ttl-hours": 6,
    "peer-limit-global": 200,
    "peer-limit-per-torrent": 50,
    "peer-port": 51822,
    "peer-port-random-high": 65535,
    "peer-port-random-low": 52152,
    "peer-port-random-on-start": false,
    "peer-socket-tos": "le",
    "pex-enabled": false,
    "port-forwarding-enabled": false,
    "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": false,
    "rpc-bind-address": "0.0.0.0",
    "rpc-enabled": true,
    "rpc-host-whitelist": "",
    "rpc-host-whitelist-enabled": false,
    "rpc-password": "redacted",
    "rpc-port": 9091,
    "rpc-socket-mode": "0750",
    "rpc-url": "/transmission/",
    "rpc-username": "",
    "rpc-whitelist": "",
    "rpc-whitelist-enabled": false,
    "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": true,
    "seed-queue-size": 10,
    "speed-limit-down": 0,
    "speed-limit-down-enabled": false,
    "speed-limit-up": 0,
    "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": 14,
    "utp-enabled": false,
    "watch-dir": "/watch",
    "watch-dir-enabled": true
}

transmission block

  transmission:
    container_name: transmission
    environment:
      - TZ=Europe/Paris
      - PEERPORT=51822
      - PUID=568
      - PGID=568
    image: lscr.io/linuxserver/transmission:amd64-4.0.5-r3-ls240
    network_mode: service:gluetun
    volumes:
      - /mnt/drum/lab/sharestack/transmission:/config
      - /mnt/drum/lab/sharestack/files:/downloads
      - /mnt/drum/lab/sharestack/files/watch:/watch

flood webui Link to heading

need to know transmission’s rpc url defined in settings.json

it also need to use the same directory as transmission

check this security precautions

flood block

  flood:
    container_name: flood
    environment:
      - HOME=/config
      - TRANSMISSION_RPC_URL=http://localhost:9091/transmission/rpc
    image: jesec/flood
    network_mode: service:gluetun
    restart: unless-stopped
    user: '568:568'
    volumes:
      - /mnt/drum/lab/sharestack/transmission:/config
      - /mnt/drum/lab/sharestack/files:/downloads

caddy Link to heading

dns provider (optionnal) Link to heading
  • if you choose http challenge at some point you’ll need to open :80 tcp

  • i use ovh’s api token to manage dns-01 challenge as i don’t want to hook some :80 ports openning

it works even if your A record is *.homelab.domain.tld > localip

go to https://eu.api.ovh.com/createToken/

create an app for caddy

  • GET /domain/zone/domain.tld/*
  • PUSH /domain/zone/domain.tld/*
  • DELETE /domain/zone/domain.tld/*
building caddy (optionnal) Link to heading

if you need some plugins as dns plugin you’ll need to build caddy

/mnt/drum/lab/sharestack/caddy.build

ARG CADDY_VERSION=latest
FROM caddy:builder AS builder

RUN xcaddy build \
  --with github.com/caddy-dns/ovh

FROM caddy:${CADDY_VERSION}

COPY --from=builder /usr/bin/caddy /usr/bin/caddy
reverse proxy Link to heading

caddy act as a simple reverse proxy for flood

caddy block

  caddy:
    build:
      context: .
      dockerfile: /mnt/drum/lab/sharestack/caddy.build
    container_name: caddy
    image: caddy_custom:latest
    network_mode: service:gluetun
    restart: unless-stopped
    volumes:
      - /mnt/drum/lab/sharestack/Caddyfile:/etc/caddy/Caddyfile:ro
      - /mnt/drum/lab/sharestack/caddy/data:/data
      - /mnt/drum/lab/sharestack/caddy/config:/config
      - /mnt/drum/lab/sharestack/logs:/srv/data

all logins are logged in ~/sharestack/logs/download_access.log

Caddyfile

download.home.domain.tld {
    reverse_proxy localhost:3000
    encode zstd gzip
    tls {
        dns ovh {
            endpoint https://eu.api.ovh.com/v1
            application_key redacted
            application_secret redacted
            consumer_key redacted
        }
    }

    log {
        format json
        output file data/download_access.log {
            roll_local_time
            roll_keep     12
            roll_keep_for 365d
        }
    }
}
logging in flood Link to heading

login url is

https://download.home.domain.tld

login with

btman/password setup in settings.json

use this url as rpc url (yes it’s clear http but insde torrentnet network not exposed to lan)

http://gluetun:9091/transmission/rpc

all logins are logged in ~/sharestack/logs/download_access.log

full stack Link to heading

services:
  caddy:
    build:
      context: /mnt/drum/lab/sharestack/caddy_build/
      dockerfile: /mnt/drum/lab/sharestack/caddy.build
    container_name: caddy
    image: caddy_custom:latest
    network_mode: service:gluetun
    restart: unless-stopped
    volumes:
      - /mnt/drum/lab/sharestack/Caddyfile:/etc/caddy/Caddyfile:ro
      - /mnt/drum/lab/sharestack/caddy/data:/data
      - /mnt/drum/lab/sharestack/caddy/config:/config
      - /mnt/drum/lab/sharestack/logs:/srv/data
  flood:
    container_name: flood
    environment:
      - HOME=/config
      - TRANSMISSION_RPC_URL=http://localhost:9091/transmission/rpc
    image: jesec/flood
    network_mode: service:gluetun
    restart: unless-stopped
    user: '568:568'
    volumes:
      - /mnt/drum/lab/sharestack/transmission:/config
      - /mnt/drum/lab/sharestack/files:/downloads
  gluetun:
    cap_add:
      - NET_ADMIN
    container_name: gluetun
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - TZ=Europe/Paris
      - PUID=568
      - PGID=568
      - VPN_SERVICE_PROVIDER=airvpn
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=redacted
      - WIREGUARD_PRESHARED_KEY=redacted
      - WIREGUARD_ADDRESSES=redacted
      - SERVER_COUNTRIES=redacted
      - UPDATER_VPN_SERVICE_PROVIDERS=airvpn
      - UPDATER_PERIOD=24h
      - FIREWALL_VPN_INPUT_PORTS=51822
      - DNS_SERVER=on
      - DNS_UPSTREAM_RESOLVER_TYPE=dot
      - DNS_UPSTREAM_RESOLVERS=quad9,libredns
      - DNS_UPDATE_PERIOD=24h
      - BLOCK_SURVEILLANCE=on
      - FIREWALL_ALLOW_LAN=true
      - FIREWALL_INPUT_PORTS=443
    image: qmcgaw/gluetun
    ports:
      - '443:443'
    restart: unless-stopped
    volumes:
      - /mnt/drum/lab/sharestack/gluetun:/gluetun
      - /mnt/drum/lab/sharestack/gluetun:/tmp/gluetun
  transmission:
    container_name: transmission
    environment:
      - TZ=Europe/Paris
      - PEERPORT=51822
      - PUID=568
      - PGID=568
    image: lscr.io/linuxserver/transmission:amd64-4.0.5-r3-ls240
    network_mode: service:gluetun
    volumes:
      - /mnt/drum/lab/sharestack/transmission:/config
      - /mnt/drum/lab/sharestack/files:/downloads
      - /mnt/drum/lab/sharestack/files/watch:/watch

troubleshooting Link to heading

at containers start soem debug can be dound here on truenas scale

/var/log/app_lifecycle.log

  • check port 51822 is ok for tranmisssion

to add a counter rule on specific tcp/udp 51822

sudo docker exec gluetun iptables -I INPUT 1 -p tcp --dport 51822 -j ACCEPT
sudo docker exec gluetun iptables -I INPUT 1 -p udp --dport 51822 -j ACCEPT

check if there’s some packets

sudo docker exec gluetun iptables -L INPUT -v -n | grep 51822

erase counters

sudo docker exec gluetun iptables -Z INPUT

drink some tea

check again

sudo docker exec gluetun iptables -L INPUT -v -n | grep 51822

check fromm flood if rpc is working it rejects auth 3 failed attempts. restart daemon if several tests

curl -u username:password https://localhost/transmission/rpc

ressources Link to heading

https://forums.truenas.com/t/guide-to-installing-transmission-with-pia-and-port-forwarding-on-truenas-24-10-electriceel-and-later/22664

https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/airvpn.md

https://docs.linuxserver.io/images/docker-transmission/#via-docker-compose

https://deepwiki.com/qdm12/gluetun-wiki/3.6-airvpn

https://github.com/haugene/docker-transmission-openvpn/discussions/2660

https://www.reddit.com/r/truenas/comments/vjmvmp/comment/idkga5i/?force-legacy-sct=1

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

https://github.com/jesec/flood/issues/500

https://github.com/jesec/flood/wiki/Security-precautions