Bind the qBittorrent session to the VPN interface
qBittorrent had no network interface configured, so libtorrent listened and announced on every interface of the shared pod network namespace - including eth0 (the pod IP) and loopback. gluetun's kill switch drops exactly those packets, so trackers answered with "skipping tracker announce (unreachable)" or timed out, no peers were found and torrents stalled at 0 bytes in stalledDL. Beyond the stall this is a leak: without the binding qBittorrent also listens on the pod IP, i.e. reachable past the tunnel. The setting only exists in the persisted config file, which qBittorrent creates itself on first start, so an init container seeds it before the app comes up. The edit is idempotent and preserves existing user settings: it replaces the keys if present, inserts them into the [BitTorrent] section if missing, and creates file or section if needed. The interface is configurable via qbittorrent.networkInterface (default tun0); emptying it drops the init container.
This commit is contained in:
2 files changed
+92
No files matched your search
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user