#!/usr/bin/env bash set -o errexit info() { printf "I %5d %s\n" "${$}" "${*}" } fatal() { printf "F %5d %s\n" "${$}" "${*}" >&2 exit 1 } MANAGE_WIREGUARD="${MANAGE_WIREGUARD:-true}" WIREGUARD_INTERFACE="${WIREGUARD_INTERFACE:-wg0}" GATEWAY_IP="${GATEWAY_IP:-10.2.0.1}" if [[ "${MANAGE_WIREGUARD}" == "true" ]]; then iptables-save | tee /tmp/rules.v4.$$.conf ip6tables-save | tee /tmp/rules.v6.$$.conf default_route_ip=$(ip route | grep '^default' | awk '{print $3}') if [[ -z "${default_route_ip}" ]]; then fatal "Error: No default route configured" fi if [[ "$(cat /proc/sys/net/ipv4/conf/all/src_valid_mark)" != "1" ]]; then fatal "Error: sysctl net.ipv4.conf.all.src_valid_mark=1 is not set" fi # sysctl is set by container, wg-quick will then error sed -i "s:sysctl -q net.ipv4.conf.all.src_valid_mark=1:echo Skipping setting net.ipv4.conf.all.src_valid_mark:" /usr/bin/wg-quick if [ ! -f "/etc/wireguard/${WIREGUARD_INTERFACE}.conf" ]; then fatal "Error: Configuration file /etc/wireguard/${WIREGUARD_INTERFACE}.conf does not exist" fi info "Bringing up wireguard interface: ${WIREGUARD_INTERFACE}..." wg-quick up ${WIREGUARD_INTERFACE} shutdown () { info "shutting down wireguard interface: ${WIREGUARD_INTERFACE}..." wg-quick down ${WIREGUARD_INTERFACE} info "restoring iptables..." iptables-restore -n < /tmp/rules.v4.$$.conf info "restoring ip6tables..." ip6tables-restore -n < /tmp/rules.v6.$$.conf } trap shutdown EXIT wg show WIREGUARD_FWMARK=$(wg show ${WIREGUARD_INTERFACE} fwmark) # allow connections from container subnets for container_subnet in $(ip -o addr show | awk '/^(\d)+: eth(.+)inet / {print $4}') do iptables -I INPUT -s ${container_subnet} -j ACCEPT iptables -I OUTPUT -d ${container_subnet} -j ACCEPT done # allow connections to local subnets specified by user, need to add routes since wireguard interface has 0.0.0.0/0 allowed ips for local_subnet in ${LOCAL_IPV4_SUBNETS//,/$IFS} do iptables -I INPUT -s ${local_subnet} -j ACCEPT iptables -I OUTPUT -d ${local_subnet} -j ACCEPT ip route add ${local_subnet} via ${default_route_ip} done # established connections iptables -I INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT iptables -I OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # kill switches for ipv4 -- @see wg-quick(8) iptables -A OUTPUT ! -o ${WIREGUARD_INTERFACE} -m mark ! --mark ${WIREGUARD_FWMARK} -m addrtype ! --dst-type LOCAL -j REJECT --reject-with icmp-admin-prohibited for container_subnet in $(ip -o addr show | awk '/^(\d)+: eth(.+)inet6 / {print $4}') do ip6tables -I INPUT -s ${container_subnet} -j ACCEPT ip6tables -I OUTPUT -d ${container_subnet} -j ACCEPT done # established connections ip6tables -I INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT ip6tables -I OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # kill switches for ipv6 -- @see wg-quick(8) ip6tables -A OUTPUT ! -o ${WIREGUARD_INTERFACE} -m mark ! --mark ${WIREGUARD_FWMARK} -m addrtype ! --dst-type LOCAL -j REJECT --reject-with icmp6-adm-prohibited iptables-save | tee /tmp/rules.v4.${WIREGUARD_INTERFACE}.$$.conf ip6tables-save | tee /tmp/rules.v6.${WIREGUARD_INTERFACE}.$$.conf else info "Using externally managed wireguard interface: ${WIREGUARD_INTERFACE}" fi sleep 8 & wait ${!} # check to see if tunnel allows port forwarding natpmpc -g "${GATEWAY_IP}" # give some delay until qbittorrent container launches sleep 5 & wait ${!} # qbittorrent webui host if [[ -n "${QBITTORRENT_HOST:-}" || -n "${QBITTORRENT_PORT:-}" ]]; then WEBUI_HOST="http://${QBITTORRENT_HOST:-localhost}:${QBITTORRENT_PORT:-8080}" else WEBUI_HOST="${WEBUI_HOST:-http://localhost:8080}" fi WEBUI_USERNAME="${WEBUI_USERNAME:-}" WEBUI_PASSWORD="${WEBUI_PASSWORD:-}" WEBUI_COOKIE_JAR="/tmp/qbittorrent-webui.$$.cookie" qb_webui_curl() { curl \ --silent \ --show-error \ --fail-with-body \ --header "Referer: ${WEBUI_HOST}" \ ${WEBUI_COOKIE_ARGS+"${WEBUI_COOKIE_ARGS[@]}"} \ "$@" } # from this point on, its best effort, so just keep going and don't fail the container set +o errexit if [[ -n "${WEBUI_USERNAME}" || -n "${WEBUI_PASSWORD}" ]]; then login_response=$( curl \ --silent \ --show-error \ --fail-with-body \ --header "Referer: ${WEBUI_HOST}" \ --cookie-jar "${WEBUI_COOKIE_JAR}" \ --data-urlencode "username=${WEBUI_USERNAME}" \ --data-urlencode "password=${WEBUI_PASSWORD}" \ "${WEBUI_HOST}/api/v2/auth/login" ) if [[ "${login_response}" == "Ok." ]]; then WEBUI_COOKIE_ARGS=(--cookie "${WEBUI_COOKIE_JAR}") info "Authenticated with qBittorrent Web UI." else info "Warning: qBittorrent Web UI login failed: ${login_response}" fi fi # loop now forever keeping port forward up to date (protonvpn) # https://protonvpn.com/support/port-forwarding-manual-setup while true; do tcp_output=$(natpmpc -a 1 0 tcp 60 -g "${GATEWAY_IP}") tcp_port=$(echo "${tcp_output}" | sed -n 's/.*Mapped public port \([0-9]\+\).*/\1/p') udp_output=$(natpmpc -a 1 0 udp 60 -g "${GATEWAY_IP}") udp_port=$(echo "${udp_output}" | sed -n 's/.*Mapped public port \([0-9]\+\).*/\1/p') if ! [[ "${tcp_port}" =~ ^[0-9]+$ && "${udp_port}" =~ ^[0-9]+$ ]]; then info "Warning: unable to parse forwarded ports. tcp='${tcp_port}' udp='${udp_port}'" sleep 45 & wait ${!} continue fi if [[ "${tcp_port}" -ne "${udp_port}" ]]; then info "Warning: tcp_port (${tcp_port}) and udp_port (${udp_port}) are different" fi # failure to connect to webui, we don't want to fail the loop, just log the error and try again preferences=$(qb_webui_curl "${WEBUI_HOST}/api/v2/app/preferences") if [[ "${?}" -ne 0 ]]; then info "Warning: unable to read qBittorrent preferences from ${WEBUI_HOST}" sleep 45 & wait ${!} continue fi current_port=$(echo "${preferences}" | jq -r .listen_port) random_port=$(echo "${preferences}" | jq -r .random_port) upnp=$(echo "${preferences}" | jq -r .upnp) if ! [[ "${current_port}" =~ ^[0-9]+$ ]]; then info "Warning: qBittorrent preferences did not contain a numeric listen_port: ${current_port}" sleep 45 & wait ${!} continue fi if [[ "${tcp_port}" -ne "${current_port}" || "${random_port}" != "false" || "${upnp}" != "false" ]]; then info "Port settings changed from listen_port=${current_port}, random_port=${random_port}, upnp=${upnp} to listen_port=${tcp_port}, random_port=false, upnp=false. Updating app preferences..." qb_webui_curl \ --request POST \ --data "json={\"listen_port\": ${tcp_port}, \"random_port\": false, \"upnp\": false}" \ "${WEBUI_HOST}/api/v2/app/setPreferences" if [[ "${?}" -ne 0 ]]; then info "Warning: unable to update qBittorrent port preferences" sleep 45 & wait ${!} continue fi updated_port=$(qb_webui_curl "${WEBUI_HOST}/api/v2/app/preferences" | jq -r .listen_port) if [[ "${updated_port}" != "${tcp_port}" ]]; then info "Warning: qBittorrent still reports listen_port=${updated_port} after update" fi fi sleep 45 & wait ${!} done info "exiting." exit 0