This page looks best with JavaScript enabled

Headless BitTorrent client in FreeBSD jail.

 ·  🎃 kr0m

One of the fastest and easiest ways to download files is through BitTorrent, but there is content that only a small number of people have and the download can take hours or even days. In these cases, it is worth having a dedicated download server exclusively for this task, so it will not be necessary to leave our computer on all that time. This article will explain how to install a headless transmission server, how to configure a client to manage downloads from our PC, and how to share downloaded content using ZFS-NFS4.

The article is composed of different sections:


Jail mounting and parent configuration:

For greater versatility and convenience, we are going to mount the server on an Iocage jail:

iocage create -r LATEST -n Lomax boot=on
iocage set ip4_addr=“nfe0|192.168.69.7/24” Lomax
iocage list

+-----+---------+-------+--------------+--------------+  
| JID |  NAME   | STATE |   RELEASE    |     IP4      |  
+=====+=========+=======+==============+==============+  
| 10  | Lomax   | up    | 12.2-RELEASE | 192.168.69.7 |  
+-----+---------+-------+--------------+--------------+

Transmission requires higher than normal UDP buffers, if we don’t increase them we will see the following error in the messages log when starting the service:

Nov 17 22:10:19 Lomax transmission-daemon[2775]: UDP Failed to set receive buffer: requested 4194304, got 42080 (/wrkdirs/usr/ports/net-p2p/transmission-daemon/work/transmission-3.00/libtransmission/tr-udp.c:97)

FreeBSD by default has the following values:

sysctl -a|grep kern.ipc.maxsockbuf

kern.ipc.maxsockbuf: 2097152  
sysctl -a|grep net.inet.udp.recvspace
net.inet.udp.recvspace: 42080

We modify them through the sysctl.conf file:

vi /etc/sysctl.conf

kern.ipc.maxsockbuf=5242880  
net.inet.udp.recvspace=4194304

We apply the changes:

service sysctl restart

Note that we are modifying the parameters for all jails, in the jail itself it is not possible to do so since the kernel is shared, if we try we will get the following error message:

service sysctl restart

sysctl: kern.ipc.maxsockbuf=5242880 at line 10: Operation not permitted  
sysctl: net.inet.udp.recvspace=4194304 at line 11: Operation not permitted

Download directory:

We create a dataset with compression enabled, this directory will be linked in the jail and will serve as the download directory:

zfs create -o compress=lz4 storage/torrents

We access the jail and assign a password to the root account:

iocage console Lomax
passwd

We create the download directory:

mkdir -p /storage/torrents
exit

We link the dataset with the jail’s download directory:

iocage fstab -a Lomax “/storage/torrents /storage/torrents nullfs rw 0 0”
iocage fstab -l Lomax

+-------+---------------------------------------------------------------------------------------------------+  
| INDEX |                                            FSTAB ENTRY                                            |  
+=======+===================================================================================================+  
| 0     | /storage/torrents       /zroot/iocage/jails/Lomax/root/storage/torrents nullfs  rw      0       0 |  
+-------+---------------------------------------------------------------------------------------------------+

We restart the jail:

iocage stop Lomax
iocage start Lomax


Minimum jail configuration:

We access the jail to install the base software and add a user:

iocage console Lomax

We install some basic programs and Python:

pkg update
pkg install vim htop bash screen py37-pip curl

We install the requests package using pip:

pip install requests

We enable and start the Ssh service:

sysrc sshd_enable="yes"
service sshd start

We create a regular user with whom we will operate:

adduser

Login group is kr0m. Invite kr0m into other groups? []: wheel  
Shell (sh csh tcsh bash rbash nologin) [sh]: bash

Transmission installation and configuration:

Now that we have our user and Ssh enabled, we can continue with the rest of the steps from an Ssh console:

ssh kr0m@192.168.69.7 -p22
su -l

We install transmission:

pkg install transmission

We can see the configuration parameters allowed in the rc.conf file in the following file:

cat /usr/local/etc/rc.d/transmission

In our case, we only need to modify the download directory:

sysrc transmission_download_dir=/storage/torrents

Regarding the flags, we will have to consult the man:

man transmission-daemon

In my case, I have to allow access to the ip addresses of the jail itself and my pc, in addition to ip filtering, we assign a user and password:

sysrc transmission_flags="-a 192.168.69.4,192.168.69.7 -t -u kr0m -v PASSWORD"

NOTE: We should never restart the service, as it seems not to reload the configuration, so we will do a stop/start.

We will modify the rest of the configuration parameters in the settings.json file, stopping the service beforehand:

service transmission stop
vi /usr/local/etc/transmission/home/settings.json
service transmission start

Solo modify the parameters that we cannot modify by the two methods described above, if we do not respect this rule, transmission will overwrite our configuration.

We change the permissions of the download directory:

chown -R transmission:transmission /storage/torrents/

We enable and start the service:

sysrc transmission_enable=YES
service transmission start

We check that it has started with the correct parameters:

ps auxwww|grep trans

transmission 89161  0.4  0.3 41292 12172  -  SsJ  12:59   0:00.07 /usr/local/bin/transmission-daemon -g /usr/local/etc/transmission/home -w /storage/torrents -x /var/run/transmission/daemon.pid -a 192.168.69.4,192.168.69.7 -t -u kr0m -v PASSWORD

Installation, configuration, and management of the client:

We install Transmission on the PC to be able to manage downloads remotely:

pkg install transmission

We check that both the IP filter and the user/password work:

transmission-remote 192.168.69.7 –auth kr0m:PASSWORD -l

ID   Done       Have  ETA           Up    Down  Ratio  Status       Name  
Sum:                None               0.0     0.0

Adding a torrent is as simple as indicating its URL:

We can also pass it a magnet link:

transmission-remote 192.168.69.7 –auth kr0m:PASSWORD -a “magnet:?xt=urn:btih:E1718B4288E1CD1C200AE8219E81D85EB1D9F0E8&dn=Lynda+-+Learning+Debian+Linux+%5BAhLaN%5D&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2710%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.uw0.xyz%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.tiny-vps.com%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.demonii.si%3A1337%2Fannounc4&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.kamigami.org%3A2710%2Fannounce&tr=udp%3A%2F%2Fretracker.akado-ural.ru%3A80%2Fannounce&tr=udp%3A%2F%2Fopentor.org%3A2710%2Fannounce&tr=udp%3A%2F%2Ftracker.lelux.fi%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce”

Checking the download status:

transmission-remote 192.168.69.7 –auth kr0m:PASSWORD -l

    ID   Done       Have  ETA           Up    Down  Ratio  Status       Name  
     1     0%       None  Unknown      0.0     0.0   None  Idle         debian-10.6.0-arm64-xfce-CD-1.iso  
     2    n/a       None  Unknown      0.0     0.0   None  Idle         Lynda+-+Learning+Debian+Linux+[AhLaN]  
Sum:                None               0.0     0.0

To remove a torrent, we must pass the ID of the torrent to be removed and the -r parameter:

transmission-remote 192.168.69.7 –auth kr0m:PASSWORD -t 1 -r

Multiple torrents can be removed with a single command:

transmission-remote 192.168.69.7 –auth kr0m:PASSWORD -t 1,2 -r

To operate with Transmission in a simpler way, we can create the following alias:

vi .bashrc

alias tsm="transmission-remote 192.168.69.7 --auth kr0m:PASSWORD"

Now checking the torrents is as simple as this:

tsm -l

    ID   Done       Have  ETA           Up    Down  Ratio  Status       Name  
     1    n/a       None  Unknown      0.0     0.0   None  Idle         Lynda+-+Learning+Debian+Linux+[AhLaN]  
     3    54%   398.4 MB  1 min        0.0  11717.0    0.0  Downloading  debian-10.6.0-arm64-xfce-CD-1.iso  
Sum:            462.2 MB               0.0  15387.0

NFS Access:

One of the advantages of serving downloaded content via NFS is that if it is a multimedia file, we can stream it directly from the server without having to copy the file to our computer.

NFS access will be done from the parent of the jail, we enable the NFS4 service and start it:

sysrc nfs_server_enable="yes"
sysrc nfsv4_server_enable="yes"
sysrc nfsuserd_enable="yes"
service nfsd start

We share the directory restricting access by IP:

zfs set sharenfs="-mapall=root,-network=192.168.69.4/32" storage/torrents

NOTE: With the mapall=root option, we make NFS accesses with the root user, all accesses from clients have the root permissions of the remote server, if we did not configure it, we should have the same users with the same UIDs/GIDs both on the NFS server and on all clients to which we give access.

My first idea was to map it to the transmission user and not to root, but since we share it from the parent, the transmission user does not exist and NFS4 no longer allows mapping by UID :

uid/gid numbers are no longer used in the NFSv4 protocol


Firewall:

We configure the firewall rules so that only our PC has access to NFS and the Transmission RPC configuration service. To make the rules clearer, I indicate below the correspondence of the IP addresses:

  • PC: 192.168.69.4
  • Father: 192.168.69.2
  • Jail: 192.168.69.7
vi /etc/ipfw.rules
# Torrent NFS access:
# NFS access:
# NFS: Port tcp/udp 111
$cmd 00805 allow tcp from 192.168.69.4 to 192.168.69.2 111 in via $wanif
$cmd 00805 allow tcp from 192.168.69.2 111 to 192.168.69.4 out via $wanif
$cmd 00805 allow udp from 192.168.69.4 to 192.168.69.2 111 in via $wanif
$cmd 00805 allow udp from 192.168.69.2 111 to 192.168.69.4 out via $wanif
$cmd 00805 deny tcp from any to 192.168.69.2 111 in via $wanif
$cmd 00805 deny udp from any to 192.168.69.2 111 in via $wanif

# NFS: Port tcp/udp 950
$cmd 00805 allow tcp from 192.168.69.4 to 192.168.69.2 950 in via $wanif
$cmd 00805 allow tcp from 192.168.69.2 950 to 192.168.69.4 out via $wanif
$cmd 00805 allow udp from 192.168.69.4 to 192.168.69.2 950 in via $wanif
$cmd 00805 allow udp from 192.168.69.2 950 to 192.168.69.4 out via $wanif
$cmd 00805 deny tcp from any to 192.168.69.2 950 in via $wanif
$cmd 00805 deny udp from any to 192.168.69.2 950 in via $wanif

# NFS: Port tcp/udp 2049
$cmd 00805 allow tcp from 192.168.69.4 to 192.168.69.2 2049 in via $wanif
$cmd 00805 allow tcp from 192.168.69.2 2049 to 192.168.69.4 out via $wanif
$cmd 00805 allow udp from 192.168.69.4 to 192.168.69.2 2049 in via $wanif
$cmd 00805 allow udp from 192.168.69.2 2049 to 192.168.69.4 out via $wanif
$cmd 00805 deny tcp from any to 192.168.69.2 2049 in via $wanif
$cmd 00805 deny udp from any to 192.168.69.2 2049 in via $wanif

# NFS: Port tcp/udp 882
$cmd 00805 allow tcp from 192.168.69.4 to 192.168.69.2 882 in via $wanif
$cmd 00805 allow tcp from 192.168.69.2 882 to 192.168.69.4 out via $wanif
$cmd 00805 allow udp from 192.168.69.4 to 192.168.69.2 882 in via $wanif
$cmd 00805 allow udp from 192.168.69.2 882 to 192.168.69.4 out via $wanif
$cmd 00805 deny tcp from any to 192.168.69.2 882 in via $wanif
$cmd 00805 deny udp from any to 192.168.69.2 882 in via $wanif

# Transmission Lomax RPC:
$cmd 00805 allow tcp from 192.168.69.4 to 192.168.69.7 9091 in via $wanif
$cmd 00805 allow tcp from 192.168.69.7 9091 to 192.168.69.4 out via $wanif
$cmd 00805 deny tcp from any to 192.168.69.7 9091 in via $wanif

# All other Lomax traffic
$cmd 00805 allow all from any to 192.168.69.7 any in via $wanif
$cmd 00805 allow all from 192.168.69.7 any to any out via $wanif

We restart the ipfw service:

service ipfw restart


NFS client access:

From my PC, we can see the resources served by NFS:

showmount -e 192.168.69.2

Exports list on 192.168.69.2:  
/storage/torrents                  192.168.69.4

We mount the /storage/torrents resource:

mkdir -p /nfs/torrents
mount 192.168.69.2:/storage/torrents /nfs/torrents

We can automate the process through a simple script, but we will have to grant some permissions to our user:

visudo

kr0m ALL=(ALL) NOPASSWD: /sbin/mount 192.168.69.2\:/storage/torrents /nfs/torrents  
kr0m ALL=(ALL) NOPASSWD: /sbin/umount /nfs/torrents

We will generate the script from our user kr0m:

vi nfs.sh

#!/usr/local/bin/bash
mount|grep '192.168.69.2:/storage/torrents'
if [ $? -eq 1 ]; then
  echo -e "-- Mounting NFS: /nfs/torrents"
  sudo /sbin/mount 192.168.69.2:/storage/torrents /nfs/torrents
else
  echo -e "-- Umouning NFS: /nfs/torrents"
  sudo /sbin/umount /nfs/torrents 
fi

We assign the necessary permissions:

chmod 700 nfs.sh


Download script:

Transmission is capable of executing a script every time a download finishes. Our script will send a Telegram with the name of the recently downloaded torrent and remove it from the list of torrents:

vi /usr/local/etc/transmission/home/notifyTorrentDownload.sh

#!/usr/local/bin/bash
function sendTelegram {
  message=${@:1}
  /usr/local/bin/curl -s -X POST https://api.telegram.org/botAPI_KEY/sendMessage -d chat_id=CHAT_ID -d text="$message"
}

tsm='/usr/local/bin/transmission-remote 192.168.69.7 --auth kr0m:PASSWORD'

DOWNLOADED=$($tsm -l | /usr/bin/awk '$2 == "100%" {print $10" "$11" "$12" "$13" "$14" "$15" "$16" "$17" "$18" "$19" "$20}')
IFS_ORI=$IFS
IFS=$'\n'
for TORRENT in $DOWNLOADED; do
	/bin/echo "TORRENT: $TORRENT"
  msg="Downloaded: $TORRENT"
  sendTelegram $msg
done

IFS=$IFS_ORI
DOWNLOADED_IDS=$($tsm -l | /usr/bin/awk '$2 == "100%" {print $1}')
for ID in $DOWNLOADED_IDS; do
	/bin/echo "ID: $ID"
  $tsm -t $ID -r
done

We assign the necessary permissions and change the owner of the script:

chmod 700 /usr/local/etc/transmission/home/notifyTorrentDownload.sh
chown transmission:transmission /usr/local/etc/transmission/home/notifyTorrentDownload.sh

We stop the service:

service transmission stop

We modify the configuration to take into account the script:

vi /usr/local/etc/transmission/home/settings.json

"script-torrent-done-enabled": true,  
"script-torrent-done-filename": "/usr/local/etc/transmission/home/notifyTorrentDownload.sh",

We start the service:

service transmission start

When a torrent finishes, we will receive a notification via Telegram similar to the following:


Debug:

We stop the service:

service transmission stop

We start the service in foreground as root:

/usr/local/bin/transmission-daemon -g /usr/local/etc/transmission/home -f –log-debug

When we finish, we will have to rectify some file permissions generated while the process was running as root:

chown transmission:wheel /var/run/transmission/daemon.pid
chown -R transmission:transmission /storage/torrents/

We start the service regularly:

service transmission start

Transmission writes its logs in /var/log/messages, so we check in this file that no errors appear:

tail -f /var/log/messages

It is also possible to execute commands with the transmission user, but for this we will have to temporarily change the shell, execute the command, and revert the change:

chsh -s /usr/local/bin/bash transmission
su transmission -c “/usr/local/etc/transmission/home/notifyTorrentDownload.sh”
chsh -s /usr/sbin/nologin transmission

If you liked the article, you can treat me to a RedBull here