qbittorrentvpn.yaml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: qbittorrentvpn
  6. namespace: plex
  7. spec:
  8. strategy:
  9. type: Recreate
  10. selector:
  11. matchLabels:
  12. app: qbittorrentvpn
  13. replicas: 1
  14. template:
  15. metadata:
  16. labels:
  17. app: qbittorrentvpn
  18. annotations:
  19. backup.velero.io/backup-volumes-excludes: seedbox,media,media2,data-ec,scratch
  20. spec:
  21. affinity:
  22. nodeAffinity:
  23. requiredDuringSchedulingIgnoredDuringExecution:
  24. nodeSelectorTerms:
  25. - matchExpressions:
  26. - key: seedbox
  27. operator: In
  28. values:
  29. - "true"
  30. containers:
  31. - name: qbittorrentvpn
  32. image: binhex/arch-qbittorrentvpn:5.1.4-1-01
  33. ports:
  34. - containerPort: 8080
  35. name: http-web-svc
  36. securityContext:
  37. privileged: true
  38. env:
  39. - name: DEBUG
  40. value: "true"
  41. - name: ENABLE_PRIVOXY
  42. value: "no"
  43. - name: LAN_NETWORK
  44. value: "172.16.69.0/24,10.42.0.0/16"
  45. - name: NAME_SERVERS
  46. value: "209.244.0.3,209.244.0.4"
  47. - name: PGID
  48. value: "1000"
  49. - name: PUID
  50. value: "1000"
  51. - name: STRICT_PORT_FORWARD
  52. value: "yes"
  53. - name: VPN_CLIENT
  54. value: "wireguard"
  55. - name: VPN_ENABLED
  56. value: "yes"
  57. - name: VPN_PROV
  58. value: "airvpn"
  59. - name: VPN_USER
  60. valueFrom:
  61. secretKeyRef:
  62. name: qbittorrentvpn
  63. key: VPN_USER
  64. - name: VPN_PASS
  65. valueFrom:
  66. secretKeyRef:
  67. name: qbittorrentvpn
  68. key: VPN_PASS
  69. livenessProbe:
  70. exec:
  71. command: ["curl", "--fail", "localhost:8080"]
  72. volumeMounts:
  73. - mountPath: "/media"
  74. name: media
  75. - mountPath: "/media2"
  76. name: media2
  77. - mountPath: "/dataec"
  78. name: data-ec
  79. - mountPath: "/config"
  80. name: config
  81. - mountPath: "/scratch"
  82. name: seedbox
  83. volumes:
  84. - name: media
  85. persistentVolumeClaim:
  86. claimName: plex-pvc
  87. - name: media2
  88. persistentVolumeClaim:
  89. claimName: media2-pvc
  90. - name: data-ec
  91. persistentVolumeClaim:
  92. claimName: data-ec-pvc
  93. - name: config
  94. persistentVolumeClaim:
  95. claimName: qbittorrentvpn-pvc
  96. - name: seedbox
  97. hostPath:
  98. path: /seedbox/torrents
  99. type: Directory
  100. ---
  101. apiVersion: v1
  102. kind: Service
  103. metadata:
  104. name: qbittorrentvpn-service
  105. namespace: plex
  106. spec:
  107. selector:
  108. app: qbittorrentvpn
  109. type: ClusterIP
  110. ports:
  111. - name: qbittorrentvpn-web-port
  112. protocol: TCP
  113. port: 8080
  114. targetPort: http-web-svc
  115. ---
  116. apiVersion: networking.k8s.io/v1
  117. kind: Ingress
  118. metadata:
  119. name: qbittorrentvpn
  120. namespace: plex
  121. annotations:
  122. traefik.ingress.kubernetes.io/router.entrypoints: websecure
  123. traefik.ingress.kubernetes.io/router.middlewares: kube-system-lanonly@kubernetescrd
  124. spec:
  125. rules:
  126. - host: qbittorrentvpn.lan.jibby.org
  127. http:
  128. paths:
  129. - path: /
  130. pathType: Prefix
  131. backend:
  132. service:
  133. name: qbittorrentvpn-service
  134. port:
  135. number: 8080
  136. ---
  137. apiVersion: external-secrets.io/v1
  138. kind: ExternalSecret
  139. metadata:
  140. name: qbittorrentvpn
  141. namespace: plex
  142. spec:
  143. target:
  144. name: qbittorrentvpn
  145. deletionPolicy: Delete
  146. template:
  147. type: Opaque
  148. data:
  149. VPN_USER: |-
  150. {{ .username }}
  151. VPN_PASS: |-
  152. {{ .password }}
  153. data:
  154. - secretKey: username
  155. sourceRef:
  156. storeRef:
  157. name: bitwarden-login
  158. kind: ClusterSecretStore
  159. remoteRef:
  160. key: 19b0020e-51d3-42eb-b78b-b1d7012d1a8a
  161. property: username
  162. - secretKey: password
  163. sourceRef:
  164. storeRef:
  165. name: bitwarden-login
  166. kind: ClusterSecretStore
  167. remoteRef:
  168. key: 19b0020e-51d3-42eb-b78b-b1d7012d1a8a
  169. property: password
  170. ---
  171. apiVersion: apps/v1
  172. kind: Deployment
  173. metadata:
  174. name: qbittorrentvpn-exporter
  175. namespace: plex
  176. spec:
  177. strategy:
  178. type: Recreate
  179. selector:
  180. matchLabels:
  181. app: qbittorrentvpn-exporter
  182. replicas: 1
  183. template:
  184. metadata:
  185. labels:
  186. app: qbittorrentvpn-exporter
  187. spec:
  188. containers:
  189. - name: qbittorrentvpn-exporter
  190. image: ghcr.io/esanchezm/prometheus-qbittorrent-exporter:latest
  191. ports:
  192. - containerPort: 8000
  193. name: metrics
  194. env:
  195. - name: QBITTORRENT_HOST
  196. value: qbittorrentvpn.lan.jibby.org
  197. - name: QBITTORRENT_PORT
  198. value: "443"
  199. - name: QBITTORRENT_SSL
  200. value: "True"
  201. - name: QBITTORRENT_USER
  202. valueFrom:
  203. secretKeyRef:
  204. name: qbittorrentvpn-exporter
  205. key: QBITTORRENT_USER
  206. - name: QBITTORRENT_PASS
  207. valueFrom:
  208. secretKeyRef:
  209. name: qbittorrentvpn-exporter
  210. key: QBITTORRENT_PASS
  211. livenessProbe:
  212. exec:
  213. command:
  214. - "/bin/sh"
  215. - "-c"
  216. - 'wget -O - 0.0.0.0:8000 | grep -E "qbittorrent_up\{.* 1.0"'
  217. initialDelaySeconds: 3
  218. timeoutSeconds: 5
  219. periodSeconds: 3
  220. failureThreshold: 15
  221. resources:
  222. requests:
  223. memory: "0"
  224. limits:
  225. memory: "256Mi"
  226. ---
  227. apiVersion: v1
  228. kind: Service
  229. metadata:
  230. name: qbittorrentvpn-exporter-service
  231. namespace: plex
  232. labels:
  233. app: qbittorrentvpn-exporter
  234. spec:
  235. selector:
  236. app: qbittorrentvpn-exporter
  237. type: ClusterIP
  238. ports:
  239. - name: metrics
  240. protocol: TCP
  241. port: 8000
  242. targetPort: metrics
  243. ---
  244. apiVersion: monitoring.coreos.com/v1
  245. kind: PrometheusRule
  246. metadata:
  247. labels:
  248. prometheus: qbittorrent
  249. role: alert-rules
  250. name: prometheus-qbittorrent-rules
  251. namespace: plex
  252. spec:
  253. groups:
  254. - name: ./qbittorrent.rules
  255. rules:
  256. - alert: QbittorrentErroredTorrents
  257. expr: sum(qbittorrent_torrents_count{status="error"}) > 0
  258. ---
  259. apiVersion: external-secrets.io/v1
  260. kind: ExternalSecret
  261. metadata:
  262. name: qbittorrentvpn-exporter
  263. namespace: plex
  264. spec:
  265. target:
  266. name: qbittorrentvpn-exporter
  267. deletionPolicy: Delete
  268. template:
  269. type: Opaque
  270. data:
  271. QBITTORRENT_USER: |-
  272. {{ .username }}
  273. QBITTORRENT_PASS: |-
  274. {{ .password }}
  275. data:
  276. - secretKey: username
  277. sourceRef:
  278. storeRef:
  279. name: bitwarden-login
  280. kind: ClusterSecretStore
  281. remoteRef:
  282. key: 8dd7dfc3-800d-4af5-8a45-b23f0132806c
  283. property: username
  284. - secretKey: password
  285. sourceRef:
  286. storeRef:
  287. name: bitwarden-login
  288. kind: ClusterSecretStore
  289. remoteRef:
  290. key: 8dd7dfc3-800d-4af5-8a45-b23f0132806c
  291. property: password
  292. # qbit_manage to auto-tag by tracker URL
  293. ---
  294. apiVersion: batch/v1
  295. kind: CronJob
  296. metadata:
  297. name: qbittorrentvpn-manage
  298. namespace: plex
  299. spec:
  300. schedule: "*/10 * * * *"
  301. successfulJobsHistoryLimit: 1
  302. failedJobsHistoryLimit: 1
  303. concurrencyPolicy: Forbid
  304. jobTemplate:
  305. spec:
  306. activeDeadlineSeconds: 60
  307. template:
  308. metadata:
  309. labels:
  310. app: qbittorrentvpn-manage
  311. spec:
  312. restartPolicy: OnFailure
  313. containers:
  314. - name: qbittorrentvpn-manage
  315. image: ghcr.io/stuffanthings/qbit_manage:v4.6.5@sha256:4f36632a138b4e5aeab3b765b7f389087bfb140c80dbbec1343eca74dc351245
  316. command:
  317. - python3
  318. - qbit_manage.py
  319. - "--run"
  320. volumeMounts:
  321. - name: config
  322. mountPath: /config/config.yml
  323. subPath: config.yml
  324. volumes:
  325. - name: config
  326. secret:
  327. secretName: qbittorrentvpn-manage
  328. items:
  329. - key: config.yml
  330. path: config.yml
  331. ---
  332. apiVersion: external-secrets.io/v1
  333. kind: ExternalSecret
  334. metadata:
  335. name: qbittorrentvpn-manage
  336. namespace: plex
  337. spec:
  338. target:
  339. name: qbittorrentvpn-manage
  340. deletionPolicy: Delete
  341. template:
  342. type: Opaque
  343. data:
  344. config.yml: |-
  345. {{ .trackertags }}
  346. qbt:
  347. host: https://qbittorrentvpn.lan.jibby.org
  348. user: {{ .username }}
  349. pass: {{ .password }}
  350. commands:
  351. recheck: false
  352. cat_update: false
  353. tag_update: true
  354. rem_unregistered: false
  355. rem_orphaned: false
  356. tag_tracker_error: false
  357. tag_nohardlinks: false
  358. share_limits: false
  359. skip_cleanup: false
  360. dry_run: false
  361. skip_qb_version_check: false
  362. # Not using any of these fields, but they're required for qbit_manage
  363. cat:
  364. tv-sonarr: Uncategorized
  365. completed: /not/using/cat
  366. recyclebin:
  367. enabled: false
  368. save_torrents: false
  369. split_by_category: false
  370. empty_after_x_days:
  371. directory:
  372. root_dir: /not/using/rootdir
  373. torrents_dir:
  374. orphaned:
  375. max_orphaned_files_to_delete: 50
  376. min_file_age_minutes: 0
  377. empty_after_x_days:
  378. exclude_patterns:
  379. settings:
  380. force_auto_tmm: false
  381. tracker_error_tag: issue
  382. nohardlinks_tag: noHL
  383. stalled_tag: stalledDL
  384. share_limits_tag: ~share_limit
  385. share_limits_min_seeding_time_tag: MinSeedTimeNotReached
  386. share_limits_min_num_seeds_tag: MinSeedsNotMet
  387. share_limits_last_active_tag: LastActiveLimitNotReached
  388. cat_filter_completed: true
  389. share_limits_filter_completed: true
  390. tag_nohardlinks_filter_completed: true
  391. rem_unregistered_filter_completed: false
  392. cat_update_all: true
  393. disable_qbt_default_share_limits: true
  394. tag_stalled_torrents: true
  395. rem_unregistered_grace_minutes: 10
  396. rem_unregistered_max_torrents: 10
  397. private_tag:
  398. force_auto_tmm_ignore_tags: []
  399. rem_unregistered_ignore_list: []
  400. webhooks:
  401. error:
  402. run_start:
  403. run_end:
  404. function:
  405. tag_tracker_error:
  406. share_limits:
  407. data:
  408. - secretKey: username
  409. sourceRef:
  410. storeRef:
  411. name: bitwarden-login
  412. kind: ClusterSecretStore
  413. remoteRef:
  414. key: 8dd7dfc3-800d-4af5-8a45-b23f0132806c
  415. property: username
  416. - secretKey: password
  417. sourceRef:
  418. storeRef:
  419. name: bitwarden-login
  420. kind: ClusterSecretStore
  421. remoteRef:
  422. key: 8dd7dfc3-800d-4af5-8a45-b23f0132806c
  423. property: password
  424. - secretKey: trackertags
  425. sourceRef:
  426. storeRef:
  427. name: bitwarden-notes
  428. kind: ClusterSecretStore
  429. remoteRef:
  430. key: 54c175aa-aa4f-4a28-a8f6-b3f80146e440
  431. # Disabled for now (see suspend: true)
  432. # Sometimes VPN throughput slows down & a restart helps. Other times a restart
  433. # slows things down.
  434. ---
  435. apiVersion: batch/v1
  436. kind: CronJob
  437. metadata:
  438. name: qbittorrentvpn-restart
  439. namespace: plex
  440. spec:
  441. suspend: true
  442. schedule: "*/30 * * * *"
  443. successfulJobsHistoryLimit: 1
  444. failedJobsHistoryLimit: 1
  445. concurrencyPolicy: Forbid
  446. jobTemplate:
  447. spec:
  448. template:
  449. metadata:
  450. labels:
  451. app: qbittorrentvpn-restart
  452. spec:
  453. serviceAccountName: qbittorrentvpn-restart-serviceaccount
  454. securityContext:
  455. runAsUser: 1000
  456. runAsGroup: 1000
  457. restartPolicy: OnFailure
  458. containers:
  459. - name: qbittorrentvpn-restart
  460. image: python:3.14
  461. command:
  462. - python3
  463. - -c
  464. - |
  465. import subprocess
  466. import json
  467. import pprint
  468. import urllib.parse
  469. import sys
  470. import datetime
  471. # Vars to configure
  472. namespace = 'plex'
  473. qparams = {'labelSelector': 'app=qbittorrentvpn'}
  474. max_runtime = datetime.timedelta(days=3)
  475. # serviceaccount/k8s specific vars. Likely don't need to edit these.
  476. serviceaccount_dir = '/var/run/secrets/kubernetes.io/serviceaccount'
  477. apiserver = 'https://kubernetes.default.svc'
  478. token = open(f'{serviceaccount_dir}/token').read()
  479. result = subprocess.run([
  480. 'curl',
  481. '--cacert', f'{serviceaccount_dir}/ca.crt',
  482. '--header', f'Authorization: Bearer {token}',
  483. '-X', 'GET',
  484. f'{apiserver}/api/v1/namespaces/{namespace}/pods?{urllib.parse.urlencode(qparams)}'
  485. ],
  486. capture_output=True,
  487. check=True,
  488. )
  489. pod_list = json.loads(result.stdout)
  490. items = pod_list.get('items')
  491. if items is None or len(items) < 1:
  492. print(f'No pod found? Exiting. {pod_list=}')
  493. sys.exit(1)
  494. if len(items) > 1:
  495. print(f'>1 pod? Exiting. {items=}, {len(items)=}')
  496. sys.exit(1)
  497. pod = items[0]
  498. container_statuses = pod['status']['containerStatuses']
  499. if len(container_statuses) != 1:
  500. print(f'len(containerStatuses) != 1? Exiting. {container_statuses=}')
  501. sys.exit(1)
  502. running = container_statuses[0]['state'].get('running')
  503. if not running:
  504. print(f'Pod not running? Exiting. {container_statuses["state"]=}')
  505. started_at = datetime.datetime.fromisoformat(running["startedAt"])
  506. runtime = datetime.datetime.now(tz=datetime.UTC) - started_at
  507. print(f'{runtime=} > {max_runtime=} ? {runtime > max_runtime}')
  508. if runtime > max_runtime:
  509. pod_name = pod['metadata']['name']
  510. print(f'Deleting pod {pod_name}')
  511. result = subprocess.run([
  512. 'curl',
  513. '--cacert', f'{serviceaccount_dir}/ca.crt',
  514. '--header', f'Authorization: Bearer {token}',
  515. '-X', 'DELETE',
  516. f'{apiserver}/api/v1/namespaces/{namespace}/pods/{pod_name}'
  517. ],
  518. capture_output=True,
  519. check=True,
  520. )
  521. ---
  522. apiVersion: v1
  523. kind: ServiceAccount
  524. metadata:
  525. name: qbittorrentvpn-restart-serviceaccount
  526. namespace: plex
  527. ---
  528. apiVersion: rbac.authorization.k8s.io/v1
  529. kind: RoleBinding
  530. metadata:
  531. name: qbittorrentvpn-restart-serviceaccount-edit
  532. namespace: plex
  533. roleRef:
  534. apiGroup: rbac.authorization.k8s.io
  535. kind: ClusterRole
  536. name: edit
  537. subjects:
  538. - kind: ServiceAccount
  539. name: qbittorrentvpn-restart-serviceaccount
  540. namespace: plex