diff --git a/freesailarr/templates/freesailarr.sfs.yaml b/freesailarr/templates/freesailarr.sfs.yaml index 8297e23..f99095d 100644 --- a/freesailarr/templates/freesailarr.sfs.yaml +++ b/freesailarr/templates/freesailarr.sfs.yaml @@ -19,6 +19,63 @@ spec: automountServiceAccountToken: false securityContext: {{- toYaml .Values.securityContext.pod | nindent 8 }} + {{- if and .Values.qbittorrent.enabled .Values.qbittorrent.networkInterface }} + initContainers: + # qBittorrent has no environment variable for the session's network + # interface - it only reads it from the persisted config file, which + # the app itself creates on first start. Without the binding + # libtorrent listens and announces on every interface of the shared + # pod network namespace (eth0/pod IP and loopback included); gluetun's + # kill switch drops those packets, trackers stay unreachable and + # torrents stall at 0 bytes. Seeding the two keys before qbittorrent + # starts closes that leak and makes a fresh install work out of the + # box. The edit is idempotent and preserves all other settings: + # existing keys are replaced, missing ones are inserted into the + # [BitTorrent] section (qBittorrent 5.x reads them there), and a + # missing file or section is created. + - name: qbittorrent-config-interface + image: "{{ .Values.qbittorrent.configInit.image.repository }}:{{ .Values.qbittorrent.configInit.image.tag }}" + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + set -eu + conf=/config/qBittorrent/qBittorrent.conf + mkdir -p /config/qBittorrent + [ -f "$conf" ] || printf '[BitTorrent]\n' > "$conf" + # A file without a trailing newline would glue an appended + # section header onto the last key. + if [ -s "$conf" ] && [ -n "$(tail -c 1 "$conf")" ]; then + printf '\n' >> "$conf" + fi + grep -q '^\[BitTorrent\]$' "$conf" || printf '[BitTorrent]\n' >> "$conf" + # Drop any existing binding keys, then re-insert them directly + # after the [BitTorrent] header. + awk -v ifc="$QBT_INTERFACE" ' + /^Session\\Interface=/ { next } + /^Session\\InterfaceName=/ { next } + { print } + /^\[BitTorrent\]$/ { + print "Session\\Interface=" ifc + print "Session\\InterfaceName=" ifc + } + ' "$conf" > "${conf}.tmp" + mv "${conf}.tmp" "$conf" + echo "qBittorrent session bound to interface $QBT_INTERFACE" + env: + - name: QBT_INTERFACE + value: {{ .Values.qbittorrent.networkInterface | quote }} + resources: + {{- include "freesailarr.resources" .Values.qbittorrent.configInit.resources | nindent 12 }} + securityContext: + runAsUser: {{ .Values.puid }} + runAsGroup: {{ .Values.pgid }} + {{- toYaml .Values.qbittorrent.configInit.securityContext | nindent 12 }} + volumeMounts: + - mountPath: /config + name: qbittorrent-config + {{- end }} containers: # gluetun is the VPN gateway: all app containers share the pod's # network namespace, talk to each other over localhost and reach the diff --git a/freesailarr/values.yaml b/freesailarr/values.yaml index b012180..64aa8ab 100644 --- a/freesailarr/values.yaml +++ b/freesailarr/values.yaml @@ -161,6 +161,41 @@ qbittorrent: enabled: true storage: size: 1Gi + # Network interface qBittorrent binds its BitTorrent session to - gluetun's + # Wireguard device. Without this binding libtorrent listens and announces on + # every interface in the shared pod network namespace, including eth0 (the + # pod IP) and loopback. Those announces bypass the tunnel, so gluetun's kill + # switch (OUTPUT DROP except tun0 and the allowed cluster subnets) drops + # them: trackers report "skipping tracker announce (unreachable)", no peers + # are found and torrents stall at 0 bytes in stalledDL. Binding to tun0 is + # therefore both the functional fix and a leak fix - it stops qBittorrent + # from listening on the pod IP past the tunnel. The value is written into + # /config/qBittorrent/qBittorrent.conf by the config-interface init + # container below; setting it to null or "" removes that init container and + # leaves the binding to the user (not recommended). + networkInterface: tun0 + # The init container that seeds qbittorrent.networkInterface into the + # persisted qBittorrent config before the app starts. Runs as puid/pgid so + # the config file keeps the ownership the app expects. + configInit: + # Auxiliary busybox image. Pinned independently of appVersion (which + # tracks gluetun); reviewed alongside every app-version update round. + image: + repository: busybox + tag: "1.38.0" + resources: + limitFactor: 3 + requests: + cpu: 10m + memory: 16Mi + ephemeralStorage: 1Mi + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL env: [] livenessProbe: httpGet: