qbittorrentvpn.yaml 15 KB

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