HOWTO: Install Transmission on OpenBSD 7.7
In this HOWTO I'll explain the process of installing Transmission on OpenBSD with a WireGuard VPN to conceal our real IP addresses. I'll use OpenBSD's acme-client
to fetch a TLS certificate from Let's Encrypt, OpenBSD's httpd
to serve a homepage and the downloads directory, and OpenBSD's relayd
to forward HTTP requests to transmission-daemon
or httpd
depending on the URL.
I'm assuming this will run on a home network, where IPv6 will simplify things since we won't need to worry about our internal vs external IPv4 addresses being different, and having DNS point to the same server.
Installing OpenBSD
You can mostly use the default options. I'll note where that differs.
- I'll set my hostname to
torrent00
(I'll usethis style
throughout this HOWTO for any values which you should change to suit your situation) - I'm assigning my DNS records to point at an IPv6 address, so let's use
autoconf
when prompted for both IPv4 and IPv6 configurations - I'll set the root user's password to
foobar
since I'll never be authenticating with that account (except via the VM's console) - When prompted, I'll create a user called
openbsd
with no password - Ensure that
sshd
is enabled during the installation -
At the end of the installation, I'll choose the
(S)hell
option to do some extra configuration before rebooting:- Run
echo torrent00.mydomain.invalid > /mnt/etc/myname
to tell OpenBSD your FQDN - If you're installing in a VM, you may want to go ahead and set up your SSH key before first boot by running
cat > /mnt/home/openbsd/.ssh/authorized_keys
and then pasting in your public key (press^D
on an empty line to finish writing the file) - Run
chroot /mnt /usr/sbin/user mod -G wheel openbsd
to add the user to the wheel group - Run
echo permit nopass :wheel > /mnt/etc/doas.conf
to allow members of the wheel group to usedoas
without a password - Run
echo inet6 alias 2001:db8::1234/128 > /mnt/etc/hostname.vio0
to set a static GUA, ensuring that2001:db8::1234
is an address within the range your router can use, and settingvio0
to the name of the interface - Create a DNS record to point the AAAA record of
torrent00.mydomain.invalid
at your address. You'll then be able to usessh openbsd@torrent00.mydomain.invalid
to SSH in
- Run
First boot and miscellaneous configuration
- Run
doas syspatch
becauserc.firsttime
says you should - Use Quad9's unfiltered DNS resolver:
echo -en 'nameserver 2620:fe::fe\nnameserver 9.9.9.10\nlookup file bind\n' | doas tee /etc/resolv.conf
- Install additional packages:
doas pkg_add transmission wireguard-tools
- You'll need to configure your firewall to allow incoming connections to
torrent00.mydomain.invalid
port 80, TCP. This is necessary for getting a certificate using the ACME protocol
Configuring httpd, relayd, acme-client, and Transmission
doas mkdir /var/www/htdocs/downloads
doas chown _transmission:_transmission /var/www/htdocs/downloads
doas rcctl set transmission_daemon flags '--download-dir=/var/www/htdocs/downloads --global-seedratio=5'
cat <<EOF | doas tee /etc/acme-client.conf authority letsencrypt { api url "https://acme-v02.api.letsencrypt.org/directory" account key "/etc/acme/letsencrypt-privkey.pem" } domain torrent00.mydomain.invalid { domain key "/etc/ssl/private/torrent00.mydomain.invalid.key" domain full chain certificate "/etc/ssl/torrent00.mydomain.invalid.crt" sign with letsencrypt } EOF
cat <<EOF | doas tee /etc/httpd.conf # Handle ACME challenges and redirect to https:// server "torrent00.mydomain.invalid" { listen on * port 80 location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } location * { block return 302 "https://\$HTTP_HOST\$REQUEST_URI" } } # Serve the homepage and downloads directory server "torrent00.mydomain.invalid" { listen on * port 8080 location "/downloads/*" { directory auto index } } EOF
cat <<EOF | doas tee /etc/relayd.conf table <transmission> { 127.0.0.1 } table <httpd> { ::1 } http protocol https { tls { keypair torrent00.mydomain.invalid } match request path "/" header set "Host" value "[::1]:8080" match request path "/" forward to <httpd> match request path "/downloads/*" header set "Host" value "[::1]:8080" match request path "/downloads/*" forward to <httpd> match request path "/transmission/*" header set "Host" value "127.0.0.1:9091" match request path "/transmission/*" forward to <transmission> } relay httpproxy { protocol https listen on :: port 443 tls forward to <httpd> port 8080 forward to <transmission> port 9091 } EOF
cat <<EOF | doas tee /var/www/htdocs/index.html <title>torrent00.mydomain.invalid</title> <link rel="icon" href="/transmission/web/images/favicon.png"> <h1>torrent00.mydomain.invalid</h1> <ul> <li><a href="/transmission/web/">Transmission</a></li> <li><a href="/downloads/">Downloads</a></li> </ul> EOF
doas rcctl enable relayd httpd transmission_daemon
doas rcctl start httpd transmission_daemon
-
Set up acme-client to run once an hour, at a random minute:
doas crontab -e
Then add the following:
~ * * * * acme-client torrent00.mydomain.invalid && rcctl reload relayd httpd
doas acme-client torrent00.mydomain.invalid && doas rcctl reload httpd && doas rcctl start relayd
Configuring WireGuard
doas mkdir /etc/wireguard
-
doas cp my-wireguard-config.conf /etc/wireguard/wg0.conf
Note: While writing this guide, I found that whenEndpoint
in the configuration file is an IPv6 address, WireGuard doesn't work. doas chmod 600 /etc/wireguard/wg0.conf
cat <<EOF | doas tee /etc/rc.d/wireguard #!/bin/ksh daemon="/usr/local/bin/wg-quick" daemon_flags="wg0" . /etc/rc.d/rc.subr rc_start() { "\${daemon}" up "\${daemon_flags}" } rc_stop() { "\${daemon}" down "\${daemon_flags}" } rc_check() { ifconfig "\${daemon_flags}" } rc_reload() { rc_stop rc_start } rc_cmd \$1 EOF
doas chmod 755 /etc/rc.d/wireguard
doas rcctl enable wireguard
doas rcctl start wireguard
- Verify your IPv4 and IPv6 addresses have changed:
ftp -VMo- https://ip4only.me/api/ ; ftp -VMo- https://ip6only.me/api/
Configuring the packet filter
This is necessary to prevent Transmission from accessing the internet when WireGuard is down.
cat <<EOF | doas tee -a /etc/pf.conf # Block all incoming connections from wg0 block in on wg0 # Prevent leaks from Transmission block proto tcp user _transmission block proto udp user _transmission pass on wg0 proto tcp user _transmission pass on wg0 proto udp user _transmission EOF
- Reload PF's configuration with
doas pfctl -f /etc/pf.conf
-
Now we'll turn off WireGuard to check that the Transmission user can't call out:
doas rcctl stop wireguard
doas -u _transmission sh -c 'ftp -VMo- https://ip4only.me/api/ ; ftp -VMo- https://ip6only.me/api/'
And you should see:
ftp: ip4only.me: no address associated with name ftp: ip6only.me: no address associated with name
-
Then we'll switch WireGuard back on and make sure we see our VPN's IP address:
doas rcctl start wireguard
doas -u _transmission sh -c 'ftp -VMo- https://ip4only.me/api/ ; ftp -VMo- https://ip6only.me/api/'
Finishing touches
- Run
ln -s /var/www/htdocs/downloads
to put a symlink to the downloads directory in~
cat <<EOF > test.sh #!/bin/ksh whoami ftp -VMo- http://ip6only.me/api/ | cut -d, -f2 ftp -VMo- http://ip4only.me/api/ | cut -d, -f2 doas -u _transmission whoami doas -u _transmission ftp -VMo- http://ip6only.me/api/ | cut -d, -f2 doas -u _transmission ftp -VMo- http://ip4only.me/api/ | cut -d, -f2 EOF
chmod 755 test.sh
doas reboot
to ensure when we reboot everything comes up- One final test:
./test.sh && doas rcctl stop wireguard && ./test.sh && doas rcctl start wireguard
- Now your Transmission server is running at
https://torrent00.mydomain.invalid
!