diff --git a/Dockerfile b/Dockerfile index 3351378..087cdb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ FROM alpine:3.22 ENV LOCAL_IPV4_SUBNETS="192.168.0.0/16" +ENV MANAGE_WIREGUARD="true" ENV TZ="UTC" ENV WEBUI_HOST="http://localhost:8080" ENV WIREGUARD_INTERFACE="wg0" +ENV GATEWAY_IP="10.2.0.1" RUN apk add --update --no-cache \ bash ca-certificates curl iproute2 iptables ip6tables jq libnatpmp tzdata wireguard-tools \ @@ -13,6 +15,6 @@ RUN apk add --update --no-cache \ COPY entrypoint.sh /entrypoint.sh HEALTHCHECK --start-period=15s --interval=60s --timeout=10s --retries=3 \ - CMD ping -c 1 10.2.0.1 || exit 1 + CMD ping -c 1 "${GATEWAY_IP:-10.2.0.1}" || exit 1 ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 37b5f3c..5d41ebe 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,15 @@ Tools ### Recommended Environment Variables -| Variable | Default | Examples | Description | -| -------- | ------- | ----- | ---------- | -| `TZ` | `UTC` | `America/Denver` | Set your [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) to make sure logs rotate at local midnight instead of at UTC midnight. -| `WIREGUARD_INTERFACE` | `wg0` | `wg0`, `wg1`, ... | Set the wireguard interface name to use. -| `LOCAL_IPV4_SUBNETS` | `192.168.0.0/16` | `192.168.0.0/16, 10.0.0.0/8` | Comma separated list of local subnet CIDRs to be allowed outside the wireguard tunnel. -| `WEBUI_HOST` | `http://localhost:8080` | | URL to the qBittorrent Web UI. Authentication must be disabled to localhost connections unless `WEBUI_USERNAME` and `WEBUI_PASSWORD` are set. -| `WEBUI_USERNAME` | | `admin` | Optional qBittorrent Web UI username. Use this if localhost authentication bypass is not enabled. -| `WEBUI_PASSWORD` | | `adminadmin` | Optional qBittorrent Web UI password. Use this if localhost authentication bypass is not enabled. +| Variable | Default | Examples | Description | +|-----------------------|-------------------------|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TZ` | `UTC` | `America/Denver` | Set your [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) to make sure logs rotate at local midnight instead of at UTC midnight. | +| `MANAGE_WIREGUARD` | `true` | `false` | Set to `false` when this container should only manage Proton NAT-PMP and qBittorrent while sharing another WireGuard container's network namespace. | +| `WIREGUARD_INTERFACE` | `wg0` | `wg0`, `wg1`, ... | Set the wireguard interface name to use. | +| `GATEWAY_IP` | `10.2.0.1` | `10.2.0.1` | Proton VPN NAT-PMP gateway IP. | +| `LOCAL_IPV4_SUBNETS` | `192.168.0.0/16` | `192.168.0.0/16, 10.0.0.0/8` | Comma separated list of local subnet CIDRs to be allowed outside the wireguard tunnel. | +| `WEBUI_HOST` | `http://localhost:8080` | | URL to the qBittorrent Web UI. Authentication must be disabled to localhost connections unless `WEBUI_USERNAME` and `WEBUI_PASSWORD` are set. | +| `QBITTORRENT_HOST` | | `localhost` | Alternative way to set the qBittorrent Web UI host. Used with `QBITTORRENT_PORT`. | +| `QBITTORRENT_PORT` | | `8088` | Alternative way to set the qBittorrent Web UI port. Used with `QBITTORRENT_HOST`. | +| `WEBUI_USERNAME` | | `admin` | Optional qBittorrent Web UI username. Use this if localhost authentication bypass is not enabled. | +| `WEBUI_PASSWORD` | | `adminadmin` | Optional qBittorrent Web UI password. Use this if localhost authentication bypass is not enabled. | diff --git a/entrypoint.sh b/entrypoint.sh index fb4e446..32e8cbc 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -11,93 +11,104 @@ fatal() { exit 1 } -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 - +MANAGE_WIREGUARD="${MANAGE_WIREGUARD:-true}" WIREGUARD_INTERFACE="${WIREGUARD_INTERFACE:-wg0}" +GATEWAY_IP="${GATEWAY_IP:-10.2.0.1}" -if [ ! -f "/etc/wireguard/${WIREGUARD_INTERFACE}.conf" ]; then - fatal "Error: Configuration file /etc/wireguard/${WIREGUARD_INTERFACE}.conf does not exist" +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}" + wg show ${WIREGUARD_INTERFACE} || true 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 - sleep 8 & wait ${!} # check to see if tunnel allows port forwarding -natpmpc -g 10.2.0.1 +natpmpc -g "${GATEWAY_IP}" # give some delay until qbittorrent container launches sleep 5 & wait ${!} # qbittorrent webui host -WEBUI_HOST="${WEBUI_HOST:-http://localhost:8080}" +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" @@ -138,10 +149,10 @@ 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 10.2.0.1) + 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 10.2.0.1) + 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