214afed0a2
DOT=off alone does not override DNS_UPSTREAM_RESOLVER_TYPE in this version of gluetun — it defaults to DoT regardless, causing DNS failures through ProtonVPN (port 853 connection reset). Setting the resolver type directly fixes plain DNS routing to k8s CoreDNS.
440 lines
16 KiB
YAML
440 lines
16 KiB
YAML
# Fornax distributed torrent workers — each is a gluetun+qBittorrent pod on a different VPN server
|
|
# Worker TX#179: US-TX#179, NAT-PMP via gluetun native ProtonVPN port forwarding
|
|
# Worker TX#220: US-TX#220, NAT-PMP via gluetun native ProtonVPN port forwarding
|
|
# Both workers share the media-data PVC; each has its own config PVC and VPN credentials
|
|
# Port file shared via emptyDir: gluetun writes /tmp/gluetun/forwarded_port, helper reads it
|
|
|
|
# ── Worker TX#179 ─────────────────────────────────────────────────────────────
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: fornax-worker-tx253
|
|
namespace: media
|
|
labels:
|
|
app: fornax-worker-tx253
|
|
fornax-role: worker
|
|
fornax-server: us-tx-179
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app: fornax-worker-tx253
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: fornax-worker-tx253
|
|
fornax-role: worker
|
|
fornax-server: us-tx-179
|
|
spec:
|
|
initContainers:
|
|
- name: tun-setup
|
|
image: busybox:latest
|
|
command: ["sh", "-c", "mkdir -p /dev/net && [ -c /dev/net/tun ] || mknod /dev/net/tun c 10 200 && chmod 666 /dev/net/tun && sysctl -w net.ipv6.conf.all.disable_ipv6=1 || true && ip route flush table 51820 2>/dev/null || true && ip rule del priority 101 2>/dev/null || true && ip rule del table 51820 2>/dev/null || true"]
|
|
securityContext:
|
|
privileged: true
|
|
- name: qbt-config-patch
|
|
image: python:3.13-alpine
|
|
command:
|
|
- python3
|
|
- -c
|
|
- |
|
|
import os, re
|
|
|
|
CONF = '/config/qBittorrent/qBittorrent.conf'
|
|
os.makedirs('/config/qBittorrent', exist_ok=True)
|
|
|
|
text = open(CONF).read() if os.path.exists(CONF) else ''
|
|
|
|
def set_key(text, section, key, value):
|
|
"""Set key=value under [section], inserting if missing."""
|
|
key_pat = re.compile(r'^' + re.escape(key) + r'=.*', re.M)
|
|
if key_pat.search(text):
|
|
return key_pat.sub(lambda m: key + '=' + value, text)
|
|
sec_pat = re.compile(r'^\[' + re.escape(section) + r'\]', re.M)
|
|
if sec_pat.search(text):
|
|
return sec_pat.sub(lambda m: '[' + section + ']\n' + key + '=' + value, text)
|
|
return text + f'\n[{section}]\n{key}={value}\n'
|
|
|
|
HASH = '@ByteArray(HqYj1eGsdXlQ4CSy597Y9A==:J9hsJIlU5FYfHb5rY5qQoIpVpTijryS/H+CE07oMtplL/ytneBVFd2tfVJtqGjhdht8tEi4wmqSSlqTgEu444w==)'
|
|
|
|
# Preferences
|
|
text = set_key(text, 'Preferences', r'WebUI\Password_PBKDF2', f'"{HASH}"')
|
|
text = set_key(text, 'Preferences', r'WebUI\LocalhostAuthEnabled', 'false')
|
|
|
|
# BitTorrent performance defaults (only applied when key absent — API updates persist)
|
|
bt_defaults = {
|
|
r'Session\MaxActiveDownloads': '50',
|
|
r'Session\MaxActiveTorrents': '100',
|
|
r'Session\MaxActiveUploads': '20',
|
|
r'Session\MaxConnections': '3000',
|
|
r'Session\MaxConnectionsPerTorrent': '300',
|
|
r'Session\MaxUploads': '-1',
|
|
r'Session\MaxUploadsPerTorrent': '10',
|
|
r'Session\GlobalDLSpeedLimit': '0',
|
|
r'Session\GlobalUPSpeedLimit': '0',
|
|
r'Session\DHTEnabled': 'true',
|
|
}
|
|
for key, val in bt_defaults.items():
|
|
text = set_key(text, 'BitTorrent', key, val)
|
|
|
|
open(CONF, 'w').write(text)
|
|
print('qBittorrent config patched.')
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /config
|
|
containers:
|
|
- name: gluetun
|
|
image: ghcr.io/qdm12/gluetun:latest
|
|
securityContext:
|
|
capabilities:
|
|
add: ["NET_ADMIN"]
|
|
env:
|
|
- name: VPN_SERVICE_PROVIDER
|
|
value: "custom"
|
|
- name: VPN_TYPE
|
|
value: "wireguard"
|
|
- name: WIREGUARD_PRIVATE_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: fornax-worker-tx253-secrets
|
|
key: PROTONVPN_PRIVATE_KEY
|
|
- name: WIREGUARD_PUBLIC_KEY
|
|
value: "mngiSxBpH7GU24nnWdBEcnhDnCPn2jq5+ZP3zwPwISA="
|
|
- name: WIREGUARD_ADDRESSES
|
|
value: "10.2.0.2/32"
|
|
- name: WIREGUARD_ENDPOINT_IP
|
|
value: "95.173.217.29"
|
|
- name: WIREGUARD_ENDPOINT_PORT
|
|
value: "51820"
|
|
- name: DOT
|
|
value: "off"
|
|
- name: DNS_UPSTREAM_RESOLVER_TYPE
|
|
value: "plain"
|
|
- name: DNS_UPSTREAM_PLAIN_ADDRESSES
|
|
value: "10.43.0.10:53"
|
|
- name: HEALTH_TARGET_ADDRESS
|
|
value: "api.protonvpn.ch:443,account.proton.me:443"
|
|
- name: FIREWALL_OUTBOUND_SUBNETS
|
|
value: "10.42.0.0/16,10.43.0.0/16"
|
|
volumeMounts:
|
|
- name: gluetun-data
|
|
mountPath: /tmp/gluetun
|
|
ports:
|
|
- containerPort: 8888
|
|
resources:
|
|
requests:
|
|
memory: 128Mi
|
|
cpu: 50m
|
|
limits:
|
|
memory: 512Mi
|
|
cpu: 200m
|
|
|
|
# Port forwarding helper — reads port from file gluetun writes to /tmp/gluetun/forwarded_port
|
|
- name: portforward-helper
|
|
image: alpine:latest
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
until apk add -q curl 2>/dev/null; do sleep 5; done
|
|
echo "Watching /tmp/gluetun/forwarded_port for assigned port..."
|
|
while true; do
|
|
if [ -f /tmp/gluetun/forwarded_port ]; then
|
|
PORT=$(cat /tmp/gluetun/forwarded_port)
|
|
if [ -n "$PORT" ] && [ "$PORT" != "0" ]; then
|
|
echo "$(date): Forwarded port: $PORT — updating qBittorrent"
|
|
curl -s -c /tmp/qbt.txt -X POST http://localhost:8080/api/v2/auth/login \
|
|
-d "username=admin&password=adminadmin" >/dev/null 2>&1
|
|
curl -s -b /tmp/qbt.txt -X POST http://localhost:8080/api/v2/app/setPreferences \
|
|
-d "json={\"listen_port\":$PORT,\"random_port\":false}" >/dev/null 2>&1
|
|
echo "$(date): qBittorrent listen port set to $PORT"
|
|
else
|
|
echo "$(date): Port file empty, waiting..."
|
|
fi
|
|
else
|
|
echo "$(date): Waiting for gluetun to write port file..."
|
|
fi
|
|
sleep 45
|
|
done
|
|
volumeMounts:
|
|
- name: gluetun-data
|
|
mountPath: /tmp/gluetun
|
|
resources:
|
|
requests:
|
|
memory: 32Mi
|
|
cpu: 10m
|
|
limits:
|
|
memory: 96Mi
|
|
cpu: 50m
|
|
|
|
- name: qbittorrent
|
|
image: lscr.io/linuxserver/qbittorrent:latest
|
|
env:
|
|
- name: PUID
|
|
value: "1000"
|
|
- name: PGID
|
|
value: "1000"
|
|
- name: TZ
|
|
value: "America/Chicago"
|
|
- name: WEBUI_PORT
|
|
value: "8080"
|
|
ports:
|
|
- containerPort: 8080
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /config
|
|
- name: media
|
|
mountPath: /media
|
|
resources:
|
|
requests:
|
|
memory: 256Mi
|
|
cpu: 100m
|
|
limits:
|
|
memory: 1Gi
|
|
cpu: 500m
|
|
volumes:
|
|
- name: gluetun-data
|
|
emptyDir: {}
|
|
- name: config
|
|
persistentVolumeClaim:
|
|
claimName: fornax-worker-tx253-config
|
|
- name: media
|
|
persistentVolumeClaim:
|
|
claimName: media-data
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: fornax-worker-tx253
|
|
namespace: media
|
|
labels:
|
|
app: fornax-worker-tx253
|
|
fornax-role: worker
|
|
spec:
|
|
selector:
|
|
app: fornax-worker-tx253
|
|
ports:
|
|
- name: webui
|
|
port: 8080
|
|
targetPort: 8080
|
|
type: ClusterIP
|
|
|
|
---
|
|
# ── Worker TX#220 ─────────────────────────────────────────────────────────────
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: fornax-worker-tx34
|
|
namespace: media
|
|
labels:
|
|
app: fornax-worker-tx34
|
|
fornax-role: worker
|
|
fornax-server: us-tx-220
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app: fornax-worker-tx34
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: fornax-worker-tx34
|
|
fornax-role: worker
|
|
fornax-server: us-tx-220
|
|
spec:
|
|
initContainers:
|
|
- name: tun-setup
|
|
image: busybox:latest
|
|
command: ["sh", "-c", "mkdir -p /dev/net && [ -c /dev/net/tun ] || mknod /dev/net/tun c 10 200 && chmod 666 /dev/net/tun && sysctl -w net.ipv6.conf.all.disable_ipv6=1 || true && ip route flush table 51820 2>/dev/null || true && ip rule del priority 101 2>/dev/null || true && ip rule del table 51820 2>/dev/null || true"]
|
|
securityContext:
|
|
privileged: true
|
|
- name: qbt-config-patch
|
|
image: python:3.13-alpine
|
|
command:
|
|
- python3
|
|
- -c
|
|
- |
|
|
import os, re
|
|
|
|
CONF = '/config/qBittorrent/qBittorrent.conf'
|
|
os.makedirs('/config/qBittorrent', exist_ok=True)
|
|
|
|
text = open(CONF).read() if os.path.exists(CONF) else ''
|
|
|
|
def set_key(text, section, key, value):
|
|
"""Set key=value under [section], inserting if missing."""
|
|
key_pat = re.compile(r'^' + re.escape(key) + r'=.*', re.M)
|
|
if key_pat.search(text):
|
|
return key_pat.sub(lambda m: key + '=' + value, text)
|
|
sec_pat = re.compile(r'^\[' + re.escape(section) + r'\]', re.M)
|
|
if sec_pat.search(text):
|
|
return sec_pat.sub(lambda m: '[' + section + ']\n' + key + '=' + value, text)
|
|
return text + f'\n[{section}]\n{key}={value}\n'
|
|
|
|
HASH = '@ByteArray(HqYj1eGsdXlQ4CSy597Y9A==:J9hsJIlU5FYfHb5rY5qQoIpVpTijryS/H+CE07oMtplL/ytneBVFd2tfVJtqGjhdht8tEi4wmqSSlqTgEu444w==)'
|
|
|
|
# Preferences
|
|
text = set_key(text, 'Preferences', r'WebUI\Password_PBKDF2', f'"{HASH}"')
|
|
text = set_key(text, 'Preferences', r'WebUI\LocalhostAuthEnabled', 'false')
|
|
|
|
# BitTorrent performance defaults (only applied when key absent — API updates persist)
|
|
bt_defaults = {
|
|
r'Session\MaxActiveDownloads': '50',
|
|
r'Session\MaxActiveTorrents': '100',
|
|
r'Session\MaxActiveUploads': '20',
|
|
r'Session\MaxConnections': '3000',
|
|
r'Session\MaxConnectionsPerTorrent': '300',
|
|
r'Session\MaxUploads': '-1',
|
|
r'Session\MaxUploadsPerTorrent': '10',
|
|
r'Session\GlobalDLSpeedLimit': '0',
|
|
r'Session\GlobalUPSpeedLimit': '0',
|
|
r'Session\DHTEnabled': 'true',
|
|
}
|
|
for key, val in bt_defaults.items():
|
|
text = set_key(text, 'BitTorrent', key, val)
|
|
|
|
open(CONF, 'w').write(text)
|
|
print('qBittorrent config patched.')
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /config
|
|
containers:
|
|
- name: gluetun
|
|
image: ghcr.io/qdm12/gluetun:latest
|
|
securityContext:
|
|
capabilities:
|
|
add: ["NET_ADMIN"]
|
|
env:
|
|
- name: VPN_SERVICE_PROVIDER
|
|
value: "custom"
|
|
- name: VPN_TYPE
|
|
value: "wireguard"
|
|
- name: WIREGUARD_PRIVATE_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: fornax-worker-tx34-secrets
|
|
key: PROTONVPN_PRIVATE_KEY
|
|
- name: WIREGUARD_PUBLIC_KEY
|
|
value: "wqJcz4akzVFxx35aJ5B7G/IJ9qsRvpcGNub3rLHcqXo="
|
|
- name: WIREGUARD_ADDRESSES
|
|
value: "10.2.0.2/32"
|
|
- name: WIREGUARD_ENDPOINT_IP
|
|
value: "146.70.58.130"
|
|
- name: WIREGUARD_ENDPOINT_PORT
|
|
value: "51820"
|
|
- name: DOT
|
|
value: "off"
|
|
- name: DNS_UPSTREAM_RESOLVER_TYPE
|
|
value: "plain"
|
|
- name: DNS_UPSTREAM_PLAIN_ADDRESSES
|
|
value: "10.43.0.10:53"
|
|
- name: HEALTH_TARGET_ADDRESS
|
|
value: "api.protonvpn.ch:443,account.proton.me:443"
|
|
- name: FIREWALL_OUTBOUND_SUBNETS
|
|
value: "10.42.0.0/16,10.43.0.0/16"
|
|
volumeMounts:
|
|
- name: gluetun-data
|
|
mountPath: /tmp/gluetun
|
|
ports:
|
|
- containerPort: 8888
|
|
resources:
|
|
requests:
|
|
memory: 128Mi
|
|
cpu: 50m
|
|
limits:
|
|
memory: 512Mi
|
|
cpu: 200m
|
|
|
|
- name: portforward-helper
|
|
image: alpine:latest
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
until apk add -q curl 2>/dev/null; do sleep 5; done
|
|
echo "Watching /tmp/gluetun/forwarded_port for assigned port..."
|
|
while true; do
|
|
if [ -f /tmp/gluetun/forwarded_port ]; then
|
|
PORT=$(cat /tmp/gluetun/forwarded_port)
|
|
if [ -n "$PORT" ] && [ "$PORT" != "0" ]; then
|
|
echo "$(date): Forwarded port: $PORT — updating qBittorrent"
|
|
curl -s -c /tmp/qbt.txt -X POST http://localhost:8080/api/v2/auth/login \
|
|
-d "username=admin&password=adminadmin" >/dev/null 2>&1
|
|
curl -s -b /tmp/qbt.txt -X POST http://localhost:8080/api/v2/app/setPreferences \
|
|
-d "json={\"listen_port\":$PORT,\"random_port\":false}" >/dev/null 2>&1
|
|
echo "$(date): qBittorrent listen port set to $PORT"
|
|
else
|
|
echo "$(date): Port file empty, waiting..."
|
|
fi
|
|
else
|
|
echo "$(date): Waiting for gluetun to write port file..."
|
|
fi
|
|
sleep 45
|
|
done
|
|
volumeMounts:
|
|
- name: gluetun-data
|
|
mountPath: /tmp/gluetun
|
|
resources:
|
|
requests:
|
|
memory: 32Mi
|
|
cpu: 10m
|
|
limits:
|
|
memory: 96Mi
|
|
cpu: 50m
|
|
|
|
- name: qbittorrent
|
|
image: lscr.io/linuxserver/qbittorrent:latest
|
|
env:
|
|
- name: PUID
|
|
value: "1000"
|
|
- name: PGID
|
|
value: "1000"
|
|
- name: TZ
|
|
value: "America/Chicago"
|
|
- name: WEBUI_PORT
|
|
value: "8080"
|
|
ports:
|
|
- containerPort: 8080
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /config
|
|
- name: media
|
|
mountPath: /media
|
|
resources:
|
|
requests:
|
|
memory: 256Mi
|
|
cpu: 100m
|
|
limits:
|
|
memory: 1Gi
|
|
cpu: 500m
|
|
volumes:
|
|
- name: gluetun-data
|
|
emptyDir: {}
|
|
- name: config
|
|
persistentVolumeClaim:
|
|
claimName: fornax-worker-tx34-config
|
|
- name: media
|
|
persistentVolumeClaim:
|
|
claimName: media-data
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: fornax-worker-tx34
|
|
namespace: media
|
|
labels:
|
|
app: fornax-worker-tx34
|
|
fornax-role: worker
|
|
spec:
|
|
selector:
|
|
app: fornax-worker-tx34
|
|
ports:
|
|
- name: webui
|
|
port: 8080
|
|
targetPort: 8080
|
|
type: ClusterIP
|