Compare commits
57 Commits
8dc529451c
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 79a28a674a | |||
| d6ee993a60 | |||
| 2c68a21d0b | |||
| 1b3f34a432 | |||
| 261aebfd10 | |||
| 9b24978342 | |||
| 1a91b72464 | |||
| ac637adaf4 | |||
| 6082e6fc14 | |||
| 7e0a38d65f | |||
| 3b480d6abf | |||
| 3ace05a695 | |||
| 290ce6a103 | |||
| e230129119 | |||
| 269ab53002 | |||
| 724568e08f | |||
| 770125e7c8 | |||
| 3239a1e729 | |||
| d476af6cfd | |||
| 930ede9b74 | |||
| a31e50f02f | |||
| aa89a5f238 | |||
| 41e272c9f2 | |||
| 4fe6ef579c | |||
| 8ea4086a37 | |||
| 3827ad656a | |||
| 9ab1659939 | |||
| 4a66fdfabf | |||
| c98f1b93b5 | |||
| 377fa8ec4a | |||
| d009a61c0e | |||
| eeb80c2662 | |||
| 24a2463f20 | |||
| 5e80bac19d | |||
| d39c8ff550 | |||
| d79e75fa88 | |||
| c773b6da26 | |||
| ba2c36b6f2 | |||
| 988a44b609 | |||
| 1b904fe20d | |||
| 9e4e0d7a9a | |||
| 78ba3041f1 | |||
| 5988d0df38 | |||
| 9a6d7670f4 | |||
| 1becccb339 | |||
| adff03ea7c | |||
| 7979c6c917 | |||
| 7bb64786ac | |||
| caa7495a71 | |||
| c87694e5ce | |||
| 10eebfc9d0 | |||
| 7abedc9bce | |||
| 2ee850da6e | |||
| 0a3f5dfc80 | |||
| 34f1e08f2c | |||
| bf8252970a | |||
| 1bf2ea313f |
+22
@@ -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.*
|
||||||
@@ -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.
|
||||||
@@ -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 2–3 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).
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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: cross-seed - Torrent seeding assistant
|
||||||
|
name: cross-seed
|
||||||
|
version: 0.1.0
|
||||||
@@ -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 }}
|
||||||
@@ -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: {}
|
||||||
@@ -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,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
|
||||||
@@ -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 }}
|
||||||
@@ -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: {}
|
||||||
@@ -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: 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 }}
|
||||||
@@ -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
|
||||||
@@ -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 }}
|
||||||
@@ -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: {}
|
||||||
@@ -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: {}
|
||||||
@@ -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: {}
|
||||||
@@ -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,7 +1,7 @@
|
|||||||
claimToken: "claim-E_NxQDtUMMVsLCBFvybK"
|
claimToken: "claim-E_NxQDtUMMVsLCBFvybK"
|
||||||
image:
|
image:
|
||||||
repository: linuxserver/plex
|
repository: linuxserver/plex
|
||||||
tag: "1.42.2.10156-f737b826c-ls288"
|
tag: "1.43.2.10687-563d026ea-ls307"
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
kubePlex:
|
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.
|
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.
|
||||||
|
|||||||
@@ -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: {}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -4,6 +4,12 @@
|
|||||||
# Single replica for homelab
|
# Single replica for homelab
|
||||||
replicaCount: 1
|
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 configuration - LoadBalancer for direct access
|
||||||
service:
|
service:
|
||||||
http:
|
http:
|
||||||
@@ -21,8 +27,8 @@ ingress:
|
|||||||
className: traefik
|
className: traefik
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
annotations:
|
annotations:
|
||||||
# Restrict to LAN access (matching your existing pattern)
|
# Restrict to LAN access via Traefik v3 Middleware (resources/gitea-middleware.yaml)
|
||||||
traefik.ingress.kubernetes.io/whitelist.sourcerange: "192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
|
traefik.ingress.kubernetes.io/router.middlewares: "gitea-lan-only@kubernetescrd"
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-production"
|
cert-manager.io/cluster-issuer: "letsencrypt-production"
|
||||||
hosts:
|
hosts:
|
||||||
- host: gitea.gilgamezh.me
|
- host: gitea.gilgamezh.me
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,12 +4,12 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-production
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
labels:
|
labels:
|
||||||
app: kube-plex
|
app: kube-plex
|
||||||
name: kube-plex
|
name: kube-plex
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- host: tp2.gilgamezh.me
|
- host: tp2.gilgamezh.me
|
||||||
http:
|
http:
|
||||||
@@ -31,12 +31,12 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-production
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
labels:
|
labels:
|
||||||
app: radarr
|
app: radarr
|
||||||
name: radarr
|
name: radarr
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- host: radarr.gilgamezh.me
|
- host: radarr.gilgamezh.me
|
||||||
http:
|
http:
|
||||||
@@ -58,12 +58,12 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-production
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
labels:
|
labels:
|
||||||
app: sonarr
|
app: sonarr
|
||||||
name: sonarr
|
name: sonarr
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- host: sonarr.gilgamezh.me
|
- host: sonarr.gilgamezh.me
|
||||||
http:
|
http:
|
||||||
@@ -85,12 +85,12 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-production
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
labels:
|
labels:
|
||||||
app: lidarr
|
app: lidarr
|
||||||
name: lidarr
|
name: lidarr
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- host: lidarr.gilgamezh.me
|
- host: lidarr.gilgamezh.me
|
||||||
http:
|
http:
|
||||||
|
|||||||
Reference in New Issue
Block a user