Compare commits

...

81 Commits

Author SHA1 Message Date
gilgamezh 98c1e7b63d feat(disk-usage-alert): Telegram alert when turing3 SSD >=90%
The SSD on turing3 (/mnt/ssd) is shared by Postgres, Plex's SQLite DB
and the media library. When it fills, Postgres crashes (cannot write
postmaster.pid) and Plex's library DB corrupts. Add a CronJob that
checks df via the plex-data mount every 15m and warns Telegram at >=90%
used (3h cooldown). Bot token/chatId live in the out-of-band
`telegram-disk-alert` Secret, not in git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:50:01 +02:00
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
gilgamezh 8dc529451c use master instead of HEAD 2026-01-11 18:37:16 +01:00
argocd-image-updater e26b8401ef build: automatic update of plex
updates image linuxserver/plex tag 'sha256:28f18c27b6822328df994154dbf7c0f511032d9f91bbd10881030b706afd8593' to '1.42.2.10156-f737b826c-ls288'
2026-01-11 17:04:12 +00:00
gilgamezh e474d0d882 fix values path 2026-01-11 17:55:58 +01:00
argocd-image-updater 585aff8319 build: automatic update of plex
updates image linuxserver/plex tag 'sha256:28f18c27b6822328df994154dbf7c0f511032d9f91bbd10881030b706afd8593' to '1.42.2.10156-f737b826c-ls288'
2026-01-11 16:55:17 +00:00
gilgamezh 3f5eeb737e fix values path 2026-01-11 17:51:35 +01:00
argocd-image-updater ee2ccd5c5e build: automatic update of plex
updates image linuxserver/plex tag 'sha256:28f18c27b6822328df994154dbf7c0f511032d9f91bbd10881030b706afd8593' to '1.42.2.10156-f737b826c-ls288'
2026-01-11 16:43:17 +00:00
gilgamezh 272b065ba7 update plex config 2026-01-11 16:50:09 +01:00
gilgamezh 825a73a5b9 update resources for lidar 2026-01-11 15:44:38 +01:00
gilgamezh 0becd2930d Update README structure 2026-01-11 13:15:00 +01:00
gilgamezh f3b7d23bb7 upgrade cert-manager and configure it to use dns instead of http 2026-01-11 12:22:18 +01:00
gilgamezh be0b6b6dd4 Move Lidarr Postgres config to config.xml 2026-01-10 18:53:54 +01:00
gilgamezh e89454c1ec Fix Lidarr Postgres DB env names 2026-01-10 18:49:12 +01:00
gilgamezh 956ddd6b73 Fix Lidarr Postgres env var names 2026-01-10 18:39:59 +01:00
gilgamezh deb2901c6f Add Lidarr ArgoCD app and custom chart 2026-01-10 18:31:58 +01:00
gilgamezh 35057c2851 image updater config 2026-01-10 18:31:26 +01:00
gilgamezh 3a467d9d5a set latest for everything 2026-01-10 17:53:36 +01:00
gilgamezh 0fcada7d14 Remove argocd-image-updater annotations 2026-01-10 17:29:53 +01:00
gilgamezh 1fdd7c1b25 Fix prowlarr Argo CD app path 2026-01-10 17:25:16 +01:00
gilgamezh 261a64de76 update argo 2026-01-10 17:23:12 +01:00
gilgamezh 32864aae67 update helm chat path 2026-01-10 17:22:46 +01:00
gilgamezh f9aba632ec more cleaning 2026-01-10 16:48:04 +01:00
gilgamezh 743c086b36 reorder and clean repo 2026-01-10 16:47:21 +01:00
gilgamezh 6460253941 remove unused files 2026-01-10 16:36:32 +01:00
141 changed files with 2769 additions and 1113 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.*
-1
View File
@@ -1 +0,0 @@
CLAUDE.md
+27 -368
View File
@@ -1,380 +1,39 @@
# CLAUDE.md
# turingpi
This file provides guidance to LLM agents like Claude, codex or cursor-cli when working with code in this repository.
Home k3s cluster on a Turing Pi (arm64 nodes), GitOps-managed by ArgoCD.
## Repository Overview
## Deploying changes
This repository contains Kubernetes configuration files for a K3s homelab cluster running on TuringPi hardware. It includes Helm charts, values files, and manifests for deploying various self-hosted applications.
- 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>/`.
## Cluster Architecture
## Gotchas
### Hardware Setup
- **turing1**: Control plane + worker (master node, IP: 192.168.222.237)
- **turing2**: Worker node (currently SchedulingDisabled)
- **turing3**: Worker node (also serves as NFS server at turing3.lan)
- **turing4**: Worker node
- **beelink**: Additional worker node
### qbittorrent + gluetun (AirVPN) — DNS / restart loop
### Core Infrastructure
- **K3s version**: v1.31.6+k3s1
- **Storage**: NFS-backed persistent volumes from turing3.lan:/mnt/ssd
- **Load Balancer**: MetalLB for bare metal LoadBalancer services
- **SSL**: cert-manager with Let's Encrypt (staging/production cluster issuers)
- **Ingress**: Traefik (K3s default) with LAN-only restrictions
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.
## Application Stack
### Media Services
- **Plex**: kube-plex (Kubernetes-native with dynamic transcoding pods)
- **Jellyfin**: Alternative media server
- **Sonarr/Radarr**: TV/Movie management (Bananaspliff charts)
- **Prowlarr**: Indexer management (custom chart)
- **Transmission**: BitTorrent client with OpenVPN
- **FlareSolverr**: Captcha solver service
### Other Applications
- **Actual Budget**: Personal finance (custom chart: my-actual-server/)
- **Home Assistant Voice LLMs**: AI voice integration (custom chart)
- **Ollama**: Local LLM inference
- **Prometheus**: Monitoring stack
- **PostgreSQL**: Database backend
## Common Helm Operations
### Repository Management
```bash
# Key repositories used
helm repo add metallb https://metallb.github.io/metallb
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
helm repo add jetstack https://charts.jetstack.io
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add bananaspliff https://bananaspliff.github.io/geek-charts
helm repo add k8s-at-home https://k8s-at-home.com/charts/
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
helm repo add ollama-helm https://otwld.github.io/ollama-helm/
helm repo update
```
### Application Deployment Pattern
```bash
# Standard deployment with values file
helm upgrade <release-name> <chart> -f <app>_values.yaml -i
# Examples from history:
helm upgrade actual my-actual-server -f actual_values.yaml -i
helm upgrade plex kube-plex/charts/kube-plex --values plex_values.yml
helm upgrade radarr bananaspliff/radarr -f radarr_values.yaml
helm upgrade sonarr bananaspliff/sonarr -f sonarr_values.yaml
helm upgrade prowlarr prowlarr -f prowlarr_values.yml
```
### Development Workflow
```bash
# Chart development
helm create <chart-name>
helm lint <chart-path>
helm template <chart> -f <values> | vim -
# Values inspection
helm show values <chart> > <app>_values.yaml
helm get values <release-name>
helm get manifest <release-name>
```
## File Structure Patterns
- `<app>_values.yaml` - Helm values overrides for each application
- Custom charts in subdirectories (my-actual-server/, home-assistant-voice-llms/, prowlarr/)
- `*_persistent_volume.yml` - PV definitions for applications requiring storage
- Infrastructure manifests: metallb.yml, ingress.yaml, cluster-issuer-*.yaml
## Storage Configuration
- **NFS Server**: turing3.lan serving /mnt/ssd
- **StorageClass**: nfs-client (via nfs-subdir-external-provisioner)
- **Access Mode**: ReadWriteMany for shared media access
- **PVC Pattern**: Applications create their own PVCs or reference pre-existing ones
## Network Setup
- **Pod Network**: Cluster subnet requires allowlisting in Plex for transcoding
- **Ingress**: LAN-only access enforced via limit_ingress_to_lan.yaml
- **Load Balancer**: MetalLB provides external IPs for services
- **DNS**: .lan domain for internal services
## Kube-Plex Specifics
The kube-plex/ directory contains a Go application that replaces the standard Plex transcoder:
- Creates Kubernetes pods for each transcode job
- Requires AMD64 nodes (configured via nodeSelector)
- Mounts shared NFS volumes for media access
- Environment variables: DATA_PVC, CONFIG_PVC, TRANSCODE_PVC, PMS_IMAGE, PMS_INTERNAL_ADDRESS
## ArgoCD Migration Guidelines
### Agent Workflow Defaults
When migrating or adding an ArgoCD Application, always:
- Create/update the Application manifest and any `helm-values/` files.
- Git add, commit, and push the changes to the tracked branch.
- Use the ArgoCD CLI to sync the app and wait for healthy status.
### Critical Configuration Preservation Rules
When migrating applications from direct Helm deployment to ArgoCD GitOps:
1. **NEVER change working configurations** - If an application was working with specific values files and service configurations, maintain those exact settings
2. **NO PATCHES OR WORKAROUNDS** - Do not create additional PVCs, port forwards, or other patches to "fix" configuration mismatches
3. **Root Cause Analysis Required** - Always identify why a configuration that worked before is now failing after ArgoCD migration
4. **Preserve Original Service Ports** - Applications should maintain their original service port configurations (e.g., Radarr:7878, Sonarr:8989)
5. **Respect Existing PVC Structure** - Use the existing PVC and storage configuration patterns, don't create compatibility layers
### Troubleshooting Process
1. Compare working Helm values with ArgoCD application configuration
2. Verify that Helm chart sources and versions match original deployments
3. Ensure service definitions in ArgoCD match original working services
4. Check that ingress configurations align with actual service ports
5. Fix the root configuration issue, don't patch around it
### Common Migration Pitfalls
- Creating unnecessary "myvolume" PVCs for chart compatibility - instead fix the chart values
- Changing service ports to match ingress instead of fixing ingress to match services
- Using generic chart defaults instead of preserving working custom configurations
## ArgoCD GitOps Implementation Lessons
### Successfully Migrated Applications
- **Plex**: Multi-source setup (kube-plex chart + turingpi values)
- **Radarr**: Bananaspliff chart with custom volume configuration
- **Sonarr**: Bananaspliff chart with custom volume configuration
### Critical ArgoCD Configuration Patterns
#### Multi-Source Application Structure
For applications requiring custom values from Git repository:
```yaml
spec:
project: default
sources:
- repoURL: https://chart-repository.com/charts
chart: app-name
targetRevision: "*"
ref: charts
helm:
releaseName: app-name
valueFiles:
- $values/helm-values/app_values.yaml
- repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
ref: values
```
**CRITICAL**: Never use both `source:` and `sources:` sections - this creates conflicts where `source:` overrides `sources:` configuration.
#### Image Auto-Update Configuration
For applications using "latest" tags with automatic updates:
```yaml
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: app=registry/image:latest
argocd-image-updater.argoproj.io/app.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: master
argocd-image-updater.argoproj.io/git-commit-user: argocd-image-updater
argocd-image-updater.argoproj.io/git-commit-email: argocd@turing.lan
```
### Chart-Specific Configuration Requirements
#### Bananaspliff Charts (Radarr/Sonarr)
These charts require specific volume configuration syntax:
```yaml
# CORRECT: Direct volumes/volumeMounts (working configuration)
volumes:
- name: "plex-data"
persistentVolumeClaim:
claimName: "plex-data"
volumeMounts:
- name: "plex-data"
mountPath: "/config"
subPath: "configs/app-name"
- name: "plex-data"
mountPath: "/nfs"
# INCORRECT: persistence syntax (doesn't work with these charts)
persistence:
config:
enabled: true
existingClaim: "plex-data"
subPath: "configs/app-name"
```
#### Service Port Configuration
Bananaspliff charts use simple service structure:
```yaml
service:
type: ClusterIP
port: 7878 # Direct port specification
```
### Ingress Controller Configuration
#### K3s Default Setup
- **Default Controller**: Traefik (not Nginx)
- **Ingress Class**: Use `kubernetes.io/ingress.class: traefik`
- **Common Mistake**: Using `nginx` class when Traefik is the active controller
#### Verification Commands
```bash
# Check active ingress controller
kubectl get pods -A | grep traefik
kubectl get pods -A | grep ingress
# Verify ingress class in configurations
kubectl get ingress -o yaml | grep "ingress.class"
```
### Pre-Migration Checklist
Before migrating any application to ArgoCD:
1. **Capture Current Configuration**:
```bash
helm get values <release-name> > original_values.yaml
helm get manifest <release-name> > original_manifest.yaml
```
2. **Document Volume Mounts**:
```bash
kubectl describe deployment <app-name> | grep -A 10 "Mounts:"
```
3. **Verify Service Configuration**:
```bash
kubectl get svc <app-name> -o yaml
```
4. **Test Chart Template Locally**:
```bash
helm template <app-name> <chart> --values <values-file> | grep -A 20 "kind: Service"
```
### Commit & Push + ArgoCD CLI Checks
After adding a new ArgoCD Application and values files:
1. Commit and push the changes:
```bash
git add applications/<app>.yaml helm-values/<app>_values.*
git commit -m "feat(argocd): add <app> application"
git push # or: git push gitea master
```
2. ArgoCD CLI login (if not already authenticated):
```bash
# Example options; adjust for your environment
# Get admin password if needed
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d; echo
# Login (use your server/credentials)
argocd login argocd-server.argocd.svc.cluster.local --username admin --password <password> --insecure
```
3. Sync and wait for health:
```bash
argocd app sync <app>
argocd app wait <app> --sync --health --timeout 300
argocd app get <app>
argocd app list | grep <app>
```
4. If sync fails, inspect details and events:
```bash
argocd app history <app> --refresh
argocd app get <app> --refresh
kubectl -n default describe deploy/<app> || true
kubectl -n default get events --sort-by=.lastTimestamp | tail -n 50
```
### Post-Migration Verification
1. **Check Volume Mounts Match Original**:
```bash
kubectl describe pod -l app=<app-name> | grep -A 10 "Mounts:"
```
2. **Verify Service Ports**:
```bash
kubectl get svc <app-name>
```
3. **Test Application Access**:
```bash
curl -I http://<app-domain>
```
4. **Confirm Configuration Persistence**:
```bash
kubectl exec -it deployment/<app-name> -- ls -la /config
```
### ArgoCD Image-Updater Multi-Source Application Issues
#### Problem: Credential Errors with Multi-Source Applications
When using ArgoCD image-updater with multi-source applications (chart from external repo + values from Git), the image-updater may fail with credential errors like:
```
Could not update application spec: could not get creds for repo 'https://chart-repository.com': credentials for 'https://chart-repository.com' are not configured in Argo CD settings
```
#### Root Cause Analysis
1. **Multi-Source Confusion**: Image-updater tries to write back changes to the chart repository instead of the values repository
2. **Git Write-Back Limitations**: The `git` write-back method doesn't handle multi-source applications properly
3. **Repository Credentials**: External chart repositories (like Bananaspliff) don't have write credentials configured
#### Solution: Use ArgoCD API Write-Back Method
Instead of using `git` write-back method, use the `argocd` API method for multi-source applications:
The gluetun sidecar in `helm-values/qbittorrent_values.yaml` **must** keep:
```yaml
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: app=registry/image:latest
argocd-image-updater.argoproj.io/app.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: argocd # Use ArgoCD API instead of git
argocd-image-updater.argoproj.io/write-back-target: http://git-repo.local/values.git # Optional: specify target repo
- name: DOT
value: "off"
- name: DNS_ADDRESS
value: "10.128.0.1" # AirVPN's pushed resolver, reached over the tunnel — no DNS leak
```
#### Implementation Steps
1. **Update Image-Updater Configuration**:
```bash
kubectl patch configmap argocd-image-updater-config -n argocd --patch '{"data":{"git.user":"argocd-image-updater","git.email":"argocd@turing.lan"}}'
```
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).
2. **Change Application Write-Back Method**:
```bash
kubectl patch application <app-name> -n argocd --type='merge' --patch='{"metadata":{"annotations":{"argocd-image-updater.argoproj.io/write-back-method":"argocd"}}}'
```
3. **Restart Image-Updater**:
```bash
kubectl rollout restart deployment argocd-image-updater -n argocd
```
#### Verification Commands
```bash
# Check image-updater logs for success
kubectl logs -n argocd deployment/argocd-image-updater --tail=20
# Look for these success indicators:
# - "Successfully updated the live application spec"
# - "Processing results: applications=X images_considered=X images_skipped=0 images_updated=X errors=0"
# Verify applications remain healthy
argocd app list
# Check that pods are updated with new images
kubectl get pods -l app=<app-name>
```
#### Key Learnings
- **ArgoCD API Method**: Works better than Git write-back for multi-source applications
- **No Repository Credentials Needed**: ArgoCD API method doesn't require external repository write credentials
- **Application Spec Updates**: Changes are applied directly to ArgoCD application specs, not Git files
- **Multi-Source Compatibility**: This approach handles complex application configurations properly
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.
+16 -8
View File
@@ -38,13 +38,12 @@ This repository contains Kubernetes configuration files for a K3s cluster runnin
## 📁 Repository Structure
```
├── *_values.yaml # Helm values overrides for applications
├── my-actual-server/ # Custom Helm chart for Actual Budget
├── home-assistant-voice-llms/ # Custom Helm chart for Voice AI
├── prowlarr/ # Custom Helm chart for Prowlarr
├── kube-plex/ # Kubernetes-native Plex implementation
── *.yml # Infrastructure manifests (MetalLB, ingress, etc.)
└── persistent_volume*.yml # Storage definitions
├── applications/ # Argo CD Application manifests
├── resources/ # Plain Kubernetes resources (kubectl apply -f)
├── helm-values/ # Helm values for Argo-managed apps
├── non_argo_values/ # Helm values for apps managed directly with Helm
├── custom_helm_charts/ # Custom charts (actual, voice, prowlarr, lidarr)
── cluster_setup/ # Cluster bootstrap + update scripts
```
## 🔧 Common Operations
@@ -79,6 +78,15 @@ Run the provided script to update all nodes:
./update.sh
```
Old way:
```bash
master: curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false sh -s - --write-kubeconfig-mode 644 --disable servicelb --token torino --node-ip 192.168.222.237 --disable-cloud-controller --disable local-storage
nodes: curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false K3S_URL=https://192.168.222.237:6443 K3S_TOKEN=torino sh -
````
### Manual Update Process
#### 1. Update Master Node (turing1)
@@ -141,4 +149,4 @@ helm template <chart> -f <values> | kubectl apply --dry-run=client -f -
### Storage Requirements
- NFS server must be running on turing3.lan
- Applications require ReadWriteMany access for shared media
- Persistent volumes are dynamically provisioned via nfs-subdir-external-provisioner
- Persistent volumes are dynamically provisioned via nfs-subdir-external-provisioner
+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
+23
View File
@@ -0,0 +1,23 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: disk-usage-alert
namespace: argocd
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: manifests/disk-usage-alert
directory:
recurse: false
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
-35
View File
@@ -1,35 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: flaresolverr
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: flaresolverr=ghcr.io/flaresolverr/flaresolverr:latest
argocd-image-updater.argoproj.io/flaresolverr.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/flaresolverr.helm.image-name: image.repository
argocd-image-updater.argoproj.io/flaresolverr.helm.image-tag: image.tag
spec:
project: default
sources:
- repoURL: https://k8s-at-home.com/charts/
chart: flaresolverr
targetRevision: "*"
ref: charts
helm:
releaseName: flaresolverr
valueFiles:
- $values/helm-values/flaresolverr_values.yaml
- repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
ref: values
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
+69
View File
@@ -0,0 +1,69 @@
apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
name: media-image-updater
namespace: argocd
spec:
namespace: argocd
commonUpdateSettings:
updateStrategy: digest
writeBackConfig:
method: argocd
applicationRefs:
- namePattern: transmission
images:
- alias: transmission
imageName: haugene/transmission-openvpn:latest
manifestTargets:
helm:
name: image.repository
tag: image.tag
- namePattern: radarr
images:
- alias: radarr
imageName: ghcr.io/linuxserver/radarr:latest
manifestTargets:
helm:
name: image.repository
tag: image.tag
- namePattern: prowlarr
images:
- alias: prowlarr
imageName: lscr.io/linuxserver/prowlarr:latest
manifestTargets:
helm:
name: image.repository
tag: image.tag
- namePattern: ollama
images:
- alias: ollama
imageName: ollama/ollama:latest
manifestTargets:
helm:
name: image.name
tag: image.tag
- namePattern: plex
writeBackConfig:
method: git
gitConfig:
repository: "http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git"
branch: "master"
writeBackTarget: "helmvalues:/helm-values/plex_values.yml"
images:
- alias: plex
imageName: linuxserver/plex
commonUpdateSettings:
updateStrategy: newest-build
allowTags: "regexp:^(amd64-)?[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+-[0-9a-f]+-ls[0-9]+$"
manifestTargets:
helm:
name: image.repository
tag: image.tag
- namePattern: sonarr
images:
- alias: sonarr
imageName: ghcr.io/linuxserver/sonarr:latest
manifestTargets:
helm:
name: image.repository
tag: image.tag
+8 -20
View File
@@ -4,28 +4,16 @@ metadata:
name: lidarr
namespace: argocd
annotations:
# Enable automatic image updates for Lidarr
argocd-image-updater.argoproj.io/image-list: lidarr=ghcr.io/linuxserver/lidarr:latest
# Use digest strategy to track latest tag updates
argocd-image-updater.argoproj.io/lidarr.update-strategy: digest
# Write changes back via ArgoCD API (multi-source friendly)
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/lidarr.helm.image-name: image.repository
argocd-image-updater.argoproj.io/lidarr.helm.image-tag: image.tag
spec:
project: default
sources:
- repoURL: https://k8s-at-home.com/charts/
chart: lidarr
targetRevision: "*"
ref: charts
helm:
releaseName: lidarr
valueFiles:
- $values/helm-values/lidarr_values.yaml
- repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
ref: values
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: custom_helm_charts/lidarr
helm:
releaseName: lidarr
valueFiles:
- ../../helm-values/lidarr_values.yaml
destination:
server: https://kubernetes.default.svc
namespace: default
+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
-5
View File
@@ -4,11 +4,6 @@ metadata:
name: ollama
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: ollama=ollama/ollama:latest
argocd-image-updater.argoproj.io/ollama.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/ollama.helm.image-name: image.repository
argocd-image-updater.argoproj.io/ollama.helm.image-tag: image.tag
spec:
project: default
sources:
+5 -13
View File
@@ -1,30 +1,22 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plex
namespace: argocd
annotations:
# Enable automatic image updates for Plex
argocd-image-updater.argoproj.io/image-list: plex=linuxserver/plex:latest
# Use digest strategy to track latest tag updates
argocd-image-updater.argoproj.io/plex.update-strategy: digest
# Write changes back via ArgoCD API (multi-source friendly)
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/plex.helm.image-name: image.repository
argocd-image-updater.argoproj.io/plex.helm.image-tag: image.tag
spec:
project: default
sources:
- repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/kube-plex.git
path: charts/kube-plex
targetRevision: HEAD
targetRevision: master
ref: charts
helm:
releaseName: plex
valueFiles:
- $values/helm-values/plex_values.yml
- repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
targetRevision: master
ref: values
destination:
server: https://kubernetes.default.svc
@@ -34,5 +26,5 @@ spec:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
- CreateNamespace=true
- ServerSideApply=true
+2 -7
View File
@@ -4,21 +4,16 @@ metadata:
name: prowlarr
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: prowlarr=lscr.io/linuxserver/prowlarr:latest
argocd-image-updater.argoproj.io/prowlarr.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/prowlarr.helm.image-name: image.repository
argocd-image-updater.argoproj.io/prowlarr.helm.image-tag: image.tag
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi.git
targetRevision: HEAD
path: prowlarr
path: custom_helm_charts/prowlarr
helm:
releaseName: prowlarr
valueFiles:
- ../helm-values/prowlarr_values.yml
- ../../helm-values/prowlarr_values.yml
destination:
server: https://kubernetes.default.svc
namespace: default
+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
-5
View File
@@ -5,13 +5,8 @@ metadata:
namespace: argocd
annotations:
# Enable automatic image updates for Radarr
argocd-image-updater.argoproj.io/image-list: radarr=ghcr.io/linuxserver/radarr:latest
# Use digest strategy to track latest tag updates
argocd-image-updater.argoproj.io/radarr.update-strategy: digest
# Write changes back via ArgoCD API (multi-source friendly)
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/radarr.helm.image-name: image.repository
argocd-image-updater.argoproj.io/radarr.helm.image-tag: image.tag
spec:
project: default
sources:
-5
View File
@@ -5,13 +5,8 @@ metadata:
namespace: argocd
annotations:
# Enable automatic image updates for Sonarr
argocd-image-updater.argoproj.io/image-list: sonarr=ghcr.io/linuxserver/sonarr:latest
# Use digest strategy to track latest tag updates
argocd-image-updater.argoproj.io/sonarr.update-strategy: digest
# Write changes back via ArgoCD API (multi-source friendly)
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/sonarr.helm.image-name: image.repository
argocd-image-updater.argoproj.io/sonarr.helm.image-tag: image.tag
spec:
project: default
sources:
-5
View File
@@ -4,11 +4,6 @@ metadata:
name: transmission
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: transmission=haugene/transmission-openvpn:latest
argocd-image-updater.argoproj.io/transmission.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: argocd
argocd-image-updater.argoproj.io/transmission.helm.image-name: image.repository
argocd-image-updater.argoproj.io/transmission.helm.image-tag: image.tag
spec:
project: default
sources:
-40
View File
@@ -1,40 +0,0 @@
# Example ArgoCD Application with Image Auto-Update
# This demonstrates how to set up your existing Helm applications in ArgoCD
# with automatic "latest" tag updates
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plex-example
namespace: argocd
annotations:
# Enable automatic image updates for Plex
argocd-image-updater.argoproj.io/image-list: plex=ghcr.io/k8s-at-home/plex:latest
# Use 'newest-build' strategy for latest images
argocd-image-updater.argoproj.io/plex.update-strategy: newest-build
# Write back to ArgoCD (for testing - production should use git method)
argocd-image-updater.argoproj.io/write-back-method: argocd
spec:
project: default
source:
# Point to your repository (replace with your actual Git repo)
repoURL: https://github.com/munnerz/kube-plex
path: charts/kube-plex
targetRevision: HEAD
helm:
valueFiles:
# This would reference your existing plex_values.yml
# For now, this is just an example structure
- values.yaml
parameters:
- name: image.tag
value: latest
destination:
server: https://kubernetes.default.svc
namespace: plex
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
-17
View File
@@ -1,17 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-image-updater-config
namespace: argocd
labels:
app.kubernetes.io/name: argocd-image-updater-config
app.kubernetes.io/part-of: argocd-image-updater
data:
# Force image-updater to use the ArgoCD API for write-back to avoid multi-source git issues.
write-back-method: argocd
# Keep git identity in case git mode is explicitly used for single-source apps later.
git.user: argocd-image-updater
git.email: argocd@turing.lan
argocd.server_addr: argocd-server.argocd.svc.cluster.local
argocd.plaintext: "true"
argocd.insecure: "true"
-114
View File
@@ -1,114 +0,0 @@
# ArgoCD Migration Guide for TuringPi Cluster
## ArgoCD Access Information
**Web UI Access:**
- URL: http://192.168.222.25 (LoadBalancer IP)
- Alternative: http://argocd.turing.lan (if you add to your hosts file)
- Username: `admin`
- Password: `fJ3diddVd2yson3W`
## Migration Strategy
Your existing Helm-based applications can be migrated to ArgoCD gradually. Here's how:
### Option 1: Keep Existing Helm + Add GitOps Overlay
1. Keep your current `*_values.yaml` files
2. Create ArgoCD Applications that reference the same charts
3. ArgoCD manages the lifecycle, you keep the familiar structure
### Option 2: Git-First Approach (Recommended for Production)
1. Commit your values files to a Git repository
2. Use ArgoCD's Git source with `argocd-image-updater` writing back to Git
3. Full GitOps workflow with audit trail
## Adding Image Auto-Updates to Your Applications
For any application, add these annotations to the ArgoCD Application manifest:
```yaml
metadata:
annotations:
# Define which images to track
argocd-image-updater.argoproj.io/image-list: myapp=myregistry/myapp:latest
# Use newest-build strategy for "latest" tags
argocd-image-updater.argoproj.io/myapp.update-strategy: newest-build
# Write method: 'argocd' for testing, 'git' for production
argocd-image-updater.argoproj.io/write-back-method: argocd
```
## Example: Converting Your Plex Deployment
Your current command:
```bash
helm upgrade plex kube-plex/charts/kube-plex --values plex_values.yml
```
Becomes this ArgoCD Application:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plex
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: plex=ghcr.io/k8s-at-home/plex:latest
argocd-image-updater.argoproj.io/plex.update-strategy: newest-build
argocd-image-updater.argoproj.io/write-back-method: argocd
spec:
project: default
source:
repoURL: https://github.com/munnerz/kube-plex # or your fork
path: charts/kube-plex
targetRevision: HEAD
helm:
valueFiles:
- ../../plex_values.yml # Reference your existing values
destination:
server: https://kubernetes.default.svc
namespace: plex
syncPolicy:
automated:
prune: true
selfHeal: true
```
## Quick Start Commands
1. **Access ArgoCD UI**: Visit http://192.168.222.25 with admin/fJ3diddVd2yson3W
2. **Create your first application via CLI**:
```bash
# Install ArgoCD CLI (optional)
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
# Login (using the LoadBalancer IP)
argocd login 192.168.222.25 --insecure --username admin --password fJ3diddVd2yson3W
```
3. **Apply the example application**:
```bash
kubectl apply -f argocd-example-app.yaml
```
## Benefits You Get Immediately
**Keep using Helm** - ArgoCD manages Helm releases
**Auto image updates** - Latest tags update automatically
**Visual UI** - See deployment status, sync state, rollback easily
**GitOps ready** - When you want to commit values to Git
**Rollback capability** - Easy rollback to previous versions
**Multi-environment** - Can manage dev/staging/prod from one place
## Next Steps
1. Access the ArgoCD UI and familiarize yourself with it
2. Create ArgoCD Applications for 1-2 of your existing services
3. Test the image auto-update functionality
4. Once comfortable, migrate more applications
5. Consider setting up a Git repository for full GitOps workflow
Your existing Helm workflow continues to work while you gain GitOps benefits!
+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"
+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
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
appVersion: "1.0"
description: Lidarr - Automatic music downloader and manager
name: lidarr
version: 0.1.0
icon: https://lidarr.audio/img/logo.png

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@@ -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 "lidarr.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 "lidarr.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "lidarr.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 "lidarr.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8686 to use your application"
kubectl port-forward $POD_NAME 8686:{{ .Values.service.port }}
{{- end }}
@@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "lidarr.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 "lidarr.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 "lidarr.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,49 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "lidarr.fullname" . }}
labels:
app: {{ template "lidarr.name" . }}
chart: {{ template "lidarr.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ template "lidarr.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "lidarr.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
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 "lidarr.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app: {{ template "lidarr.name" . }}
chart: {{ template "lidarr.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 "lidarr.fullname" . }}
labels:
app: {{ template "lidarr.name" . }}
chart: {{ template "lidarr.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 "lidarr.name" . }}
release: {{ .Release.Name }}
+60
View File
@@ -0,0 +1,60 @@
replicaCount: 1
image:
repository: ghcr.io/linuxserver/lidarr
tag: latest
pullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Europe/Amsterdam"
service:
type: ClusterIP
port: 8686
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
path: /
hosts:
- lidarr.example.org
tls:
- secretName: lidarr-example-org
hosts:
- lidarr.example.org
volumes:
- name: plex-data
persistentVolumeClaim:
claimName: plex-data
volumeMounts:
- name: plex-data
mountPath: "/config"
subPath: "configs/lidarr"
- name: plex-data
mountPath: "/nfs"
resources:
requests:
memory: 500Mi
cpu: 500m
ephemeral-storage: 50Mi
limits:
memory: 800Mi
cpu: 1000m
ephemeral-storage: 1Gi
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: {}
+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
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

@@ -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 }}

Some files were not shown because too many files have changed in this diff Show More