From 03011a73e884a182a7e80bc943f844bda427bb09 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sat, 11 Apr 2026 09:46:18 -0500 Subject: [PATCH] =?UTF-8?q?Add=20Fornax=20worker=20pods=20=E2=80=94=20US-T?= =?UTF-8?q?X#253=20and=20US-TX#34?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two initial Fornax distributed torrent workers, each a gluetun+qBittorrent+natpmpc-helper pod on a different ProtonVPN TX server with NAT-PMP enabled. VPN private keys stored in Vault at secret/fornax/worker-tx253 and secret/fornax/worker-tx34, surfaced via ExternalSecrets. Workers share the media-data PVC; each has its own config PVC. Services: fornax-worker-tx253:8080 and fornax-worker-tx34:8080 (ClusterIP, media ns) --- .../k8s/media/fornax-external-secrets.yaml | 39 +++ servers/legion/k8s/media/fornax-workers.yaml | 312 ++++++++++++++++++ servers/legion/k8s/media/pvc.yaml | 27 ++ 3 files changed, 378 insertions(+) create mode 100644 servers/legion/k8s/media/fornax-external-secrets.yaml create mode 100644 servers/legion/k8s/media/fornax-workers.yaml diff --git a/servers/legion/k8s/media/fornax-external-secrets.yaml b/servers/legion/k8s/media/fornax-external-secrets.yaml new file mode 100644 index 0000000..e5d67a0 --- /dev/null +++ b/servers/legion/k8s/media/fornax-external-secrets.yaml @@ -0,0 +1,39 @@ +# Fornax worker VPN credentials — one ExternalSecret per worker, each pulls its own private key +# Vault paths: secret/fornax/worker-tx253, secret/fornax/worker-tx34 +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: fornax-worker-tx253-secrets + namespace: media +spec: + refreshInterval: 1h + secretStoreRef: + name: vault + kind: ClusterSecretStore + target: + name: fornax-worker-tx253-secrets + creationPolicy: Owner + data: + - secretKey: PROTONVPN_PRIVATE_KEY + remoteRef: + key: secret/data/fornax/worker-tx253 + property: private_key +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: fornax-worker-tx34-secrets + namespace: media +spec: + refreshInterval: 1h + secretStoreRef: + name: vault + kind: ClusterSecretStore + target: + name: fornax-worker-tx34-secrets + creationPolicy: Owner + data: + - secretKey: PROTONVPN_PRIVATE_KEY + remoteRef: + key: secret/data/fornax/worker-tx34 + property: private_key diff --git a/servers/legion/k8s/media/fornax-workers.yaml b/servers/legion/k8s/media/fornax-workers.yaml new file mode 100644 index 0000000..b20068b --- /dev/null +++ b/servers/legion/k8s/media/fornax-workers.yaml @@ -0,0 +1,312 @@ +# Fornax distributed torrent workers — each is a gluetun+qBittorrent pod on a different VPN server +# Worker TX#253: US-TX#253 (95.173.217.29), NAT-PMP enabled +# Worker TX#34: US-TX#34 (146.70.58.130), NAT-PMP enabled +# Both workers share the media-data PVC; each has its own config PVC and VPN credentials +# The Fornax coordinator proxy (TBD) routes work across these workers + +# ── Worker TX#253 ───────────────────────────────────────────────────────────── +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fornax-worker-tx253 + namespace: media + labels: + app: fornax-worker-tx253 + fornax-role: worker + fornax-server: us-tx-253 +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-253 + 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 + 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: FIREWALL_OUTBOUND_SUBNETS + value: "10.42.0.0/16,10.43.0.0/16" + ports: + - containerPort: 8888 + resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 512Mi + cpu: 200m + + - name: natpmpc-helper + image: alpine:latest + command: + - sh + - -c + - | + echo "Waiting for VPN to establish before installing packages..." + sleep 30 + until apk add -q libnatpmp curl 2>/dev/null; do + echo "apk failed (VPN not ready?), retrying in 10s..." + sleep 10 + done + echo "Packages installed, starting NAT-PMP loop" + while true; do + OUT=$(natpmpc -a 0 0 udp 60 -g 10.2.0.1 2>&1) + PORT=$(echo "$OUT" | grep -oE 'Mapped public port [0-9]+' | grep -oE '[0-9]+$') + if [ -n "$PORT" ]; then + echo "$(date): Got forwarded port $PORT" + natpmpc -a "$PORT" "$PORT" tcp 60 -g 10.2.0.1 >/dev/null 2>&1 + 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): NAT-PMP failed: $OUT" + fi + sleep 45 + done + resources: + requests: + memory: 32Mi + cpu: 10m + limits: + memory: 64Mi + 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: 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#34 ────────────────────────────────────────────────────────────── +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fornax-worker-tx34 + namespace: media + labels: + app: fornax-worker-tx34 + fornax-role: worker + fornax-server: us-tx-34 +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-34 + 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 + 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: FIREWALL_OUTBOUND_SUBNETS + value: "10.42.0.0/16,10.43.0.0/16" + ports: + - containerPort: 8888 + resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 512Mi + cpu: 200m + + - name: natpmpc-helper + image: alpine:latest + command: + - sh + - -c + - | + echo "Waiting for VPN to establish before installing packages..." + sleep 30 + until apk add -q libnatpmp curl 2>/dev/null; do + echo "apk failed (VPN not ready?), retrying in 10s..." + sleep 10 + done + echo "Packages installed, starting NAT-PMP loop" + while true; do + OUT=$(natpmpc -a 0 0 udp 60 -g 10.2.0.1 2>&1) + PORT=$(echo "$OUT" | grep -oE 'Mapped public port [0-9]+' | grep -oE '[0-9]+$') + if [ -n "$PORT" ]; then + echo "$(date): Got forwarded port $PORT" + natpmpc -a "$PORT" "$PORT" tcp 60 -g 10.2.0.1 >/dev/null 2>&1 + 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): NAT-PMP failed: $OUT" + fi + sleep 45 + done + resources: + requests: + memory: 32Mi + cpu: 10m + limits: + memory: 64Mi + 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: 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 diff --git a/servers/legion/k8s/media/pvc.yaml b/servers/legion/k8s/media/pvc.yaml index 2becd35..734b61d 100644 --- a/servers/legion/k8s/media/pvc.yaml +++ b/servers/legion/k8s/media/pvc.yaml @@ -132,3 +132,30 @@ spec: resources: requests: storage: 1Gi +--- +# Fornax worker config PVCs — one per worker pod (qBittorrent config + state) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: fornax-worker-tx253-config + namespace: media +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: fornax-worker-tx34-config + namespace: media +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi