From c0539ea87d22882e5b52e7c22b1f3e0afa659525 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:28:20 +0100 Subject: [PATCH] Handle IPv6-less hosts in ip6tables shim --- qbittorrent/CHANGELOG.md | 24 ++++++ qbittorrent/Dockerfile | 2 +- qbittorrent/README.md | 21 ++++- qbittorrent/config.yaml | 6 +- .../rootfs/etc/cont-init.d/93-openvpn.sh | 12 ++- .../rootfs/etc/cont-init.d/94-wireguard.sh | 86 +++++++++++++++++++ .../s6-overlay/s6-rc.d/svc-qbittorrent/run | 59 +++++++++++-- qbittorrent/rootfs/etc/services.d/nginx/run | 5 +- qbittorrent/rootfs/usr/local/bin/resolvconf | 86 +++++++++++++++++++ .../rootfs/usr/local/sbin/ip6tables-restore | 73 ++++++++++++++++ .../rootfs/usr/local/sbin/iptables-restore | 52 +++++++++++ qbittorrent/rootfs/usr/local/sbin/sysctl | 17 ++++ 12 files changed, 429 insertions(+), 14 deletions(-) create mode 100755 qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh create mode 100755 qbittorrent/rootfs/usr/local/bin/resolvconf create mode 100755 qbittorrent/rootfs/usr/local/sbin/ip6tables-restore create mode 100755 qbittorrent/rootfs/usr/local/sbin/iptables-restore create mode 100755 qbittorrent/rootfs/usr/local/sbin/sysctl diff --git a/qbittorrent/CHANGELOG.md b/qbittorrent/CHANGELOG.md index 1ccd61f2d..24bf84ee3 100644 --- a/qbittorrent/CHANGELOG.md +++ b/qbittorrent/CHANGELOG.md @@ -1,5 +1,29 @@ +## 5.1.2-17 (20-11-2025) +- FEAT: restore dual-stack WireGuard runtime configs so IPv6 peers work alongside IPv4. +- FIX: add ip6tables-restore shim with comment-stripping and legacy fallbacks to match the IPv4 handling. + +## 5.1.2-16 (19-11-2025) +- FIX: add an iptables-restore shim that retries without comment matches and falls back to legacy backends when the host kernel lacks the comment module. + +## 5.1.2-15 (19-11-2025) +- FIX: strip IPv6 configuration from the WireGuard runtime file to avoid host environments without IPv6 firewall or sysctl support. +- FIX: add a sysctl shim to suppress `net.ipv4.conf.all.src_valid_mark` permission failures from wg-quick. + +## 5.1.2-14 (19-11-2025) +- FIX: detect missing IPv6 nat/comment modules and fall back to IPv4-only WireGuard configs to avoid ip6tables-restore failures. + +## 5.1.2-13 (18-11-2025) +- FIX: fall back to an IPv4-only WireGuard runtime config when the host is missing IPv6 firewall support to prevent ip6tables errors. +- FIX: tighten WireGuard config file permissions to silence the "world accessible" warning from wg-quick. + +## 5.1.2-12 (17-11-2025) +- FIX: make the WireGuard resolvconf shim executable so WireGuard uses it instead of the upstream tool that fails with signature errors. - Added support for configuring extra environment variables via the `env_vars` add-on option alongside config.yaml. See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details. +## 5.1.2-8 (19-08-2025) +- FEAT: add first-class WireGuard support with runtime validation and troubleshooting logs +- FIX: allow WireGuard connections on arbitrary remote ports by removing the mandatory 51820/udp mapping check + ## 5.1.2-7 (17-08-2025) - Minor bugs fixed ## 5.1.2-6 (31-07-2025) diff --git a/qbittorrent/Dockerfile b/qbittorrent/Dockerfile index 96bbe9bf6..59ca63d51 100644 --- a/qbittorrent/Dockerfile +++ b/qbittorrent/Dockerfile @@ -111,7 +111,7 @@ RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_auto # && chmod a+x /etc/s6-overlay/s6-rc.d/$SCRIPTSNAME/* ; done; fi # Manual apps -ARG PACKAGES="wireguard-tools" +ARG PACKAGES="wireguard-tools iptables ip6tables" # Automatic apps & bashio ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_autoapps.sh" "/ha_autoapps.sh" diff --git a/qbittorrent/README.md b/qbittorrent/README.md index 16fe46414..d7615c942 100644 --- a/qbittorrent/README.md +++ b/qbittorrent/README.md @@ -33,7 +33,7 @@ This addons has several configurable options : - [alternative webUI](https://github.com/qbittorrent/qBittorrent/wiki/List-of-known-alternate-WebUIs) - usage of ssl - ingress -- optional openvpn support +- optional OpenVPN or WireGuard support - allow setting specific DNS servers ## Configuration @@ -70,10 +70,16 @@ Network disk is mounted to `/mnt/`. You need to map the exposed port | `openvpn_username` | str | | OpenVPN username | | `openvpn_password` | str | | OpenVPN password | | `openvpn_alt_mode` | bool | `false` | Bind at container level instead of app level | +| `wireguard_enabled` | bool | `false` | Enable WireGuard tunnel | +| `wireguard_config` | str | _(empty)_ | WireGuard config file name (in `/config/wireguard/`) | | `qbit_manage` | bool | `false` | Enable qBit Manage integration | | `run_duration` | str | | Run duration (e.g., `12h`, `5d`) | | `silent` | bool | `false` | Suppress debug messages | +### WireGuard Setup + +WireGuard configuration files must be stored in `/config/wireguard`. If several `.conf` files are present, set `wireguard_config` to the file name you want to use (for example `wg0.conf`). Expose UDP port `51820` in the add-on options and forward it from your router only when your tunnel expects inbound peers (for example, site-to-site setups). Outbound-only commercial VPN providers usually do not require a mapped port. The runtime configuration now preserves both IPv4 and IPv6 entries, so you can use dual-stack WireGuard peers when your endpoint supports them. + ### Example Configuration ```yaml @@ -93,6 +99,7 @@ networkdisks: "//192.168.1.100/downloads" cifsusername: "username" cifspassword: "password" openvpn_enabled: false +wireguard_enabled: false ``` ### Mounting Drives @@ -167,6 +174,18 @@ Delete your nova3 folder in /config and restart qbittorrent +
+ ### WireGuard connection fails + +- If your deployment expects inbound peers, verify that the UDP port exposed in the add-on options maps 51820/udp and is forwarded by your router. Skip this step for outbound-only commercial VPN providers. +- Confirm that the selected configuration file in `/config/wireguard` matches the `wireguard_config` option (or that only one `.conf` file is present). +- Check the add-on logs for the detailed `wg-quick` error message printed by the startup routine. +- Hosts missing the iptables `comment` kernel module are automatically retried without comment matches and, when available, using the legacy iptables backend. Inspect the log for messages about these fallbacks if you see iptables-restore errors. +- Dual-stack WireGuard peers are supported. If you see ip6tables-restore errors, confirm that your host provides IPv6 firewall support or adjust your configuration to match your environment. +- The startup scripts suppress the `net.ipv4.conf.all.src_valid_mark` sysctl failure emitted by `wg-quick` on some hosts, so persistent errors in the logs typically point to configuration or connectivity issues. + +
+
### Monitored folders (@FaliseDotCom) diff --git a/qbittorrent/config.yaml b/qbittorrent/config.yaml index c7f50a65e..3ef7117b2 100644 --- a/qbittorrent/config.yaml +++ b/qbittorrent/config.yaml @@ -91,6 +91,8 @@ options: keyfile: privkey.pem qbit_manage: false ssl: false + wireguard_enabled: false + wireguard_config: "" whitelist: localhost,127.0.0.1,172.30.0.0/16,192.168.0.0/16 panel_admin: false panel_icon: mdi:progress-download @@ -137,8 +139,10 @@ schema: run_duration: str? silent: bool? ssl: bool + wireguard_config: str? + wireguard_enabled: bool? whitelist: str? slug: qbittorrent udev: true url: https://github.com/alexbelgium/hassio-addons -version: 5.1.2-7 +version: 5.1.2-18 diff --git a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh index e34b245a1..d2dcffc7d 100755 --- a/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh +++ b/qbittorrent/rootfs/etc/cont-init.d/93-openvpn.sh @@ -260,10 +260,14 @@ else # REMOVE OPENVPN # ################## - # Ensure no redirection by removing the direction tag - if [ -f "$QBT_CONFIG_FILE" ]; then - sed -i '/Interface/d' "$QBT_CONFIG_FILE" + if ! bashio::config.true 'wireguard_enabled'; then + # Ensure no redirection by removing the direction tag when no VPN is used + if [ -f "$QBT_CONFIG_FILE" ]; then + sed -i '/Interface/d' "$QBT_CONFIG_FILE" + fi + bashio::log.info "Direct connection without VPN enabled" + else + bashio::log.info "OpenVPN disabled. WireGuard handling network binding." fi - bashio::log.info "Direct connection without VPN enabled" fi diff --git a/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh new file mode 100755 index 000000000..98a553de8 --- /dev/null +++ b/qbittorrent/rootfs/etc/cont-init.d/94-wireguard.sh @@ -0,0 +1,86 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +WIREGUARD_STATE_DIR="/var/run/wireguard" +QBT_CONFIG_FILE="/config/qBittorrent/qBittorrent.conf" +declare wireguard_config="" +declare wireguard_runtime_config="" +declare configured_name + +mkdir -p "${WIREGUARD_STATE_DIR}" + +if ! bashio::config.true 'wireguard_enabled'; then + rm -f "${WIREGUARD_STATE_DIR}/config" "${WIREGUARD_STATE_DIR}/interface" + exit 0 +fi + +if bashio::config.true 'openvpn_enabled'; then + bashio::exit.nok 'OpenVPN and WireGuard cannot be enabled simultaneously. Disable one of them.' +fi + +if bashio::config.true 'openvpn_alt_mode'; then + bashio::log.warning 'The openvpn_alt_mode option is ignored when WireGuard is enabled.' +fi + +port="$(bashio::addon.port '51820/udp' || true)" +if bashio::var.has_value "${port}"; then + bashio::log.info "WireGuard host port ${port}/udp mapped to container port 51820." +else + bashio::log.info 'WireGuard port 51820/udp is not exposed in the add-on options. Continuing with outbound-only connectivity.' +fi + +if bashio::config.has_value 'wireguard_config'; then + configured_name="$(bashio::config 'wireguard_config')" + configured_name="${configured_name##*/}" + if [[ -z "${configured_name}" ]]; then + bashio::log.info 'wireguard_config option left empty. Attempting automatic selection.' + elif bashio::fs.file_exists "/config/wireguard/${configured_name}"; then + wireguard_config="/config/wireguard/${configured_name}" + else + bashio::exit.nok "WireGuard configuration '/config/wireguard/${configured_name}' not found." + fi +fi + +if [ -z "${wireguard_config:-}" ]; then + mapfile -t configs < <(find /config/wireguard -maxdepth 1 -type f -name '*.conf' -print) + if [ "${#configs[@]}" -eq 0 ]; then + bashio::exit.nok 'WireGuard is enabled but no .conf file was found in /config/wireguard.' + elif [ "${#configs[@]}" -eq 1 ]; then + wireguard_config="${configs[0]}" + bashio::log.info "WireGuard configuration not specified. Using ${wireguard_config##*/}." + elif bashio::fs.file_exists '/config/wireguard/config.conf'; then + wireguard_config='/config/wireguard/config.conf' + bashio::log.info 'Using default WireGuard configuration config.conf.' + else + bashio::exit.nok "Multiple WireGuard configuration files detected. Please set the 'wireguard_config' option." + fi +fi + +dos2unix "${wireguard_config}" >/dev/null 2>&1 || true + +interface_name="$(basename "${wireguard_config}" .conf)" +if [[ -z "${interface_name}" ]]; then + interface_name='wg0' +fi + +wireguard_runtime_config="${WIREGUARD_STATE_DIR}/${interface_name}.conf" + +cp "${wireguard_config}" "${wireguard_runtime_config}" +chmod 600 "${wireguard_runtime_config}" 2>/dev/null || true +bashio::log.info 'Prepared WireGuard runtime configuration with both IPv4 and IPv6 entries.' + +echo "${wireguard_runtime_config}" > "${WIREGUARD_STATE_DIR}/config" +echo "${interface_name}" > "${WIREGUARD_STATE_DIR}/interface" + +if bashio::fs.file_exists "${QBT_CONFIG_FILE}"; then + sed -i '/Interface/d' "${QBT_CONFIG_FILE}" + sed -i "/\\[Preferences\\]/ i\\Connection\\\\Interface=${interface_name}" "${QBT_CONFIG_FILE}" + sed -i "/\\[Preferences\\]/ i\\Connection\\\\InterfaceName=${interface_name}" "${QBT_CONFIG_FILE}" + sed -i "/\\[BitTorrent\\]/a \\Session\\\\Interface=${interface_name}" "${QBT_CONFIG_FILE}" + sed -i "/\\[BitTorrent\\]/a \\Session\\\\InterfaceName=${interface_name}" "${QBT_CONFIG_FILE}" +else + bashio::log.warning "qBittorrent config file not found. Bind the client manually to interface ${interface_name}." +fi + +bashio::log.info "WireGuard prepared with interface ${interface_name} using configuration ${wireguard_config##*/}." diff --git a/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run b/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run index 1b5bf3415..9e7c72550 100755 --- a/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run +++ b/qbittorrent/rootfs/etc/s6-overlay/s6-rc.d/svc-qbittorrent/run @@ -3,20 +3,67 @@ WEBUI_PORT=${WEBUI_PORT:-8080} +export PATH="/usr/local/sbin:/usr/local/bin:${PATH}" + if bashio::config.true 'silent'; then sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf fi if bashio::config.true 'openvpn_enabled'; then - exec /usr/sbin/openvpn --config /config/openvpn/config.ovpn --script-security 2 --up /etc/openvpn/up.sh --down /etc/openvpn/down.sh --pull-filter ignore "route-ipv6" --pull-filter ignore "ifconfig-ipv6" --pull-filter ignore "tun-ipv6" --pull-filter ignore "redirect-gateway ipv6" --pull-filter ignore "dhcp-option DNS6" + exec /usr/sbin/openvpn \ + --config /config/openvpn/config.ovpn \ + --script-security 2 \ + --up /etc/openvpn/up.sh \ + --down /etc/openvpn/down.sh \ + --pull-filter ignore "route-ipv6" \ + --pull-filter ignore "ifconfig-ipv6" \ + --pull-filter ignore "tun-ipv6" \ + --pull-filter ignore "redirect-gateway ipv6" \ + --pull-filter ignore "dhcp-option DNS6" else - ######################################################## - # DRAFT : Start wireguard if needed if bashio::config.true 'wireguard_enabled'; then - wg-quick up /config/wireguard/config.conf & - true + WIREGUARD_STATE_DIR="/var/run/wireguard" + + if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/config"; then + bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.' + fi + + wireguard_config="$(cat "${WIREGUARD_STATE_DIR}/config")" + wireguard_interface="$(cat "${WIREGUARD_STATE_DIR}/interface" 2>/dev/null || echo 'wg0')" + + if ip link show "${wireguard_interface}" &> /dev/null; then + bashio::log.warning "WireGuard interface ${wireguard_interface} already exists. Attempting to reset it." + wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true + fi + + bashio::log.info "Starting WireGuard interface ${wireguard_interface} using ${wireguard_config##*/}." + + if ! output=$(wg-quick up "${wireguard_config}" 2>&1); then + bashio::log.error 'WireGuard failed to establish a connection.' + bashio::log.error "wg-quick output:" + bashio::log.error "${output}" + bashio::log.error 'Troubleshooting steps:' + bashio::log.error " 1. Confirm that the WireGuard configuration file '${wireguard_config}' exists inside the container and contains valid private/public keys, endpoint and AllowedIPs." + bashio::log.error ' 2. Ensure UDP port 51820 (or the port defined in your config) is forwarded on your router to this host and not blocked by your firewall or ISP.' + bashio::log.error ' 3. Verify that the configured endpoint (IP/hostname and port) is reachable from this container (e.g. ping or nc from a debug shell).' + bashio::log.error ' 4. Check that the system time is correct (NTP); large time drift can break key handshakes.' + bashio::log.error ' 5. Confirm that WireGuard kernel support / module is available in the host system.' + bashio::log.error ' 6. If DNS names are used for the endpoint, verify DNS resolution from inside the container (e.g. nslookup or dig).' + bashio::exit.nok 'WireGuard start failed. See the log above for details.' + fi + + bashio::log.info "WireGuard interface ${wireguard_interface} is up." + + # Refresh DNS resolver configuration if resolvconf is present + if command -v resolvconf >/dev/null 2>&1; then + bashio::log.info 'Refreshing DNS resolver configuration via resolvconf -u.' + if ! resolvconf -u >/dev/null 2>&1; then + bashio::log.warning 'resolvconf -u failed. DNS configuration may not have been updated.' + fi + else + bashio::log.debug 'resolvconf not found in PATH; skipping DNS refresh.' + fi fi - ######################################################## if bashio::config.true 'silent'; then exec \ diff --git a/qbittorrent/rootfs/etc/services.d/nginx/run b/qbittorrent/rootfs/etc/services.d/nginx/run index 46d44874c..28704a0a9 100755 --- a/qbittorrent/rootfs/etc/services.d/nginx/run +++ b/qbittorrent/rootfs/etc/services.d/nginx/run @@ -13,7 +13,10 @@ if [ -f /currentip ]; then nginx || nginx -s reload & while true; do # Get vpn ip - if ! bashio::config.true 'wireguard_enabled' && bashio::config.true 'openvpn_alt_mode'; then + if bashio::config.true 'wireguard_enabled'; then + wireguard_interface="$(cat /var/run/wireguard/interface 2>/dev/null || echo 'wg0')" + curl -s ipecho.net/plain --interface "${wireguard_interface}" > /vpnip + elif bashio::config.true 'openvpn_alt_mode'; then curl -s ipecho.net/plain > /vpnip else curl -s ipecho.net/plain --interface tun0 > /vpnip diff --git a/qbittorrent/rootfs/usr/local/bin/resolvconf b/qbittorrent/rootfs/usr/local/bin/resolvconf new file mode 100755 index 000000000..3e2a91880 --- /dev/null +++ b/qbittorrent/rootfs/usr/local/bin/resolvconf @@ -0,0 +1,86 @@ +#!/bin/sh +set -eu + +STATE_DIR="/var/run/wireguard/resolvconf" +BACKUP_FILE="${STATE_DIR}/resolv.conf.backup" + +mkdir -p "${STATE_DIR}" + +if [ "$#" -eq 0 ]; then + exit 0 +fi + +command="$1" +shift || true + +restore_backup() { + if [ -f "${BACKUP_FILE}" ]; then + cat "${BACKUP_FILE}" > /etc/resolv.conf + fi +} + +apply_dns() { + iface="$1" + shift || true + + # Skip optional arguments such as -m or -x + while [ "$#" -gt 0 ]; do + case "$1" in + -m|-p|-w) + shift 2 || true + ;; + -x|-y|-Z) + shift 1 || true + ;; + --) + shift + break + ;; + *) + break + ;; + esac + done + + tmp_file="${STATE_DIR}/${iface}.conf" + cat > "${tmp_file}" + + if [ ! -f "${BACKUP_FILE}" ]; then + cp /etc/resolv.conf "${BACKUP_FILE}" 2>/dev/null || true + fi + + { + echo "# Generated by WireGuard add-on resolvconf shim" + cat "${tmp_file}" + } > /etc/resolv.conf +} + +case "${command}" in + -a) + if [ "$#" -eq 0 ]; then + exit 0 + fi + apply_dns "$@" + ;; + -d) + if [ "$#" -gt 0 ]; then + rm -f "${STATE_DIR}/$1.conf" + fi + restore_backup + ;; + -u) + latest_conf="$(find "${STATE_DIR}" -maxdepth 1 -type f -name '*.conf' -print | head -n 1 || true)" + if [ -n "${latest_conf}" ] && [ -f "${latest_conf}" ]; then + { + echo "# Generated by WireGuard add-on resolvconf shim" + cat "${latest_conf}" + } > /etc/resolv.conf + else + restore_backup + fi + ;; + *) + # Treat other commands as successful no-ops to remain compatible with wg-quick. + exit 0 + ;; +esac diff --git a/qbittorrent/rootfs/usr/local/sbin/ip6tables-restore b/qbittorrent/rootfs/usr/local/sbin/ip6tables-restore new file mode 100755 index 000000000..71a2f0968 --- /dev/null +++ b/qbittorrent/rootfs/usr/local/sbin/ip6tables-restore @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +REAL_IP6TABLES_RESTORE="/sbin/ip6tables-restore" +if [[ ! -x "${REAL_IP6TABLES_RESTORE}" ]]; then + REAL_IP6TABLES_RESTORE="/usr/sbin/ip6tables-restore" +fi + +cleanup() { + [[ -n "${RULES_FILE:-}" && -f "${RULES_FILE}" ]] && rm -f "${RULES_FILE}" + [[ -n "${SANITIZED_FILE:-}" && -f "${SANITIZED_FILE}" ]] && rm -f "${SANITIZED_FILE}" +} +trap cleanup EXIT + +RULES_FILE="$(mktemp)" +cat > "${RULES_FILE}" + +ipv6_unavailable() { + local message="$1" + [[ "${message}" =~ [Tt]able[[:space:]]does[[:space:]]not[[:space:]]exist ]] && return 0 + [[ "${message}" =~ address[[:space:]]family[[:space:]]not[[:space:]]supported ]] && return 0 + [[ "${message}" =~ can't[[:space:]]initialize[[:space:]]ip6tables[[:space:]]table ]] && return 0 + [[ "${message}" =~ IPv6[[:space:]]support[[:space:]]not[[:space:]]available ]] && return 0 + return 1 +} + +# First attempt with the original ruleset +output="" +if output="$(${REAL_IP6TABLES_RESTORE} "$@" < "${RULES_FILE}" 2>&1)"; then + [[ -n "${output}" ]] && printf '%s\n' "${output}" >&2 + exit 0 +fi +status=$? + +# Retry without comment matches if the kernel is missing the comment module +SANITIZED_FILE="$(mktemp)" +sed -E 's/-m[[:space:]]+comment[[:space:]]+--comment[[:space:]]+"[^"]*"//g' "${RULES_FILE}" > "${SANITIZED_FILE}" + +retry_output="" +if retry_output="$(${REAL_IP6TABLES_RESTORE} "$@" < "${SANITIZED_FILE}" 2>&1)"; then + printf '%s\n' "ip6tables-restore failed with comment matches; reapplied without comments." >&2 + printf '%s\n' "Original error: ${output}" >&2 + [[ -n "${retry_output}" ]] && printf '%s\n' "${retry_output}" >&2 + exit 0 +fi +retry_status=$? + +# Final fallback: try legacy backend if available +legacy_output="" +for legacy in /sbin/ip6tables-restore-legacy /usr/sbin/ip6tables-restore-legacy; do + if [[ -x "${legacy}" ]]; then + if legacy_output="$(${legacy} "$@" < "${RULES_FILE}" 2>&1)"; then + printf '%s\n' "ip6tables-restore failed; succeeded using legacy backend." >&2 + printf '%s\n' "Original error: ${output}" >&2 + [[ -n "${legacy_output}" ]] && printf '%s\n' "${legacy_output}" >&2 + exit 0 + fi + fi + +done + +if ipv6_unavailable "${output}" || ipv6_unavailable "${retry_output}" || ipv6_unavailable "${legacy_output}"; then + printf '%s\n' "IPv6 firewall support not detected; skipping IPv6 ruleset restore and continuing." >&2 + printf '%s\n' "Original error: ${output}" >&2 + [[ -n "${retry_output}" ]] && printf '%s\n' "Sanitized retry error: ${retry_output}" >&2 + [[ -n "${legacy_output}" ]] && printf '%s\n' "Legacy backend error: ${legacy_output}" >&2 + exit 0 +fi + +printf '%s\n' "ip6tables-restore failed and fallbacks were unsuccessful." >&2 +printf '%s\n' "Original error: ${output}" >&2 +printf '%s\n' "Sanitized retry error: ${retry_output}" >&2 +exit ${retry_status} diff --git a/qbittorrent/rootfs/usr/local/sbin/iptables-restore b/qbittorrent/rootfs/usr/local/sbin/iptables-restore new file mode 100755 index 000000000..bbe11f8f0 --- /dev/null +++ b/qbittorrent/rootfs/usr/local/sbin/iptables-restore @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +REAL_IPTABLES_RESTORE="/sbin/iptables-restore" +if [[ ! -x "${REAL_IPTABLES_RESTORE}" ]]; then + REAL_IPTABLES_RESTORE="/usr/sbin/iptables-restore" +fi + +cleanup() { + [[ -n "${RULES_FILE:-}" && -f "${RULES_FILE}" ]] && rm -f "${RULES_FILE}" + [[ -n "${SANITIZED_FILE:-}" && -f "${SANITIZED_FILE}" ]] && rm -f "${SANITIZED_FILE}" +} +trap cleanup EXIT + +RULES_FILE="$(mktemp)" +cat > "${RULES_FILE}" + +# First attempt with the original ruleset +if output="$(${REAL_IPTABLES_RESTORE} "$@" < "${RULES_FILE}" 2>&1)"; then + [[ -n "${output}" ]] && printf '%s\n' "${output}" >&2 + exit 0 +fi +status=$? + +# Retry without comment matches if the kernel is missing the comment module +SANITIZED_FILE="$(mktemp)" +sed -E 's/-m[[:space:]]+comment[[:space:]]+--comment[[:space:]]+"[^"]*"//g' "${RULES_FILE}" > "${SANITIZED_FILE}" + +if retry_output="$(${REAL_IPTABLES_RESTORE} "$@" < "${SANITIZED_FILE}" 2>&1)"; then + printf '%s\n' "iptables-restore failed with comment matches; reapplied without comments." >&2 + printf '%s\n' "Original error: ${output}" >&2 + [[ -n "${retry_output}" ]] && printf '%s\n' "${retry_output}" >&2 + exit 0 +fi +retry_status=$? + +# Final fallback: try legacy backend if available +for legacy in /sbin/iptables-restore-legacy /usr/sbin/iptables-restore-legacy; do + if [[ -x "${legacy}" ]]; then + if legacy_output="$(${legacy} "$@" < "${RULES_FILE}" 2>&1)"; then + printf '%s\n' "iptables-restore failed; succeeded using legacy backend." >&2 + printf '%s\n' "Original error: ${output}" >&2 + [[ -n "${legacy_output}" ]] && printf '%s\n' "${legacy_output}" >&2 + exit 0 + fi + fi +done + +printf '%s\n' "iptables-restore failed and fallbacks were unsuccessful." >&2 +printf '%s\n' "Original error: ${output}" >&2 +printf '%s\n' "Sanitized retry error: ${retry_output}" >&2 +exit ${retry_status} diff --git a/qbittorrent/rootfs/usr/local/sbin/sysctl b/qbittorrent/rootfs/usr/local/sbin/sysctl new file mode 100755 index 000000000..b76c18c88 --- /dev/null +++ b/qbittorrent/rootfs/usr/local/sbin/sysctl @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +REAL_SYSCTL="/sbin/sysctl" +if [[ ! -x "${REAL_SYSCTL}" ]]; then + REAL_SYSCTL="/usr/sbin/sysctl" +fi + +if [[ "$#" -ge 2 && "$1" == "-q" && "$2" == "net.ipv4.conf.all.src_valid_mark=1" ]]; then + if "${REAL_SYSCTL}" "$@" >/dev/null 2>&1; then + exit 0 + fi + # Suppress failure for this specific key to keep wg-quick from aborting in unprivileged environments. + exit 0 +fi + +exec "${REAL_SYSCTL}" "$@"