Adds a lightweight Alpine sidecar that runs natpmpc against the ProtonVPN
NAT-PMP gateway (10.2.0.1) every 45s to maintain a forwarded port, then
updates qBittorrent's listen port via API. Without inbound port forwarding
peers can only be dialed out to, which severely limits download speeds on
a gigabit connection.
Audio tracks in some video files return language as a BoxList (list of
Language objects) from the video metadata parser. The existing code
tries to add this directly to a Python set, which fails because BoxList
is not hashable — crashing subtitle search for every movie.
Fix: check if lang has __iter__ and iterate over it, or call .alpha3
directly if it's a single Language object.
Delivered via ConfigMap volumeMount so it survives pod restarts.
The /gluetun binary has restrictive permissions and cannot be exec'd
from a shell wrapper. The OOM fix (512Mi limit) prevents the crashes
that caused stale ip rules, so the wrapper is not needed.
- Increase memory limit 128Mi → 512Mi (DNS block list download needs ~200MB)
- Add command wrapper that cleans stale ip rules before exec'ing gluetun
so container restarts within the same pod don't fail with "file exists"
- Fix deprecated VPN_ENDPOINT_IP/PORT → WIREGUARD_ENDPOINT_IP/PORT
The protonvpn named provider rotates through P2P servers and DNS
health checks were failing on every server it tried. Switching back
to custom provider pinned to the specific US-TX#457 server/key combo
from the ProtonVPN conf file, which is confirmed to work.
This sacrifices automatic port forwarding but restores stable downloads.
Custom WireGuard mode doesn't support NAT-PMP port forwarding — peers
can't connect inbound so most torrents stall. Switching to protonvpn
provider with SERVER_FEATURES=p2p picks a port-forwarding-capable server
and VPN_PORT_FORWARDING=on handles the NAT-PMP handshake. Up command
updates qBittorrent listen port automatically when gluetun gets the port.
Seerr's scheduler compares mediaServerType against numeric enum values
(PLEX=1, JELLYFIN=2, EMBY=3). restore.js was setting the string 'JELLYFIN'
which never matched, so Jellyfin scan jobs were never registered in the
scheduler — causing 'Watch on Jellyfin' buttons to never appear.
Bazarr initializes scheduler, DB, and Radarr/Sonarr connections before
serving HTTP. 30s initialDelay was too short, causing restart loops.
Also add readinessProbe and failureThreshold for resilience.
Seerr's checkOverseerrMerge calls Settings.load with raw=true, which
replaces this.data entirely with file contents. If settings.main is
absent (as in our jellyfin-only restore), it crashes at line 418:
'Cannot read properties of undefined (reading apiKey)'.
restore.js now ensures settings.main exists and mediaServerType=JELLYFIN,
which both prevents the crash and causes checkOverseerrMerge to return
early (migration already done).
Init container detects if Jellyfin apiKey is missing and restores
it from the seerr-defaults ConfigMap. Prevents config loss on pod
restarts or when the Jellyfin API key fails validation.
Backs up all service config PVCs (Jellyfin, Radarr, Sonarr, Prowlarr,
qBittorrent, Bazarr, Seerr) at 3am daily to legion-media-backup R2 bucket.
Media files are excluded — they are re-downloadable.
Adds a Dockerfile extending wordpress:latest to include wp-cli at
/usr/local/bin/wp. Required for sync-whitepapers.py to manage content
via kubectl exec without manual installation after each pod restart.
Split-horizon breaks VPN/LAN hybrid scenarios. Let all clients use
public DNS (Cloudflare proxy IPs) and route via CF tunnel. Works
everywhere, no network-topology-dependent DNS required.
LAN IP 192.168.68.77 unreachable over Tailscale VPN. 100.64.0.1 works
for both LAN (Tailscale installed) and remote VPN clients. Also adds
*.neuralplatform.dev which was missing from the configmap.