diff --git a/charts/qbittorrent-vpn/Chart.yaml b/charts/qbittorrent-vpn/Chart.yaml new file mode 100644 index 0000000..0a6aefa --- /dev/null +++ b/charts/qbittorrent-vpn/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: qbittorrent-vpn +description: qBittorrent with Gluetun VPN sidecar for Kubernetes +type: application +version: 0.0.1 +appVersion: 5.1.0 +maintainers: + - name: Richard Tomik + email: richard.tomik@proton.me +keywords: + - qbittorrent + - vpn + - gluetun + - torrent +home: https://github.com/rtomik/helm-charts +sources: + - https://github.com/linuxserver/docker-qbittorrent + - https://github.com/qdm12/gluetun \ No newline at end of file diff --git a/charts/qbittorrent-vpn/NOTES.txt b/charts/qbittorrent-vpn/NOTES.txt new file mode 100644 index 0000000..1f71870 --- /dev/null +++ b/charts/qbittorrent-vpn/NOTES.txt @@ -0,0 +1,38 @@ +Thank you for installing {{ .Chart.Name }}. + +Your qBittorrent with VPN has been deployed successfully! + +1. Get the application URL: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "qbittorrent-vpn.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "qbittorrent-vpn.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "qbittorrent-vpn.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "qbittorrent-vpn.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + Visit http://127.0.0.1:{{ .Values.service.port }} to access qBittorrent +{{- end }} + +2. VPN Status: + To check the VPN connection status: + kubectl exec -it -n {{ .Release.Namespace }} deployment/{{ include "qbittorrent-vpn.fullname" . }} -c gluetun -- curl -s http://localhost:8000/v1/vpn/status + +3. Public IP: + To check your current public IP through the VPN: + kubectl exec -it -n {{ .Release.Namespace }} deployment/{{ include "qbittorrent-vpn.fullname" . }} -c gluetun -- curl -s http://localhost:8000/v1/publicip/ip + +4. Verify qBittorrent: + Make sure qBittorrent is functioning by accessing the Web UI at the URL in step 1. + +For more information about this chart: +https://github.com/rtomik/helm-charts/tree/main/charts/qbittorrent-vpn \ No newline at end of file diff --git a/charts/qbittorrent-vpn/readme.md b/charts/qbittorrent-vpn/readme.md new file mode 100644 index 0000000..a228c6e --- /dev/null +++ b/charts/qbittorrent-vpn/readme.md @@ -0,0 +1,270 @@ +# qBittorrent with Gluetun VPN + +A Helm chart for deploying qBittorrent with a Gluetun VPN sidecar container on Kubernetes. + +## Introduction + +This chart deploys [qBittorrent](https://www.qbittorrent.org/) alongside [Gluetun](https://github.com/qdm12/gluetun), a VPN client/tunnel in a container, to ensure all BitTorrent traffic is routed through the VPN. The chart supports all major VPN providers and protocols through Gluetun's comprehensive compatibility. + +Note: Currently only tested with NordVPN an OpenVPN configuration. + +## Features + +- **Multiple VPN Providers**: Support for 30+ VPN providers including NordVPN, ProtonVPN, Private Internet Access, ExpressVPN, Surfshark, Mullvad, and more +- **Protocol Support**: Use OpenVPN or WireGuard based on your provider's capabilities +- **Server Selection**: Choose servers by country, city, or specific hostnames with optional randomization +- **Security**: Proper container security settings to ensure traffic only flows through the VPN +- **Health Monitoring**: Integrated health checks for both qBittorrent and the VPN connection +- **Persistence**: Separate volume storage for configuration and downloads +- **Web UI**: Access qBittorrent via web interface with optional ingress support +- **Proxy Services**: HTTP and Shadowsocks proxies for additional devices to use the VPN tunnel + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- PV provisioner support in the cluster +- A valid subscription to a VPN service + +## Installation + +### Add the Repository + +```bash +helm repo add rtomik-charts https://rtomik.github.io/helm-charts +helm repo update +``` + +### Create a Secret for VPN Credentials + +For better security, store your VPN credentials in a Kubernetes secret: + +```bash +# For OpenVPN authentication +kubectl create secret generic vpn-credentials \ + --namespace default \ + --from-literal=username='your-vpn-username' \ + --from-literal=password='your-vpn-password' + +# For WireGuard authentication (if using WireGuard) +kubectl create secret generic wireguard-keys \ + --namespace default \ + --from-literal=private_key='your-wireguard-private-key' +``` + +Then reference this secret in your values: + +```yaml +gluetun: + credentials: + create: false + existingSecret: "vpn-credentials" + usernameKey: "username" + passwordKey: "password" +``` + +### Install the Chart + +```bash +# Option 1: Installation with custom values file (recommended) +helm install qbittorrent-vpn rtomik-charts/qbittorrent-vpn -f values.yaml -n media + +# Option 2: Installation with inline parameter overrides +helm install qbittorrent-vpn rtomik-charts/qbittorrent-vpn -n media \ + --set gluetun.vpn.provider=nordvpn \ + --set gluetun.vpn.serverCountries=Germany \ + --set-string gluetun.credentials.existingSecret=vpn-credentials +``` + +## Uninstallation + +```bash +helm uninstall qbittorrent-vpn -n media +``` + +Note: This will not delete Persistent Volume Claims. To delete them: + +```bash +kubectl delete pvc -l app.kubernetes.io/instance=qbittorrent-vpn +``` + +## Configuration + +### Key Parameters + +| Parameter | Description | Default | +|---------------------------------------|-------------------------------------------------------|----------------------------| +| `qbittorrent.image.repository` | qBittorrent image repository | `linuxserver/qbittorrent` | +| `qbittorrent.image.tag` | qBittorrent image tag | `latest` | +| `gluetun.image.repository` | Gluetun image repository | `qmcgaw/gluetun` | +| `gluetun.image.tag` | Gluetun image tag | `v3.40.0` | +| `gluetun.vpn.provider` | VPN provider name | `nordvpn` | +| `gluetun.vpn.type` | VPN protocol (`openvpn` or `wireguard`) | `openvpn` | +| `gluetun.vpn.serverCountries` | Countries to connect to (comma-separated) | `Germany` | +| `persistence.config.size` | Size of PVC for qBittorrent config | `2Gi` | +| `persistence.downloads.size` | Size of PVC for downloads | `100Gi` | +| `ingress.enabled` | Enable ingress controller resource | `true` | +| `ingress.hosts[0].host` | Hostname for the ingress | `qbittorrent.domain.com` | + +For a complete list of parameters, see the [values.yaml](values.yaml) file. + +### Example: Using with NordVPN + +```yaml +gluetun: + vpn: + provider: "nordvpn" + type: "openvpn" + serverCountries: "United States" + openvpn: + NORDVPN_CATEGORY: "P2P" # For torrent-optimized servers + credentials: + create: true + username: "your-nordvpn-username" + password: "your-nordvpn-password" +``` + +### Example: Using with ProtonVPN + +```yaml +gluetun: + vpn: + provider: "protonvpn" + type: "openvpn" + serverCountries: "Switzerland" + openvpn: + PROTONVPN_TIER: "2" # 0 is free, 2 is paid (Plus/Visionary) + SERVER_FEATURES: "p2p" # For torrent support + credentials: + create: true + username: "protonvpn-username" + password: "protonvpn-password" +``` + +### Example: Using with Private Internet Access + +```yaml +gluetun: + vpn: + provider: "private internet access" + type: "openvpn" + serverCountries: "US" + credentials: + create: true + username: "pia-username" + password: "pia-password" + settings: + VPN_PORT_FORWARDING: "on" # PIA supports port forwarding +``` + +## VPN Provider Support + +This chart supports all VPN providers compatible with Gluetun, including: + +- AirVPN +- Cyberghost +- ExpressVPN +- FastestVPN +- HideMyAss +- IPVanish +- IVPN +- Mullvad +- NordVPN +- Perfect Privacy +- Private Internet Access (PIA) +- PrivateVPN +- ProtonVPN +- PureVPN +- Surfshark +- TorGuard +- VyprVPN +- WeVPN +- Windscribe + +For the complete list and provider-specific options, see the [Gluetun Providers Documentation](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers). + +## Additional Features + +### Accessing the HTTP Proxy + +Gluetun provides an HTTP proxy on port 8888 that can be used by other applications to route traffic through the VPN. To expose this proxy: + +```yaml +service: + proxies: + enabled: true + httpPort: 8888 + socksPort: 8388 +``` + +### Firewall Settings + +By default, the chart enables the Gluetun firewall to prevent leaks if the VPN connection drops. You can customize this: + +```yaml +gluetun: + settings: + FIREWALL: "on" + FIREWALL_OUTBOUND_SUBNETS: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" +``` + +### Port Forwarding + +For VPN providers that support port forwarding (like PIA): + +```yaml +gluetun: + settings: + VPN_PORT_FORWARDING: "on" + STATUS_FILE: "/tmp/gluetun-status.json" +``` + +## Troubleshooting + +### VPN Connection Issues + +If the VPN isn't connecting properly: + +1. Check the Gluetun logs: + ```bash + kubectl logs deployment/qbittorrent-vpn -c gluetun + ``` + +2. Verify your credentials are correct: + ```bash + kubectl describe secret vpn-credentials + ``` + +3. Try setting the log level to debug for more detailed information: + ```yaml + gluetun: + extraEnv: + - name: LOG_LEVEL + value: "debug" + ``` + +### qBittorrent Can't Create Directories + +If you see errors like "Could not create required directory": + +1. Make sure the init container is enabled and properly configured +2. Ensure proper `fsGroup` is set in the `podSecurityContext` +3. Check that the persistence volume allows the correct permissions + +### Firewall/Security Issues + +If you encounter iptables or network issues: + +1. Ensure the Gluetun container has `privileged: true` +2. Verify the `NET_ADMIN` capability is added +3. Check that the `/dev/net/tun` device is correctly mounted + +## License + +This chart is licensed under the MIT License. + +## Acknowledgements + +- [Gluetun](https://github.com/qdm12/gluetun) by [qdm12](https://github.com/qdm12) +- [LinuxServer.io](https://linuxserver.io/) for the qBittorrent container +- The qBittorrent team for the excellent torrent client \ No newline at end of file diff --git a/charts/qbittorrent-vpn/templates/_helpers.tpl b/charts/qbittorrent-vpn/templates/_helpers.tpl new file mode 100644 index 0000000..7beb65d --- /dev/null +++ b/charts/qbittorrent-vpn/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "qbittorrent-vpn.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "qbittorrent-vpn.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "qbittorrent-vpn.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "qbittorrent-vpn.labels" -}} +helm.sh/chart: {{ include "qbittorrent-vpn.chart" . }} +{{ include "qbittorrent-vpn.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "qbittorrent-vpn.selectorLabels" -}} +app.kubernetes.io/name: {{ include "qbittorrent-vpn.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/charts/qbittorrent-vpn/templates/deployment.yaml b/charts/qbittorrent-vpn/templates/deployment.yaml new file mode 100644 index 0000000..e3d55a5 --- /dev/null +++ b/charts/qbittorrent-vpn/templates/deployment.yaml @@ -0,0 +1,306 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }} + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "qbittorrent-vpn.selectorLabels" . | nindent 6 }} + strategy: + type: Recreate # Using Recreate instead of RollingUpdate for stateful pods + template: + metadata: + labels: + {{- include "qbittorrent-vpn.selectorLabels" . | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + # Add hostNetwork if specified + {{- if .Values.hostNetwork }} + hostNetwork: {{ .Values.hostNetwork }} + {{- end }} + + # Init containers if needed for directory setup + {{- if .Values.initContainers }} + initContainers: + {{- toYaml .Values.initContainers | nindent 8 }} + {{- end }} + + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + + containers: + {{- if .Values.gluetun.enabled }} + # Gluetun VPN container + - name: gluetun + image: "{{ .Values.gluetun.image.repository }}:{{ .Values.gluetun.image.tag }}" + imagePullPolicy: {{ .Values.gluetun.image.pullPolicy }} + securityContext: + {{- toYaml .Values.gluetun.securityContext | nindent 12 }} + env: + # VPN Provider selection - Common settings for all VPN types + - name: VPN_SERVICE_PROVIDER + value: {{ .Values.gluetun.vpn.provider | quote }} + - name: VPN_TYPE + value: {{ .Values.gluetun.vpn.type | quote }} + - name: SERVER_COUNTRIES + value: {{ .Values.gluetun.vpn.serverCountries | quote }} + {{- if .Values.gluetun.vpn.serverNames }} + - name: SERVER_HOSTNAMES + value: {{ .Values.gluetun.vpn.serverNames | quote }} + {{- end }} + {{- if .Values.gluetun.vpn.serverCities }} + - name: SERVER_CITIES + value: {{ .Values.gluetun.vpn.serverCities | quote }} + {{- end }} + {{- if .Values.gluetun.vpn.randomize }} + - name: SERVER_HOSTNAMES_RANDOMIZED + value: {{ .Values.gluetun.vpn.randomize | quote }} + {{- end }} + + # OpenVPN specific configuration + {{- if eq .Values.gluetun.vpn.type "openvpn" }} + {{- if .Values.gluetun.credentials.create }} + - name: OPENVPN_USER + valueFrom: + secretKeyRef: + name: {{ include "qbittorrent-vpn.fullname" . }}-vpn-credentials + key: {{ .Values.gluetun.credentials.usernameKey }} + - name: OPENVPN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "qbittorrent-vpn.fullname" . }}-vpn-credentials + key: {{ .Values.gluetun.credentials.passwordKey }} + {{- else if .Values.gluetun.credentials.existingSecret }} + - name: OPENVPN_USER + valueFrom: + secretKeyRef: + name: {{ .Values.gluetun.credentials.existingSecret }} + key: {{ .Values.gluetun.credentials.usernameKey }} + - name: OPENVPN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.gluetun.credentials.existingSecret }} + key: {{ .Values.gluetun.credentials.passwordKey }} + {{- end }} + + # Additional OpenVPN settings + {{- with .Values.gluetun.vpn.openvpn }} + {{- range $key, $value := . }} + - name: {{ $key | upper }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + {{- end }} + + # WireGuard specific configuration + {{- if eq .Values.gluetun.vpn.type "wireguard" }} + {{- if and .Values.gluetun.vpn.wireguard.privateKey .Values.gluetun.credentials.create }} + - name: WIREGUARD_PRIVATE_KEY + valueFrom: + secretKeyRef: + name: {{ include "qbittorrent-vpn.fullname" . }}-vpn-credentials + key: wireguard_private_key + {{- else if and .Values.gluetun.vpn.wireguard.privateKeyExistingSecret .Values.gluetun.vpn.wireguard.privateKeyExistingSecretKey }} + - name: WIREGUARD_PRIVATE_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.gluetun.vpn.wireguard.privateKeyExistingSecret }} + key: {{ .Values.gluetun.vpn.wireguard.privateKeyExistingSecretKey }} + {{- end }} + + # Additional WireGuard settings + {{- with .Values.gluetun.vpn.wireguard }} + {{- if .addresses }} + - name: WIREGUARD_ADDRESSES + value: {{ .addresses | quote }} + {{- end }} + {{- if .endpointIP }} + - name: WIREGUARD_ENDPOINT_IP + value: {{ .endpointIP | quote }} + {{- end }} + {{- if .endpointPort }} + - name: WIREGUARD_ENDPOINT_PORT + value: {{ .endpointPort | quote }} + {{- end }} + {{- if .publicKey }} + - name: WIREGUARD_PUBLIC_KEY + value: {{ .publicKey | quote }} + {{- end }} + {{- end }} + {{- end }} + + # Gluetun general settings + {{- with .Values.gluetun.settings }} + {{- range $key, $value := . }} + - name: {{ $key | upper }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + + # Extra environment variables + {{- with .Values.gluetun.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + + ports: + - name: control + containerPort: 8000 + protocol: TCP + - name: http-proxy + containerPort: 8888 + protocol: TCP + - name: shadowsocks-tcp + containerPort: 8388 + protocol: TCP + - name: shadowsocks-udp + containerPort: 8388 + protocol: UDP + {{- with .Values.gluetun.extraPorts }} + {{- toYaml . | nindent 12 }} + {{- end }} + + volumeMounts: + # Mount tun device for VPN + - name: tun + mountPath: /dev/net/tun + {{- if .Values.gluetun.persistence.enabled }} + - name: gluetun-config + mountPath: /gluetun + {{- end }} + {{- with .Values.gluetun.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + + resources: + {{- toYaml .Values.gluetun.resources | nindent 12 }} + + {{- end }} + + # qBittorrent container + - name: qbittorrent + image: "{{ .Values.qbittorrent.image.repository }}:{{ .Values.qbittorrent.image.tag }}" + imagePullPolicy: {{ .Values.qbittorrent.image.pullPolicy }} + {{- if .Values.qbittorrent.securityContext }} + securityContext: + {{- toYaml .Values.qbittorrent.securityContext | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.qbittorrent.service.port }} + protocol: TCP + {{- if .Values.qbittorrent.bittorrentPort }} + - name: bittorrent-tcp + containerPort: {{ .Values.qbittorrent.bittorrentPort }} + protocol: TCP + - name: bittorrent-udp + containerPort: {{ .Values.qbittorrent.bittorrentPort }} + protocol: UDP + {{- end }} + + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + successThreshold: {{ .Values.probes.liveness.successThreshold }} + {{- end }} + + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + successThreshold: {{ .Values.probes.readiness.successThreshold }} + {{- end }} + + env: + {{- range .Values.qbittorrent.env }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- with .Values.qbittorrent.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + + volumeMounts: + {{- if .Values.qbittorrent.persistence.config.enabled }} + - name: config + mountPath: {{ .Values.qbittorrent.persistence.config.mountPath }} + {{- end }} + + {{- if .Values.qbittorrent.persistence.downloads.enabled }} + - name: downloads + mountPath: {{ .Values.qbittorrent.persistence.downloads.mountPath }} + {{- end }} + + {{- with .Values.qbittorrent.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + + resources: + {{- toYaml .Values.qbittorrent.resources | nindent 12 }} + + volumes: + # Create /dev/net/tun as a device + - name: tun + hostPath: + path: /dev/net/tun + type: CharDevice + + {{- if .Values.qbittorrent.persistence.config.enabled }} + - name: config + persistentVolumeClaim: + claimName: {{ if .Values.qbittorrent.persistence.config.existingClaim }}{{ .Values.qbittorrent.persistence.config.existingClaim }}{{ else }}{{ include "qbittorrent-vpn.fullname" . }}-config{{ end }} + {{- end }} + + {{- if .Values.qbittorrent.persistence.downloads.enabled }} + - name: downloads + persistentVolumeClaim: + claimName: {{ if .Values.qbittorrent.persistence.downloads.existingClaim }}{{ .Values.qbittorrent.persistence.downloads.existingClaim }}{{ else }}{{ include "qbittorrent-vpn.fullname" . }}-downloads{{ end }} + {{- end }} + + {{- if and .Values.gluetun.enabled .Values.gluetun.persistence.enabled }} + {{- if .Values.gluetun.persistence.useEmptyDir }} + - name: gluetun-config + emptyDir: {} + {{- else }} + - name: gluetun-config + persistentVolumeClaim: + claimName: {{ if .Values.gluetun.persistence.existingClaim }}{{ .Values.gluetun.persistence.existingClaim }}{{ else }}{{ include "qbittorrent-vpn.fullname" . }}-gluetun{{ end }} + {{- end }} + {{- end }} + + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/charts/qbittorrent-vpn/templates/ingress.yaml b/charts/qbittorrent-vpn/templates/ingress.yaml new file mode 100644 index 0000000..4a0572b --- /dev/null +++ b/charts/qbittorrent-vpn/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }} + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + {{- if .secretName }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "qbittorrent-vpn.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/qbittorrent-vpn/templates/pvc.yaml b/charts/qbittorrent-vpn/templates/pvc.yaml new file mode 100644 index 0000000..e8f1792 --- /dev/null +++ b/charts/qbittorrent-vpn/templates/pvc.yaml @@ -0,0 +1,55 @@ +{{- if and .Values.qbittorrent.persistence.config.enabled (not .Values.qbittorrent.persistence.config.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }}-config + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.qbittorrent.persistence.config.accessMode | quote }} + {{- if .Values.qbittorrent.persistence.config.storageClass }} + storageClassName: {{ .Values.qbittorrent.persistence.config.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.qbittorrent.persistence.config.size | quote }} +{{- end }} + +{{- if and .Values.qbittorrent.persistence.downloads.enabled (not .Values.qbittorrent.persistence.downloads.existingClaim) }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }}-downloads + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.qbittorrent.persistence.downloads.accessMode | quote }} + {{- if .Values.qbittorrent.persistence.downloads.storageClass }} + storageClassName: {{ .Values.qbittorrent.persistence.downloads.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.qbittorrent.persistence.downloads.size | quote }} +{{- end }} + +{{- if and .Values.gluetun.enabled .Values.gluetun.persistence.enabled (not .Values.gluetun.persistence.useEmptyDir) (not .Values.gluetun.persistence.existingClaim) }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }}-gluetun + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.gluetun.persistence.accessMode | quote }} + {{- if .Values.gluetun.persistence.storageClass }} + storageClassName: {{ .Values.gluetun.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.gluetun.persistence.size | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/qbittorrent-vpn/templates/secret.yaml b/charts/qbittorrent-vpn/templates/secret.yaml new file mode 100644 index 0000000..c2eaafe --- /dev/null +++ b/charts/qbittorrent-vpn/templates/secret.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.gluetun.enabled .Values.gluetun.credentials.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }}-vpn-credentials + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} +type: Opaque +data: + {{- if eq .Values.gluetun.vpn.type "openvpn" }} + {{ .Values.gluetun.credentials.usernameKey }}: {{ .Values.gluetun.credentials.username | b64enc | quote }} + {{ .Values.gluetun.credentials.passwordKey }}: {{ .Values.gluetun.credentials.password | b64enc | quote }} + {{- end }} + + {{- if and (eq .Values.gluetun.vpn.type "wireguard") .Values.gluetun.vpn.wireguard.privateKey }} + wireguard_private_key: {{ .Values.gluetun.vpn.wireguard.privateKey | b64enc | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/qbittorrent-vpn/templates/service.yaml b/charts/qbittorrent-vpn/templates/service.yaml new file mode 100644 index 0000000..939289c --- /dev/null +++ b/charts/qbittorrent-vpn/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "qbittorrent-vpn.fullname" . }} + labels: + {{- include "qbittorrent-vpn.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "qbittorrent-vpn.selectorLabels" . | nindent 4 }} diff --git a/charts/qbittorrent-vpn/values.yaml b/charts/qbittorrent-vpn/values.yaml new file mode 100644 index 0000000..f0f973d --- /dev/null +++ b/charts/qbittorrent-vpn/values.yaml @@ -0,0 +1,228 @@ +## Global settings +nameOverride: "" +fullnameOverride: "" + +## Deployment settings +replicaCount: 1 +revisionHistoryLimit: 3 + +## Pod security settings +podSecurityContext: + runAsNonRoot: false + runAsUser: 0 # Run all containers as root + fsGroup: 0 # Use root group for volumes + +## qBittorrent Image settings +qbittorrent: + image: + repository: linuxserver/qbittorrent + tag: 5.1.0 + pullPolicy: IfNotPresent + + securityContext: {} + + # Open port for BitTorrent traffic + bittorrentPort: 6881 + + env: + - name: PUID + value: "0" # Run as root + - name: PGID + value: "0" # Root group + - name: TZ + value: "UTC" + - name: WEBUI_PORT + value: "8080" + + + extraEnv: [] + + service: + port: 8080 + + #resources: + # limits: + # cpu: 1000m + # memory: 2Gi + # requests: + # cpu: 200m + # memory: 512Mi + + persistence: + config: + enabled: true + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 2Gi + mountPath: /config + + downloads: + enabled: true + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 2Gi + mountPath: /downloads + + # Volume mounts specific to qBittorrent + extraVolumeMounts: [] + + # Volumes specific to qBittorrent + extraVolumes: [] + +# Probes for qBittorrent +probes: + liveness: + enabled: true + path: / + initialDelaySeconds: 0 # Startup probe handles delayed start + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + + readiness: + enabled: true + path: / + initialDelaySeconds: 0 # Startup probe handles delayed start + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + +## Gluetun VPN settings +gluetun: + enabled: true + image: + repository: qmcgaw/gluetun + tag: v3.40.0 # Latest version as of this writing + pullPolicy: IfNotPresent + + securityContext: + privileged: true + capabilities: + add: + - NET_ADMIN + + # VPN provider configuration + vpn: + # Choose from: nordvpn, protonvpn, expressvpn, surfshark, mullvad, ivpn, private internet access, etc. + provider: "nordvpn" + + # Choose from: openvpn or wireguard + type: "openvpn" + + # Server selection (comma-separated lists) + serverCountries: "Netherlands" # e.g., "Netherlands,Germany,Sweden" + serverCities: "" # e.g., "Amsterdam,Frankfurt" (optional) + serverNames: "" # e.g., "nl1,nl2" (optional) + randomize: "true" # Randomize server selection + + # OpenVPN specific settings (when type is "openvpn") + openvpn: + # Add any OpenVPN specific settings here, they'll be converted to env vars + OPENVPN_PROTOCOL: "udp" + + # WireGuard specific settings (when type is "wireguard") + wireguard: + privateKey: "" # Will be stored in Secret if provided + privateKeyExistingSecret: "" + privateKeyExistingSecretKey: "" + addresses: "" # e.g., "10.64.222.21/32" + endpointIP: "" # Optional: specify endpoint IP + endpointPort: "" # Optional: specify endpoint port + publicKey: "" # Optional: server public key + + # VPN credentials (choose one method) + credentials: + create: true # set to false if using existing secret + # For OpenVPN (normal credentials) + username: "" + password: "" + # For WireGuard, the privateKey is specified in vpn.wireguard.privateKey + + # Alternatively, reference an existing secret + existingSecret: "" + usernameKey: "username" + passwordKey: "password" + + # General Gluetun settings as environment variables + settings: + FIREWALL: "on" + FIREWALL_OUTBOUND_SUBNETS: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" + DNS_ADDRESS: "1.1.1.1" + HEALTH_SERVER_PORT: "8000" + + # Important: Add these settings to make networking work correctly with ingress + SERVER_ALLOWLIST: "qbittorrent:8080" # Allow accessing qBittorrent container + FIREWALL_INPUT_PORTS: "8080" # Allow ingress traffic to port 8080 + FIREWALL_DEBUG: "on" # Enable firewall debugging (temporarily) + JOURNALD: "off" # Disable journald (not needed for debugging) + + # Optional port forwarding + VPN_PORT_FORWARDING: "off" + + # Extra environment variables + extraEnv: + - name: LOG_LEVEL + value: "info" + + # Extra ports to expose + extraPorts: [] + # - name: custom-port + # containerPort: 9999 + # protocol: TCP + + # Resources for Gluetun + resources: + limits: + cpu: 300m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + + # Persistence for Gluetun + persistence: + enabled: true + existingClaim: false + storageClass: "" + accessMode: ReadWriteOnce + size: 100Mi + + # Volume mounts specific to Gluetun + extraVolumeMounts: [] + + # Volumes specific to Gluetun + extraVolumes: [] + +## Service settings +service: + type: ClusterIP + port: 8080 + +## Ingress settings +ingress: + enabled: false + className: "" + annotations: [] + hosts: + - host: qbittorrent.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - qbittorrent.example.com + +# Additional specifications +nodeSelector: {} +tolerations: [] +affinity: {} +podAnnotations: {} +extraVolumes: [] + +# Temporary options for development/debugging +hostNetwork: false +initContainers: [] \ No newline at end of file