Compare commits

..

57 Commits

Author SHA1 Message Date
gilgamezh 79a28a674a feat(maintainerr): deploy for watched-movie cleanup (Plex -> Radarr)
Rule-based deletion of watched movies from Radarr (with files), driven by
Maintainerr. Raw manifests + directory-type Argo Application (no Helm).
Config on shared plex-data NFS PVC (subPath configs/maintainerr); Recreate
strategy since it uses SQLite on RWX NFS. ClusterIP only, no ingress —
access via kubectl/k9s port-forward.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:34:44 +02:00
gilgamezh d6ee993a60 docs: add CloudNativePG migration TODO for postgresql
Plan to move both Bitnami postgres instances (pgsql PG16 in default,
gitea-postgresql PG17 bundled in gitea) to CloudNativePG, since Bitnami
images are frozen (bitnamilegacy). Not executed -- planning doc only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 11:15:54 +02:00
gilgamezh 2c68a21d0b ops(metallb): upgrade 0.13.12 -> 0.16.1, pin native L2 (no FRR)
0.16.1 chart defaults frr.enabled=false but frrk8s.enabled=true, which
deploys a heavy frr-k8s daemonset. With no BGP peers (pure L2/ARP), FRR is
unnecessary and its images caused DiskPressure on the Pi nodes, evicting a
speaker and stalling the rollout. Disable both frr and frrk8s for a single
-container L2 speaker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 11:13:59 +02:00
gilgamezh 1b3f34a432 build: upgrade qbittorrent 5.1.4-r3-ls453 -> 5.2.1_v2.0.12-ls459
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 10:59:16 +02:00
gilgamezh 261aebfd10 ops(gitea): Recreate strategy to avoid RWO upgrade deadlock
Bumped gitea helm chart 12.4.0->12.6.0 (app 1.24.6->1.26.1). The chart
default RollingUpdate (maxSurge 100%/maxUnavailable 0) surges a second pod
that can't mount the single RWO NFS PVC, deadlocking 'helm upgrade --wait'.
Recreate avoids it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 10:59:03 +02:00
gilgamezh 9b24978342 docs: add CLAUDE.md (GitOps flow + AirVPN/gluetun DNS gotcha)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 10:20:16 +02:00
gilgamezh 1a91b72464 fix(qbittorrent): use AirVPN plaintext DNS, disable gluetun DoT
AirVPN blocks outbound DNS-over-TLS (tcp/853), so gluetun's default DoT
resolver at 127.0.0.1 never gets answers. The startup healthcheck's
"lookup cloudflare.com" then times out and the VPN restarts every ~6s
in a permanent loop, leaving qbittorrent with no working DNS.

Verified inside the pod netns: tunnel egress works (ping 8.8.8.8 18ms),
AirVPN's pushed resolver 10.128.0.1 resolves fine, but tcp/853 to both
1.1.1.1 and 8.8.8.8 times out.

Set DOT=off and DNS_ADDRESS=10.128.0.1 so gluetun points resolv.conf at
AirVPN's pushed DNS, reached over the tunnel (no DNS leak, no port 853).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 10:17:11 +02:00
argocd-image-updater ac637adaf4 build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10611-1e34174b1-ls306' to '1.43.2.10687-563d026ea-ls307'
2026-05-19 19:33:41 +00:00
argocd-image-updater 6082e6fc14 build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10611-1e34174b1-ls305' to '1.43.1.10611-1e34174b1-ls306'
2026-05-18 12:53:50 +00:00
gilgamezh 7e0a38d65f build: pin gluetun to v3.41.1
Fixes VPN restart loop after :latest pulled a build with the Alpine 3.22
iptables parsing regression and the healthcheck race (#3123). v3.41.1
includes the k8s cluster-DNS auto-detection so DNS lookups in the
startup healthcheck no longer time out behind the killswitch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 09:24:50 +02:00
gilgamezh 3b480d6abf build: backup traefik HelmChartConfig from k3s master manifests
Snapshot of /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
on turing1 after dropping the v2 image pin during the Traefik v3
migration. Lives only on the node otherwise — track it here so it can
be restored on a node rebuild.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 09:24:50 +02:00
gilgamezh 3ace05a695 build: migrate ingresses for Traefik v3 (k3s upgrade)
k3s update bumped Traefik chart 37 → 39, dropping v2 support. Replace
the v2-only `whitelist.sourcerange` annotation on the gitea ingress
with an `ipAllowList` Middleware (resources/gitea-middleware.yaml),
referenced via `router.middlewares`. Switch the default-ns ingresses
(kube-plex, radarr, sonarr, lidarr) from the deprecated
`kubernetes.io/ingress.class` annotation to `spec.ingressClassName`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 09:24:50 +02:00
argocd-image-updater 290ce6a103 build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10611-1e34174b1-ls304' to '1.43.1.10611-1e34174b1-ls305'
2026-05-11 12:31:16 +00:00
gilgamezh e230129119 build: use Recreate strategy for qbittorrent
qbittorrent holds an exclusive lockfile on /config; rolling updates
deadlock because the new pod can't acquire the lock until the old one
is gone.
2026-05-07 10:03:16 +02:00
gilgamezh 269ab53002 build: pin qbittorrent to 5.1.4-r3-ls453 2026-05-07 09:51:40 +02:00
gilgamezh 724568e08f build: pin qbittorrent to 5.1.4 (linuxserver has no libtorrent2 5.2.0) 2026-05-07 09:49:11 +02:00
gilgamezh 770125e7c8 build: update qbittorrent to 5.2.0 and unstick prowlarr
prowlarr was pinned to a stale digest (v2.0.5.5160) via
.argocd-source-prowlarr.yaml; remove the file so the live app's
helm.parameters (which already has the current :latest digest =
v2.3.5.5327) takes effect.

qbittorrent: bump 5.1.0 -> 5.2.0.
2026-05-07 09:46:18 +02:00
argocd-image-updater 3239a1e729 build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10611-1e34174b1-ls303' to '1.43.1.10611-1e34174b1-ls304'
2026-05-04 11:24:15 +00:00
argocd-image-updater d476af6cfd build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10611-1e34174b1-ls302' to '1.43.1.10611-1e34174b1-ls303'
2026-04-27 11:20:51 +00:00
argocd-image-updater 930ede9b74 build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10611-1e34174b1-ls301' to '1.43.1.10611-1e34174b1-ls302'
2026-04-20 11:05:41 +00:00
argocd-image-updater a31e50f02f build: automatic update of plex
updates image linuxserver/plex tag '1.43.1.10576-06378bdcd-ls300' to '1.43.1.10611-1e34174b1-ls301'
2026-04-10 14:05:19 +00:00
argocd-image-updater aa89a5f238 build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls299' to '1.43.1.10576-06378bdcd-ls300'
2026-04-08 15:58:38 +00:00
argocd-image-updater 41e272c9f2 build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls298' to '1.43.0.10492-121068a07-ls299'
2026-04-06 10:26:04 +00:00
argocd-image-updater 4fe6ef579c build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls297' to '1.43.0.10492-121068a07-ls298'
2026-03-30 10:35:06 +00:00
argocd-image-updater 8ea4086a37 build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls296' to '1.43.0.10492-121068a07-ls297'
2026-03-21 14:45:07 +00:00
argocd-image-updater 3827ad656a build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls295' to '1.43.0.10492-121068a07-ls296'
2026-03-15 21:59:52 +00:00
argocd-image-updater 9ab1659939 build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls294' to '1.43.0.10492-121068a07-ls295'
2026-03-02 10:19:10 +00:00
argocd-image-updater 4a66fdfabf build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10492-121068a07-ls293' to '1.43.0.10492-121068a07-ls294'
2026-02-23 10:12:30 +00:00
argocd-image-updater c98f1b93b5 build: automatic update of plex
updates image linuxserver/plex tag '1.42.2.10156-f737b826c-ls292' to '1.43.0.10492-121068a07-ls293'
2026-02-11 16:35:08 +00:00
argocd-image-updater 377fa8ec4a build: automatic update of plex
updates image linuxserver/plex tag '1.42.2.10156-f737b826c-ls291' to '1.42.2.10156-f737b826c-ls292'
2026-02-09 10:20:10 +00:00
argocd-image-updater d009a61c0e build: automatic update of plex
updates image linuxserver/plex tag '1.43.0.10467-2b1ba6e69-ls290' to '1.42.2.10156-f737b826c-ls291'
2026-01-28 21:21:09 +00:00
argocd-image-updater eeb80c2662 build: automatic update of plex
updates image linuxserver/plex tag '1.42.2.10156-f737b826c-ls289' to '1.43.0.10467-2b1ba6e69-ls290'
2026-01-27 19:54:58 +00:00
argocd-image-updater 24a2463f20 build: automatic update of plex
updates image linuxserver/plex tag '1.42.2.10156-f737b826c-ls288' to '1.42.2.10156-f737b826c-ls289'
2026-01-19 09:53:36 +00:00
gilgamezh 5e80bac19d fix torrent path 2026-01-18 14:35:48 +01:00
gilgamezh d39c8ff550 update torrent port 2026-01-18 14:26:47 +01:00
gilgamezh d79e75fa88 update mount path 2026-01-18 14:22:55 +01:00
gilgamezh c773b6da26 update mount path 2026-01-18 14:13:34 +01:00
gilgamezh ba2c36b6f2 media: set cross-seed config dir env 2026-01-18 13:49:28 +01:00
gilgamezh 988a44b609 media: run cross-seed via explicit command 2026-01-18 13:45:00 +01:00
gilgamezh 1b904fe20d media: bind cross-seed daemon to pod IP 2026-01-18 13:38:14 +01:00
gilgamezh 9e4e0d7a9a media: drop unsupported cross-seed config flag 2026-01-18 13:35:29 +01:00
gilgamezh 78ba3041f1 media: run cross-seed with config args 2026-01-18 13:31:41 +01:00
gilgamezh 5988d0df38 media: run cross-seed daemon explicitly 2026-01-18 13:25:27 +01:00
gilgamezh 9a6d7670f4 media: fix cross-seed config for qbittorrent 2026-01-18 13:21:41 +01:00
gilgamezh 1becccb339 argocd: register qbittorrent and cross-seed applications 2026-01-17 13:29:20 +01:00
gilgamezh adff03ea7c media: add cross-seed with nzbgeek config 2026-01-17 13:29:13 +01:00
gilgamezh 7979c6c917 media: add qbittorrent with gluetun sidecar 2026-01-17 13:29:06 +01:00
gilgamezh 7bb64786ac align paths 2026-01-17 12:56:26 +01:00
gilgamezh caa7495a71 chore: add gitignore for AirVPN configs 2026-01-17 12:38:56 +01:00
gilgamezh c87694e5ce media: add lowercase proxy env for nzbget 2026-01-17 12:36:30 +01:00
gilgamezh 10eebfc9d0 media: select airvpn by country for wireguard 2026-01-17 12:30:28 +01:00
gilgamezh 7abedc9bce media: add wireguard preshared key support 2026-01-17 12:21:35 +01:00
gilgamezh 2ee850da6e media: wireguard values from airvpn config 2026-01-17 12:14:59 +01:00
gilgamezh 0a3f5dfc80 docs: add airvpn wireguard key + verification notes 2026-01-17 11:16:55 +01:00
gilgamezh 34f1e08f2c argocd: register gluetun and nzbget applications 2026-01-17 11:16:48 +01:00
gilgamezh bf8252970a media: add nzbget with pvc and proxy routing 2026-01-17 11:16:43 +01:00
gilgamezh 1bf2ea313f media: add gluetun (AirVPN wireguard) with proxy service 2026-01-17 11:16:38 +01:00
59 changed files with 2269 additions and 15 deletions
+22
View File
@@ -0,0 +1,22 @@
/AirVPN*
# OS files
.DS_Store
Thumbs.db
# Editor/IDE
*.swp
*.swo
*~
.idea/
.vscode/
# Logs and temp
*.log
*.bak
*.tmp
.tmp/
# Env files
.env
.env.*
+39
View File
@@ -0,0 +1,39 @@
# turingpi
Home k3s cluster on a Turing Pi (arm64 nodes), GitOps-managed by ArgoCD.
## Deploying changes
- Apps are ArgoCD `Application`s in `applications/*.yaml`, pointing at an **internal Gitea**
repo (`gitea-http.gitea.svc.cluster.local`), `targetRevision: HEAD`.
- The git remote is `gitea` (`git@192.168.222.26:admin/turingpi.git`); working branch is **`master`**.
- To deploy: **commit and push to `gitea/master`**. Apps have `syncPolicy.automated` with
`selfHeal: true`, so direct `kubectl patch`/`edit` is reverted — changes must go through git.
- Argo polls Gitea every ~3 min. Force a sync with:
`kubectl -n argocd annotate application <name> argocd.argoproj.io/refresh=hard --overwrite`
- Helm values: `helm-values/<app>_values.yaml`. Custom charts: `custom_helm_charts/<app>/`.
## Gotchas
### qbittorrent + gluetun (AirVPN) — DNS / restart loop
AirVPN blocks outbound **DNS-over-TLS (tcp/853)** to force its own resolver. Gluetun's default
`DOT=on` resolver (127.0.0.1) therefore never gets answers, **all DNS fails**, and the VPN
startup healthcheck (`lookup cloudflare.com`) times out — gluetun restarts the VPN every ~6s in a
permanent loop. The pod still shows `2/2 Running` with 0 restarts, so it looks healthy while
having no usable network.
The gluetun sidecar in `helm-values/qbittorrent_values.yaml` **must** keep:
```yaml
- name: DOT
value: "off"
- name: DNS_ADDRESS
value: "10.128.0.1" # AirVPN's pushed resolver, reached over the tunnel — no DNS leak
```
Diagnose: gluetun logs repeat `restarting VPN ... lookup ... i/o timeout`. Confirm with
`ping 8.8.8.8` (works) and `nslookup x 10.128.0.1` (works) but `curl 1.1.1.1:853` (times out).
Note: the `7e0a38d` "pin gluetun to v3.41.1" commit message falsely claimed v3.41.1 fixed this
DNS timeout. It did not — don't trust that claim.
+174
View File
@@ -0,0 +1,174 @@
# TODO: Migrate PostgreSQL to CloudNativePG
**Status:** planned, not started
**Target:** [CloudNativePG](https://cloudnative-pg.io/) (CNPG operator)
**Created:** 2026-05-31
## Why
Both Postgres instances run **Bitnami** images, which Bitnami froze in
Aug 2025 (free images moved to the `bitnamilegacy` archive and stopped
getting updates / security patches):
| Instance | Namespace | Version | Image | How deployed | Consumers |
|---|---|---|---|---|---|
| `pgsql` | `default` | PG **16.2** | `docker.io/bitnami/postgresql:16.2.0-debian-12-r8` (non-legacy, tag effectively gone from the registry) | standalone helm release `pgsql` (chart `postgresql-15.1.2`) | lidarr, radarr, sonarr, prowlarr, alhfmf |
| `gitea-postgresql` | `gitea` | PG **17.6** | `bitnamilegacy/postgresql:17.6.0-debian-12-r4` | **bundled subchart** inside the `gitea` helm release | gitea only |
CloudNativePG is the chosen replacement: actively maintained, operator-managed
HA/backups, first-class `pg_dump`-based import for migration, and not tied to
Bitnami.
> ⚠️ `pgsql` is on the **non-legacy** `bitnami/` path whose `16.2.0` tag is no
> longer pullable — if that pod is ever rescheduled to a node without the image
> cached, it will **fail to start**. Treat instance A as the higher priority.
## Databases to migrate
- **pgsql (PG16):** `alhfmf`, `lidarr_db`, `lidarr_db_log`, `radarr_db`,
`radarr_db_log`, `sonarr_db`, `sonarr_db_log`, `prowlarr_db`, `prowlarr_db_log`
(confirm full list at cutover with `\l`).
- **gitea (PG17):** `gitea` (owner role `gitea`).
---
## 0. Prerequisites
- [ ] Install the CNPG operator (pin a recent stable version):
```bash
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update cnpg
helm upgrade --install cnpg cnpg/cloudnative-pg \
-n cnpg-system --create-namespace --wait
kubectl get deploy -n cnpg-system # operator Running
```
- [ ] Decide on storage: reuse `nfs-client` StorageClass, **or** prefer local
storage for the DB PVCs (Postgres on NFS is workable but not ideal;
CNPG defaults to RWO). Pick a `storageClass` + size per cluster below.
- [ ] Decide CNPG topology: homelab can run `instances: 1` (no HA) to keep it
light on the Pi nodes; bump to 23 later if desired.
- [ ] Take a manual backup of both instances first (safety net):
```bash
kubectl exec -n default pgsql-postgresql-0 -- \
bash -c 'PGPASSWORD=$POSTGRES_PASSWORD pg_dumpall -U postgres' > pgsql-all.sql
kubectl exec -n gitea gitea-postgresql-0 -- \
bash -c 'PGPASSWORD=$POSTGRES_PASSWORD pg_dumpall -U postgres' > gitea-all.sql
```
---
## Instance A — `pgsql` (media *arr + alhfmf), PG16
CNPG can pull data straight from the old server at bootstrap via
`initdb.import` (it runs `pg_dump`/`pg_restore` for you). Use **monolith** type
to copy all listed databases + roles in one shot.
- [ ] Keep the old `pgsql` release **running** during migration (read source).
- [ ] Create a secret with the old server's superuser creds for the importer:
```bash
# password: kubectl get secret pgsql-postgresql -n default -o jsonpath='{.data.postgres-password}' | base64 -d
kubectl create secret generic pg16-source-superuser -n default \
--from-literal=username=postgres --from-literal=password='<OLD_PW>'
```
- [ ] Apply a CNPG `Cluster` with import bootstrap (sketch — tune names/size):
```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg16
namespace: default
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:16 # match major 16
storage:
size: 10Gi
storageClass: nfs-client # or local SC — see prereqs
bootstrap:
initdb:
import:
type: monolith
databases: ["alhfmf","lidarr_db","lidarr_db_log","radarr_db","radarr_db_log","sonarr_db","sonarr_db_log","prowlarr_db","prowlarr_db_log"]
roles: ["*"]
source:
externalCluster: old-pgsql
externalClusters:
- name: old-pgsql
connectionParameters:
host: pgsql-postgresql.default.svc.cluster.local
user: postgres
dbname: postgres
password:
name: pg16-source-superuser
key: password
```
- [ ] Wait for import to finish: `kubectl get cluster pg16 -n default -w`
(phase → `Cluster in healthy state`). Check logs of the bootstrap job.
- [ ] Verify row counts / `\dt` in a couple of DBs vs. the old server.
- [ ] **Cutover:** the new service is `pg16-rw.default.svc.cluster.local:5432`.
Update each consumer's DB host:
- [ ] lidarr — `custom_helm_charts/lidarr` / `helm-values/lidarr_values.yaml`
- [ ] radarr — `helm-values/radarr_values.yaml`
- [ ] sonarr — `helm-values/sonarr_values.yaml`
- [ ] prowlarr — `custom_helm_charts/prowlarr` / `helm-values/prowlarr_values.yml`
- [ ] alhfmf — `alhfmf` release (find its DB env/secret)
(the *arr apps store DB host/creds in their `config.xml`/Postgres env —
confirm where each one is configured before flipping.)
Commit + push so Argo redeploys; verify each app reconnects.
- [ ] Soak for a few days.
- [ ] Decommission old: `helm uninstall pgsql -n default`, delete its PVC
(`data-pgsql-postgresql-0`) and `resources/pgsql_persistent_volume.yml`,
remove `non_argo_values/pgsql_values.yaml`, drop `pg16-source-superuser`.
---
## Instance B — `gitea` DB, PG17
Trickier because Postgres is a **subchart of the gitea release**, so it must be
externalized from the gitea chart.
- [ ] Stand up a CNPG `Cluster` `pg17` in the `gitea` ns, PG major **17**
(`imageName: ghcr.io/cloudnative-pg/postgresql:17`), same import approach:
`databases: ["gitea"]`, `roles: ["*"]`, source =
`gitea-postgresql.gitea.svc.cluster.local`, superuser secret from
`kubectl get secret gitea-postgresql -n gitea -o jsonpath='{.data.postgres-password}'`.
- [ ] **Quiesce gitea during final cutover** (scale gitea deploy to 0) so the
source DB is static, then run/refresh the import (or do a final `pg_dump`
of `gitea` and restore into `pg17`) to avoid losing writes.
- [ ] Edit `non_argo_values/gitea_values.yaml`:
- set `postgresql.enabled: false` (drop the bundled subchart)
- point `gitea.config.database` at the CNPG service:
```yaml
gitea:
config:
database:
DB_TYPE: postgres
HOST: pg17-rw.gitea.svc.cluster.local:5432
NAME: gitea
USER: gitea
PASSWD: <from CNPG-managed secret pg17-app>
```
(CNPG creates an app user/secret; either reuse the migrated `gitea` role or
wire gitea to the `pg17-app` secret.)
- [ ] `helm upgrade gitea ...` (Recreate strategy already set). Bring gitea back
up; verify login + that the GitOps repo (`admin/turingpi.git`) is intact —
**Argo depends on this**, so validate before moving on.
- [ ] Decommission: once `postgresql.enabled: false` is live, the old
`gitea-postgresql` StatefulSet is removed by the chart; delete leftover
PVC `data-gitea-postgresql-0` after confirming the new DB is good.
---
## Rollback
- Old instances stay intact until the explicit decommission steps — to roll
back, just point the consumer/gitea config back at the original
`*-postgresql` service. Keep the `pg_dumpall` files until both soaks pass.
## Open questions / decisions
- [ ] NFS vs local storage for DB PVCs (perf + RWO behavior on node loss).
- [ ] HA (`instances: 1` vs `3`) given Pi resource limits.
- [ ] Backups: configure CNPG scheduled backups (Barman to NFS or object store)
as part of this — replaces whatever (if any) backup the Bitnami charts did.
- [ ] Confirm exact per-app DB connection config for the 5 consumers of `pgsql`
before cutover (where each *arr stores its Postgres host/creds).
+26
View File
@@ -0,0 +1,26 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cross-seed
namespace: argocd
annotations:
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: custom_helm_charts/cross-seed
helm:
releaseName: cross-seed
valueFiles:
- ../../helm-values/cross-seed_values.yaml
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+26
View File
@@ -0,0 +1,26 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gluetun
namespace: argocd
annotations:
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: custom_helm_charts/gluetun
helm:
releaseName: gluetun
valueFiles:
- ../../helm-values/gluetun_values.yaml
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+23
View File
@@ -0,0 +1,23 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: maintainerr
namespace: argocd
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: manifests/maintainerr
directory:
recurse: false
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+26
View File
@@ -0,0 +1,26 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nzbget
namespace: argocd
annotations:
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: custom_helm_charts/nzbget
helm:
releaseName: nzbget
valueFiles:
- ../../helm-values/nzbget_values.yaml
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+26
View File
@@ -0,0 +1,26 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: qbittorrent
namespace: argocd
annotations:
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: custom_helm_charts/qbittorrent
helm:
releaseName: qbittorrent
valueFiles:
- ../../helm-values/qbittorrent_values.yaml
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+37
View File
@@ -0,0 +1,37 @@
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
logs:
access:
enabled: true
format: common
# opcional: para logs de Traefik (no sólo access logs)
log:
level: INFO
format: json
# esto ya estaba, pero si querés mantenerlo:
deployment:
podAnnotations:
prometheus.io/port: "8082"
prometheus.io/scrape: "true"
providers:
kubernetesIngress:
publishedService:
enabled: true
priorityClassName: "system-cluster-critical"
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
service:
ipFamilyPolicy: "PreferDualStack"
+21
View File
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: cross-seed - Torrent seeding assistant
name: cross-seed
version: 0.1.0
+17
View File
@@ -0,0 +1,17 @@
# cross-seed
Service endpoint:
- cross-seed UI: cross-seed.default.svc.cluster.local:2468
Config notes:
- Config file is generated from values at `custom_helm_charts/cross-seed/templates/configmap.yaml`.
- Update `helm-values/cross-seed_values.yaml` for qbittorrent URL, torznab list, and paths.
Secrets:
- Secret name: `cross-seed-secrets`
- Required keys:
- `QBITTORRENT_USERNAME`
- `QBITTORRENT_PASSWORD`
- `NZBGEEK_API_KEY`
After setting qBittorrent credentials and NZBGeek API key, restart the cross-seed deployment.
@@ -0,0 +1,19 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "cross-seed.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 svc -w {{ template "cross-seed.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "cross-seed.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "cross-seed.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:2468 to use your application"
kubectl port-forward $POD_NAME 2468:{{ .Values.service.port }}
{{- end }}
@@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "cross-seed.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "cross-seed.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "cross-seed.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,30 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cross-seed-config
labels:
app: {{ template "cross-seed.name" . }}
chart: {{ template "cross-seed.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
data:
config.js: |
module.exports = {
torrentClients: [
{
type: "qbittorrent",
url: "{{ .Values.config.qbittorrentUrl }}",
username: process.env.QBITTORRENT_USERNAME,
password: process.env.QBITTORRENT_PASSWORD
}
],
host: "{{ .Values.config.host }}",
port: {{ .Values.config.port }},
torrentDir: "{{ .Values.config.torrentDir }}",
dataDirs: {{ toJson .Values.config.dataDirs }},
outputDir: {{ toJson .Values.config.outputDir }},
action: "{{ .Values.config.action }}",
torznab: [
`{{ .Values.config.torznabBaseUrl }}?t=search&apikey=${process.env.NZBGEEK_API_KEY}`
]
};
@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "cross-seed.fullname" . }}
labels:
app: {{ template "cross-seed.name" . }}
chart: {{ template "cross-seed.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ template "cross-seed.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "cross-seed.name" . }}
release: {{ .Release.Name }}
spec:
volumes:
{{ toYaml .Values.volumes | indent 6 }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.command }}
command:
{{ toYaml . | indent 12 }}
{{- end }}
{{- with .Values.args }}
args:
{{ toYaml . | indent 12 }}
{{- end }}
env:
{{ toYaml .Values.env | indent 12 }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{ toYaml .Values.livenessProbe | indent 12 }}
readinessProbe:
{{ toYaml .Values.readinessProbe | indent 12 }}
volumeMounts:
{{ toYaml .Values.volumeMounts | indent 12 }}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
@@ -0,0 +1,38 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "cross-seed.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app: {{ template "cross-seed.name" . }}
chart: {{ template "cross-seed.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}
@@ -0,0 +1,16 @@
{{- if .Values.secret.create -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.secret.name }}
labels:
app: {{ template "cross-seed.name" . }}
chart: {{ template "cross-seed.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
type: Opaque
stringData:
QBITTORRENT_USERNAME: {{ .Values.secret.qbittorrentUsername | quote }}
QBITTORRENT_PASSWORD: {{ .Values.secret.qbittorrentPassword | quote }}
NZBGEEK_API_KEY: {{ .Values.secret.nzbgeekApiKey | quote }}
{{- end }}
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "cross-seed.fullname" . }}
labels:
app: {{ template "cross-seed.name" . }}
chart: {{ template "cross-seed.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP
name: http
selector:
app: {{ template "cross-seed.name" . }}
release: {{ .Release.Name }}
+121
View File
@@ -0,0 +1,121 @@
replicaCount: 1
image:
repository: ghcr.io/cross-seed/cross-seed
tag: "6"
pullPolicy: IfNotPresent
env:
- name: TZ
value: "Europe/Amsterdam"
- name: CONFIG_DIR
value: "/config"
- name: DOCKER_ENV
value: "true"
- name: QBITTORRENT_USERNAME
valueFrom:
secretKeyRef:
name: cross-seed-secrets
key: QBITTORRENT_USERNAME
- name: QBITTORRENT_PASSWORD
valueFrom:
secretKeyRef:
name: cross-seed-secrets
key: QBITTORRENT_PASSWORD
- name: NZBGEEK_API_KEY
valueFrom:
secretKeyRef:
name: cross-seed-secrets
key: NZBGEEK_API_KEY
command:
- "cross-seed"
args:
- "daemon"
config:
qbittorrentUrl: "http://qbittorrent.default.svc.cluster.local:8080"
host: "0.0.0.0"
port: 2468
torrentDir: "/qbittorrent-config/qBittorrent/BT_backup"
dataDirs:
- "/data/torrents"
outputDir: null
action: "inject"
torznabBaseUrl: "https://api.nzbgeek.info/api"
secret:
create: true
name: cross-seed-secrets
qbittorrentUsername: "REPLACE_ME"
qbittorrentPassword: "REPLACE_ME"
nzbgeekApiKey: "REPLACE_ME"
service:
type: ClusterIP
port: 2468
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
path: /
hosts:
- cross-seed.example.org
tls:
- secretName: cross-seed-example-org
hosts:
- cross-seed.example.org
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
- name: config
configMap:
name: cross-seed-config
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/cross-seed"
- name: config
mountPath: "/config/config.js"
subPath: "config.js"
- name: plex-data
mountPath: "/qbittorrent-config"
subPath: "configs/qbittorrent"
- name: plex-data
mountPath: "/data/torrents"
subPath: "torrent"
livenessProbe:
tcpSocket:
port: 2468
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 2468
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
nodeSelector: {}
tolerations: []
affinity: {}
+21
View File
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
appVersion: "1.0"
description: Gluetun - VPN client with HTTP proxy
name: gluetun
version: 0.1.0
icon: https://raw.githubusercontent.com/qdm12/gluetun/master/.github/logo.png
+22
View File
@@ -0,0 +1,22 @@
# Usenet stack (Gluetun + NZBGet)
Service endpoints:
- NZBGet UI: nzbget.default.svc.cluster.local:6789
- Gluetun HTTP proxy: gluetun.default.svc.cluster.local:8888
AirVPN WireGuard values:
- Update `helm-values/gluetun_values.yaml`:
- `env.WIREGUARD_ADDRESSES` -> WireGuard tunnel address(es) (IPv4 /32 and optional IPv6)
- `env.SERVER_COUNTRIES` -> recommended AirVPN server selection (e.g. Netherlands)
- If your cluster does not support IPv6, remove the IPv6 address from `env.WIREGUARD_ADDRESSES`.
- Create a Secret named `gluetun-wireguard` with key `WIREGUARD_PRIVATE_KEY` from your AirVPN WireGuard config (do not commit the key).
- Add `WIREGUARD_PRESHARED_KEY` from the same AirVPN WireGuard config.
- `helm-values/gluetun_values.yaml` sets `secret.create: false` so the chart does not create a placeholder secret.
Validation:
- ArgoCD health: `argocd app get gluetun` and `argocd app get nzbget`
- WireGuard up: `kubectl -n default logs deploy/gluetun | rg -i "wireguard|tunnel"`
- VPN egress from NZBGet: `kubectl -n default exec deploy/nzbget -- curl -s ifconfig.me`
- NZBGet UI: `kubectl -n default port-forward deploy/nzbget 6789:6789`
Note: NZBGet is configured to use the HTTP proxy via `HTTP_PROXY`/`HTTPS_PROXY`. If your NNTP traffic does not honor proxy settings, consider using a proxy-aware downloader or running the downloader in the same pod as Gluetun.
@@ -0,0 +1,19 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "gluetun.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 svc -w {{ template "gluetun.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "gluetun.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "gluetun.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8888 to use your application"
kubectl port-forward $POD_NAME 8888:{{ .Values.service.port }}
{{- end }}
@@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "gluetun.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "gluetun.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "gluetun.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "gluetun.fullname" . }}
labels:
app: {{ template "gluetun.name" . }}
chart: {{ template "gluetun.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ template "gluetun.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "gluetun.name" . }}
release: {{ .Release.Name }}
spec:
volumes:
{{ toYaml .Values.volumes | indent 6 }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
{{ toYaml .Values.securityContext | indent 12 }}
env:
{{ toYaml .Values.env | indent 12 }}
ports:
- name: http-proxy
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{ toYaml .Values.livenessProbe | indent 12 }}
readinessProbe:
{{ toYaml .Values.readinessProbe | indent 12 }}
volumeMounts:
{{ toYaml .Values.volumeMounts | indent 12 }}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
@@ -0,0 +1,38 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "gluetun.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app: {{ template "gluetun.name" . }}
chart: {{ template "gluetun.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}
@@ -0,0 +1,15 @@
{{- if .Values.secret.create -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.secret.name }}
labels:
app: {{ template "gluetun.name" . }}
chart: {{ template "gluetun.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
type: Opaque
stringData:
WIREGUARD_PRIVATE_KEY: {{ .Values.secret.privateKey | quote }}
WIREGUARD_PRESHARED_KEY: {{ .Values.secret.presharedKey | quote }}
{{- end }}
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "gluetun.fullname" . }}
labels:
app: {{ template "gluetun.name" . }}
chart: {{ template "gluetun.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP
name: http-proxy
selector:
app: {{ template "gluetun.name" . }}
release: {{ .Release.Name }}
+102
View File
@@ -0,0 +1,102 @@
replicaCount: 1
image:
repository: qmcgaw/gluetun
tag: latest
pullPolicy: IfNotPresent
env:
- name: VPN_SERVICE_PROVIDER
value: "airvpn"
- name: VPN_TYPE
value: "wireguard"
- name: WIREGUARD_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRIVATE_KEY
- name: WIREGUARD_PRESHARED_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRESHARED_KEY
- name: WIREGUARD_ADDRESSES
value: "REPLACE_ME"
- name: SERVER_COUNTRIES
value: "REPLACE_ME"
- name: HTTPPROXY
value: "on"
- name: HTTPPROXY_LOG
value: "off"
- name: FIREWALL_INPUT_PORTS
value: "8888"
- name: TZ
value: "Europe/Amsterdam"
secret:
create: true
name: gluetun-wireguard
privateKey: "REPLACE_ME"
presharedKey: "REPLACE_ME"
service:
type: ClusterIP
port: 8888
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
path: /
hosts:
- gluetun.example.org
tls:
- secretName: gluetun-example-org
hosts:
- gluetun.example.org
volumes:
- name: dev-tun
hostPath:
path: /dev/net/tun
volumeMounts:
- name: dev-tun
mountPath: "/dev/net/tun"
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
livenessProbe:
tcpSocket:
port: 8888
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8888
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
nodeSelector: {}
tolerations: []
affinity: {}
+21
View File
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: NZBGet - Usenet downloader
name: nzbget
version: 0.1.0
@@ -0,0 +1,19 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "nzbget.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 svc -w {{ template "nzbget.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "nzbget.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "nzbget.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:6789 to use your application"
kubectl port-forward $POD_NAME 6789:{{ .Values.service.port }}
{{- end }}
@@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "nzbget.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "nzbget.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nzbget.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,53 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "nzbget.fullname" . }}
labels:
app: {{ template "nzbget.name" . }}
chart: {{ template "nzbget.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ template "nzbget.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "nzbget.name" . }}
release: {{ .Release.Name }}
spec:
volumes:
{{ toYaml .Values.volumes | indent 6 }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
{{ toYaml .Values.env | indent 12 }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{ toYaml .Values.livenessProbe | indent 12 }}
readinessProbe:
{{ toYaml .Values.readinessProbe | indent 12 }}
volumeMounts:
{{ toYaml .Values.volumeMounts | indent 12 }}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
@@ -0,0 +1,38 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "nzbget.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app: {{ template "nzbget.name" . }}
chart: {{ template "nzbget.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "nzbget.fullname" . }}
labels:
app: {{ template "nzbget.name" . }}
chart: {{ template "nzbget.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP
name: http
selector:
app: {{ template "nzbget.name" . }}
release: {{ .Release.Name }}
+86
View File
@@ -0,0 +1,86 @@
replicaCount: 1
image:
repository: lscr.io/linuxserver/nzbget
tag: latest
pullPolicy: IfNotPresent
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Europe/Amsterdam"
- name: HTTP_PROXY
value: "http://gluetun.default.svc.cluster.local:8888"
- name: http_proxy
value: "http://gluetun.default.svc.cluster.local:8888"
- name: HTTPS_PROXY
value: "http://gluetun.default.svc.cluster.local:8888"
- name: https_proxy
value: "http://gluetun.default.svc.cluster.local:8888"
- name: NO_PROXY
value: "localhost,127.0.0.1,.svc,.cluster.local"
service:
type: ClusterIP
port: 6789
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
path: /
hosts:
- nzbget.example.org
tls:
- secretName: nzbget-example-org
hosts:
- nzbget.example.org
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/nzbget"
- name: plex-data
mountPath: "/downloads"
subPath: "usenet"
livenessProbe:
tcpSocket:
port: 6789
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 6789
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: "500Mi"
cpu: "500m"
ephemeral-storage: "50Mi"
limits:
memory: "2Gi"
cpu: "2"
ephemeral-storage: "1Gi"
nodeSelector: {}
tolerations: []
affinity: {}
@@ -1,8 +0,0 @@
helm:
parameters:
- name: image.repository
value: lscr.io/linuxserver/prowlarr
forcestring: true
- name: image.tag
value: latest@sha256:4f2a6d597845b2f3e19284b1d982b3e0b4bd7c22472c2979c956aa198b83f472
forcestring: true
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
@@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: qBittorrent - BitTorrent client (VPN via Gluetun sidecar)
name: qbittorrent
version: 0.1.0
+11
View File
@@ -0,0 +1,11 @@
# qBittorrent
Service endpoint:
- qBittorrent UI: qbittorrent.default.svc.cluster.local:8080
VPN routing:
- qBittorrent runs in the same pod as a Gluetun sidecar and uses the `gluetun-wireguard` secret.
- Update `helm-values/qbittorrent_values.yaml` if you change AirVPN WireGuard addresses or server selection.
Port forwarding:
- Set `qbittorrent.env.TORRENTING_PORT` and `gluetun.env.FIREWALL_VPN_INPUT_PORTS` to your AirVPN forwarded port if you use VPN port forwarding.
@@ -0,0 +1,19 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "qbittorrent.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 svc -w {{ template "qbittorrent.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "qbittorrent.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "qbittorrent.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:{{ .Values.service.port }}
{{- end }}
@@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "qbittorrent.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "qbittorrent.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "qbittorrent.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,76 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "qbittorrent.fullname" . }}
labels:
app: {{ template "qbittorrent.name" . }}
chart: {{ template "qbittorrent.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
strategy:
type: Recreate
selector:
matchLabels:
app: {{ template "qbittorrent.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "qbittorrent.name" . }}
release: {{ .Release.Name }}
spec:
volumes:
{{ toYaml .Values.volumes | indent 6 }}
containers:
- name: gluetun
image: "{{ .Values.gluetun.image.repository }}:{{ .Values.gluetun.image.tag }}"
imagePullPolicy: {{ .Values.gluetun.image.pullPolicy }}
securityContext:
{{ toYaml .Values.gluetun.securityContext | indent 12 }}
env:
{{ toYaml .Values.gluetun.env | indent 12 }}
livenessProbe:
{{ toYaml .Values.gluetun.livenessProbe | indent 12 }}
readinessProbe:
{{ toYaml .Values.gluetun.readinessProbe | indent 12 }}
volumeMounts:
{{ toYaml .Values.gluetun.volumeMounts | indent 12 }}
resources:
{{ toYaml .Values.gluetun.resources | indent 12 }}
- name: qbittorrent
image: "{{ .Values.qbittorrent.image.repository }}:{{ .Values.qbittorrent.image.tag }}"
imagePullPolicy: {{ .Values.qbittorrent.image.pullPolicy }}
env:
{{ toYaml .Values.qbittorrent.env | indent 12 }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
- name: torrent
containerPort: {{ .Values.qbittorrent.torrentPort }}
protocol: TCP
- name: torrent-udp
containerPort: {{ .Values.qbittorrent.torrentPort }}
protocol: UDP
livenessProbe:
{{ toYaml .Values.qbittorrent.livenessProbe | indent 12 }}
readinessProbe:
{{ toYaml .Values.qbittorrent.readinessProbe | indent 12 }}
volumeMounts:
{{ toYaml .Values.qbittorrent.volumeMounts | indent 12 }}
resources:
{{ toYaml .Values.qbittorrent.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
@@ -0,0 +1,38 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "qbittorrent.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app: {{ template "qbittorrent.name" . }}
chart: {{ template "qbittorrent.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "qbittorrent.fullname" . }}
labels:
app: {{ template "qbittorrent.name" . }}
chart: {{ template "qbittorrent.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP
name: http
selector:
app: {{ template "qbittorrent.name" . }}
release: {{ .Release.Name }}
+142
View File
@@ -0,0 +1,142 @@
replicaCount: 1
qbittorrent:
image:
repository: lscr.io/linuxserver/qbittorrent
tag: "5.1.0"
pullPolicy: IfNotPresent
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Europe/Amsterdam"
- name: WEBUI_PORT
value: "8080"
- name: TORRENTING_PORT
value: "6881"
torrentPort: 6881
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: "500Mi"
cpu: "500m"
ephemeral-storage: "50Mi"
limits:
memory: "2Gi"
cpu: "2"
ephemeral-storage: "1Gi"
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/qbittorrent"
- name: plex-data
mountPath: "/data/torrents"
subPath: "torrent"
gluetun:
image:
repository: qmcgaw/gluetun
tag: latest
pullPolicy: IfNotPresent
env:
- name: VPN_SERVICE_PROVIDER
value: "airvpn"
- name: VPN_TYPE
value: "wireguard"
- name: WIREGUARD_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRIVATE_KEY
- name: WIREGUARD_PRESHARED_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRESHARED_KEY
- name: WIREGUARD_ADDRESSES
value: "10.160.17.207/32,fd7d:76ee:e68f:a993:61d7:a5fe:f834:90e1/128"
- name: SERVER_COUNTRIES
value: "Netherlands"
- name: FIREWALL_INPUT_PORTS
value: "8080"
- name: FIREWALL_VPN_INPUT_PORTS
value: "6881"
- name: TZ
value: "Europe/Amsterdam"
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
livenessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
volumeMounts:
- name: dev-tun
mountPath: "/dev/net/tun"
service:
type: ClusterIP
port: 8080
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
path: /
hosts:
- qbittorrent.example.org
tls:
- secretName: qbittorrent-example-org
hosts:
- qbittorrent.example.org
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
- name: dev-tun
hostPath:
path: /dev/net/tun
nodeSelector: {}
tolerations: []
affinity: {}
+108
View File
@@ -0,0 +1,108 @@
replicaCount: 1
image:
repository: ghcr.io/cross-seed/cross-seed
tag: "6"
pullPolicy: Always
env:
- name: TZ
value: "Europe/Amsterdam"
- name: CONFIG_DIR
value: "/config"
- name: DOCKER_ENV
value: "true"
- name: QBITTORRENT_USERNAME
valueFrom:
secretKeyRef:
name: cross-seed-secrets
key: QBITTORRENT_USERNAME
- name: QBITTORRENT_PASSWORD
valueFrom:
secretKeyRef:
name: cross-seed-secrets
key: QBITTORRENT_PASSWORD
- name: NZBGEEK_API_KEY
valueFrom:
secretKeyRef:
name: cross-seed-secrets
key: NZBGEEK_API_KEY
command:
- "cross-seed"
args:
- "daemon"
config:
qbittorrentUrl: "http://qbittorrent.default.svc.cluster.local:8080"
host: "0.0.0.0"
port: 2468
torrentDir: "/qbittorrent-config/qBittorrent/BT_backup"
dataDirs:
- "/data/torrents"
outputDir: null
action: "inject"
torznabBaseUrl: "https://api.nzbgeek.info/api"
secret:
create: true
name: cross-seed-secrets
qbittorrentUsername: "REPLACE_ME"
qbittorrentPassword: "REPLACE_ME"
nzbgeekApiKey: "REPLACE_ME"
service:
type: ClusterIP
port: 2468
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
- name: config
configMap:
name: cross-seed-config
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/cross-seed"
- name: config
mountPath: "/config/config.js"
subPath: "config.js"
- name: plex-data
mountPath: "/qbittorrent-config"
subPath: "configs/qbittorrent"
- name: plex-data
mountPath: "/data/torrents"
subPath: "torrent"
livenessProbe:
tcpSocket:
port: 2468
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 2468
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
nodeSelector: {}
tolerations: []
affinity: {}
+88
View File
@@ -0,0 +1,88 @@
replicaCount: 1
image:
repository: qmcgaw/gluetun
tag: "latest"
pullPolicy: Always
env:
- name: VPN_SERVICE_PROVIDER
value: "airvpn"
- name: VPN_TYPE
value: "wireguard"
- name: WIREGUARD_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRIVATE_KEY
- name: WIREGUARD_PRESHARED_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRESHARED_KEY
- name: WIREGUARD_ADDRESSES
value: "10.160.17.207/32,fd7d:76ee:e68f:a993:61d7:a5fe:f834:90e1/128"
- name: SERVER_COUNTRIES
value: "Netherlands"
- name: HTTPPROXY
value: "on"
- name: HTTPPROXY_LOG
value: "off"
- name: FIREWALL_INPUT_PORTS
value: "8888"
- name: TZ
value: "Europe/Amsterdam"
secret:
create: false
name: gluetun-wireguard
privateKey: "REPLACE_ME"
service:
type: ClusterIP
port: 8888
volumes:
- name: dev-tun
hostPath:
path: /dev/net/tun
volumeMounts:
- name: dev-tun
mountPath: "/dev/net/tun"
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
livenessProbe:
tcpSocket:
port: 8888
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8888
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
nodeSelector: {}
tolerations: []
affinity: {}
+72
View File
@@ -0,0 +1,72 @@
replicaCount: 1
image:
repository: lscr.io/linuxserver/nzbget
tag: "latest"
pullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Europe/Amsterdam"
- name: HTTP_PROXY
value: "http://gluetun.default.svc.cluster.local:8888"
- name: http_proxy
value: "http://gluetun.default.svc.cluster.local:8888"
- name: HTTPS_PROXY
value: "http://gluetun.default.svc.cluster.local:8888"
- name: https_proxy
value: "http://gluetun.default.svc.cluster.local:8888"
- name: NO_PROXY
value: "localhost,127.0.0.1,.svc,.cluster.local"
service:
type: ClusterIP
port: 6789
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: "plex-data"
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/nzbget"
- name: plex-data
mountPath: "/nfs"
livenessProbe:
tcpSocket:
port: 6789
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 6789
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: "500Mi"
cpu: "500m"
ephemeral-storage: "50Mi"
limits:
memory: "2Gi"
cpu: "2"
ephemeral-storage: "1Gi"
nodeSelector: {}
tolerations: []
affinity: {}
+1 -1
View File
@@ -1,7 +1,7 @@
claimToken: "claim-E_NxQDtUMMVsLCBFvybK"
image:
repository: linuxserver/plex
tag: "1.42.2.10156-f737b826c-ls288"
tag: "1.43.2.10687-563d026ea-ls307"
pullPolicy: Always
kubePlex:
enabled: false # kubePlex (transcoder job) is disabled because not available on ARM. The transcoding will be performed by the main Plex instance instead of a separate Job.
+140
View File
@@ -0,0 +1,140 @@
---
replicaCount: 1
qbittorrent:
image:
repository: lscr.io/linuxserver/qbittorrent
tag: "5.2.1_v2.0.12-ls459"
pullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Europe/Amsterdam"
- name: WEBUI_PORT
value: "8080"
- name: TORRENTING_PORT
value: "54408"
torrentPort: 54408
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: "500Mi"
cpu: "500m"
ephemeral-storage: "50Mi"
limits:
memory: "2Gi"
cpu: "2"
ephemeral-storage: "1Gi"
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/qbittorrent"
- name: plex-data
mountPath: "/nfs/incomplete_torrents"
subPath: "incomplete_torrents"
- name: plex-data
mountPath: "/nfs/torrent"
subPath: "torrent"
gluetun:
image:
repository: qmcgaw/gluetun
tag: v3.41.1
pullPolicy: IfNotPresent
env:
- name: VPN_SERVICE_PROVIDER
value: "airvpn"
- name: VPN_TYPE
value: "wireguard"
- name: WIREGUARD_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRIVATE_KEY
- name: WIREGUARD_PRESHARED_KEY
valueFrom:
secretKeyRef:
name: gluetun-wireguard
key: WIREGUARD_PRESHARED_KEY
- name: WIREGUARD_ADDRESSES
value: "10.160.17.207/32,fd7d:76ee:e68f:a993:61d7:a5fe:f834:90e1/128"
- name: SERVER_COUNTRIES
value: "Netherlands"
# AirVPN blocks outbound DNS-over-TLS (tcp/853), so gluetun's default
# DoT resolver never gets answers and the startup healthcheck loops
# forever on "lookup cloudflare.com: i/o timeout". Use AirVPN's pushed
# plaintext resolver instead (reached over the tunnel, no DNS leak).
- name: DOT
value: "off"
- name: DNS_ADDRESS
value: "10.128.0.1"
- name: FIREWALL_INPUT_PORTS
value: "8080"
- name: FIREWALL_VPN_INPUT_PORTS
value: "54408"
- name: TZ
value: "Europe/Amsterdam"
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
livenessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
volumeMounts:
- name: dev-tun
mountPath: "/dev/net/tun"
service:
type: ClusterIP
port: 8080
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
- name: dev-tun
hostPath:
path: /dev/net/tun
nodeSelector:
kubernetes.io/arch: arm64
tolerations: []
affinity: {}
+63
View File
@@ -0,0 +1,63 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: maintainerr
namespace: default
labels:
app: maintainerr
spec:
replicas: 1
# SQLite on the shared NFS PVC: never run two writers at once.
strategy:
type: Recreate
selector:
matchLabels:
app: maintainerr
template:
metadata:
labels:
app: maintainerr
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: maintainerr
image: ghcr.io/maintainerr/maintainerr:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 6246
protocol: TCP
env:
- name: TZ
value: "Europe/Amsterdam"
volumeMounts:
- name: plex-data
mountPath: /opt/data
subPath: configs/maintainerr
readinessProbe:
httpGet:
path: /api/app/status
port: http
initialDelaySeconds: 20
periodSeconds: 15
livenessProbe:
httpGet:
path: /api/app/status
port: http
initialDelaySeconds: 60
periodSeconds: 30
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "1000m"
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
+17
View File
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Service
metadata:
name: maintainerr
namespace: default
labels:
app: maintainerr
spec:
type: ClusterIP
ports:
- port: 6246
targetPort: http
protocol: TCP
name: http
selector:
app: maintainerr
+8 -2
View File
@@ -4,6 +4,12 @@
# Single replica for homelab
replicaCount: 1
# Gitea data lives on a single RWO NFS PVC, so two pods can't run at once.
# The chart default (RollingUpdate maxSurge 100%/maxUnavailable 0) surges a
# second pod and deadlocks on upgrade -- use Recreate instead.
strategy:
type: Recreate
# Service configuration - LoadBalancer for direct access
service:
http:
@@ -21,8 +27,8 @@ ingress:
className: traefik
pathType: Prefix
annotations:
# Restrict to LAN access (matching your existing pattern)
traefik.ingress.kubernetes.io/whitelist.sourcerange: "192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
# Restrict to LAN access via Traefik v3 Middleware (resources/gitea-middleware.yaml)
traefik.ingress.kubernetes.io/router.middlewares: "gitea-lan-only@kubernetescrd"
cert-manager.io/cluster-issuer: "letsencrypt-production"
hosts:
- host: gitea.gilgamezh.me
+13
View File
@@ -0,0 +1,13 @@
# MetalLB configuration for TuringPi K3s cluster
#
# L2 (ARP) mode only: the cluster advertises LoadBalancer IPs via the default
# IPAddressPool + L2Advertisement (resources/metallb.yml). There are no BGP
# peers, so FRR is not needed. Disable both the legacy embedded FRR and the
# newer frr-k8s daemonset -- their images cause DiskPressure on the small Pi
# nodes and add no value without BGP.
speaker:
frr:
enabled: false
frrk8s:
enabled: false
+12
View File
@@ -0,0 +1,12 @@
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: lan-only
namespace: gitea
spec:
ipAllowList:
sourceRange:
- 192.168.0.0/16
- 10.0.0.0/8
- 172.16.0.0/12
+4 -4
View File
@@ -4,12 +4,12 @@ kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
kubernetes.io/ingress.class: traefik
labels:
app: kube-plex
name: kube-plex
namespace: default
spec:
ingressClassName: traefik
rules:
- host: tp2.gilgamezh.me
http:
@@ -31,12 +31,12 @@ kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
kubernetes.io/ingress.class: traefik
labels:
app: radarr
name: radarr
namespace: default
spec:
ingressClassName: traefik
rules:
- host: radarr.gilgamezh.me
http:
@@ -58,12 +58,12 @@ kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
kubernetes.io/ingress.class: traefik
labels:
app: sonarr
name: sonarr
namespace: default
spec:
ingressClassName: traefik
rules:
- host: sonarr.gilgamezh.me
http:
@@ -85,12 +85,12 @@ kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
kubernetes.io/ingress.class: traefik
labels:
app: lidarr
name: lidarr
namespace: default
spec:
ingressClassName: traefik
rules:
- host: lidarr.gilgamezh.me
http: