
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
watchwill 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://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/