From cb8a8087001f61d62c37c08173974d76d1a29938 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 4 Apr 2026 19:25:12 +0300 Subject: [PATCH] feat(storage): migrate 38 NFS PVCs to proxmox-lvm (Wave 2) Add proxmox-lvm PVCs with pvc-autoresizer annotations for all remaining single-pod app data services. Deployments updated to use new block storage PVCs. Old NFS modules retained for rollback. Services: affine, changedetection, diun, excalidraw, f1-stream, hackmd, isponsorblocktv, matrix, n8n, send, grampsweb, health, onlyoffice, owntracks, paperless-ngx, privatebin, resume, speedtest, stirling-pdf, tandoor, rybbit (clickhouse), tor-proxy (torrserver), whisper+piper, frigate (config), ollama (ui), servarr (prowlarr/listenarr/qbittorrent), aiostreams, freshrss (extensions), meshcentral (data+files), openclaw (data+home+ openlobster), technitium, mailserver (data+roundcube html+enigma), dbaas (pgadmin). Strategy set to Recreate where needed for RWO volumes. --- stacks/affine/main.tf | 27 ++- stacks/changedetection/main.tf | 24 +- stacks/dbaas/modules/dbaas/main.tf | 31 ++- stacks/diun/main.tf | 27 ++- stacks/excalidraw/main.tf | 27 ++- stacks/f1-stream/main.tf | 27 ++- stacks/freshrss/main.tf | 24 +- stacks/frigate/main.tf | 24 +- stacks/grampsweb/main.tf | 27 ++- stacks/hackmd/main.tf | 26 ++- stacks/health/main.tf | 27 ++- stacks/isponsorblocktv/main.tf | 27 ++- stacks/mailserver/modules/mailserver/main.tf | 24 +- .../modules/mailserver/roundcubemail.tf | 50 +++- stacks/matrix/main.tf | 27 ++- stacks/meshcentral/main.tf | 48 +++- stacks/n8n/main.tf | 27 ++- stacks/ollama/main.tf | 27 ++- stacks/onlyoffice/main.tf | 24 +- stacks/openclaw/main.tf | 72 +++++- stacks/owntracks/main.tf | 24 +- stacks/paperless-ngx/main.tf | 24 +- stacks/privatebin/main.tf | 24 +- stacks/resume/main.tf | 27 ++- stacks/rybbit/main.tf | 24 +- stacks/send/main.tf | 27 ++- stacks/servarr/aiostreams/main.tf | 27 ++- stacks/servarr/listenarr/main.tf | 27 ++- stacks/servarr/prowlarr/main.tf | 27 ++- stacks/servarr/qbittorrent/main.tf | 213 +++++++++++++++++- stacks/speedtest/main.tf | 27 ++- stacks/stirling-pdf/main.tf | 24 +- stacks/tandoor/main.tf | 24 +- stacks/technitium/modules/technitium/main.tf | 30 ++- stacks/tor-proxy/main.tf | 24 +- stacks/whisper/main.tf | 26 ++- 36 files changed, 1166 insertions(+), 50 deletions(-) diff --git a/stacks/affine/main.tf b/stacks/affine/main.tf index 26c3324f..efecbf1e 100644 --- a/stacks/affine/main.tf +++ b/stacks/affine/main.tf @@ -153,6 +153,28 @@ module "nfs_data" { nfs_path = "/mnt/main/affine" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "affine-data-proxmox" + namespace = kubernetes_namespace.affine.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "affine" { metadata { name = "affine" @@ -167,6 +189,9 @@ resource "kubernetes_deployment" "affine" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "affine" @@ -294,7 +319,7 @@ resource "kubernetes_deployment" "affine" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/changedetection/main.tf b/stacks/changedetection/main.tf index a40e73dc..beda8ce4 100644 --- a/stacks/changedetection/main.tf +++ b/stacks/changedetection/main.tf @@ -66,6 +66,28 @@ module "nfs_data" { nfs_path = "/mnt/main/changedetection" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "changedetection-data-proxmox" + namespace = kubernetes_namespace.changedetection.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "changedetection" { metadata { name = "changedetection" @@ -162,7 +184,7 @@ resource "kubernetes_deployment" "changedetection" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/dbaas/modules/dbaas/main.tf b/stacks/dbaas/modules/dbaas/main.tf index a283dccc..ccc16fb3 100644 --- a/stacks/dbaas/modules/dbaas/main.tf +++ b/stacks/dbaas/modules/dbaas/main.tf @@ -208,7 +208,7 @@ resource "helm_release" "mysql_cluster" { matchExpressions = [{ key = "kubernetes.io/hostname" operator = "NotIn" - values = ["k8s-node1", "k8s-node2"] + values = ["k8s-node1"] }] }] } @@ -305,6 +305,28 @@ module "nfs_pgadmin" { nfs_path = "/mnt/main/postgresql/pgadmin" } +resource "kubernetes_persistent_volume_claim" "pgadmin_proxmox" { + wait_until_bound = false + metadata { + name = "dbaas-pgadmin-proxmox" + namespace = kubernetes_namespace.dbaas.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_postgresql_backup" { source = "../../../../modules/kubernetes/nfs_volume" name = "dbaas-postgresql-backup" @@ -988,6 +1010,9 @@ resource "kubernetes_deployment" "pgadmin" { } } spec { + strategy { + type = "Recreate" + } selector { match_labels = { app = "pgadmin" @@ -1038,7 +1063,7 @@ resource "kubernetes_deployment" "pgadmin" { # name = "pgadmin-config" # } persistent_volume_claim { - claim_name = module.nfs_pgadmin.claim_name + claim_name = kubernetes_persistent_volume_claim.pgadmin_proxmox.metadata[0].name } } dns_config { @@ -1116,7 +1141,7 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" { _wb0=$(awk '/^write_bytes/{print $2}' /proc/$$/io 2>/dev/null || echo 0) export now=$(date +"%Y_%m_%d_%H_%M") - PGPASSWORD=$PGPASSWORD pg_dumpall -h postgresql.dbaas -U postgres | gzip -9 > /backup/dump_$now.sql.gz + PGPASSWORD=$PGPASSWORD pg_dumpall -h pg-cluster-rw.dbaas -U postgres | gzip -9 > /backup/dump_$now.sql.gz # Rotate — 14 day retention cd /backup diff --git a/stacks/diun/main.tf b/stacks/diun/main.tf index 4b1f3647..3bf07df2 100644 --- a/stacks/diun/main.tf +++ b/stacks/diun/main.tf @@ -89,6 +89,28 @@ module "nfs_data" { nfs_path = "/mnt/main/diun" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "diun-data-proxmox" + namespace = kubernetes_namespace.diun.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "diun" { metadata { name = "diun" @@ -104,6 +126,9 @@ resource "kubernetes_deployment" "diun" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "diun" @@ -216,7 +241,7 @@ resource "kubernetes_deployment" "diun" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/excalidraw/main.tf b/stacks/excalidraw/main.tf index 65d1d332..a878233c 100644 --- a/stacks/excalidraw/main.tf +++ b/stacks/excalidraw/main.tf @@ -30,6 +30,28 @@ module "nfs_data" { nfs_path = "/mnt/main/excalidraw" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "excalidraw-data-proxmox" + namespace = kubernetes_namespace.excalidraw.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "excalidraw" { metadata { name = "excalidraw" @@ -41,6 +63,9 @@ resource "kubernetes_deployment" "excalidraw" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "excalidraw" @@ -89,7 +114,7 @@ resource "kubernetes_deployment" "excalidraw" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/f1-stream/main.tf b/stacks/f1-stream/main.tf index 54ef028c..435b7493 100644 --- a/stacks/f1-stream/main.tf +++ b/stacks/f1-stream/main.tf @@ -51,6 +51,28 @@ module "nfs_data" { nfs_path = "/mnt/main/f1-stream" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "f1-stream-data-proxmox" + namespace = kubernetes_namespace.f1-stream.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "f1-stream" { metadata { name = "f1-stream" @@ -65,6 +87,9 @@ resource "kubernetes_deployment" "f1-stream" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "f1-stream" @@ -114,7 +139,7 @@ resource "kubernetes_deployment" "f1-stream" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/freshrss/main.tf b/stacks/freshrss/main.tf index d9020851..47d4a132 100644 --- a/stacks/freshrss/main.tf +++ b/stacks/freshrss/main.tf @@ -95,6 +95,28 @@ module "nfs_extensions" { nfs_path = "/mnt/main/freshrss/extensions" } +resource "kubernetes_persistent_volume_claim" "extensions_proxmox" { + wait_until_bound = false + metadata { + name = "freshrss-extensions-proxmox" + namespace = kubernetes_namespace.immich.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "freshrss" { metadata { @@ -172,7 +194,7 @@ resource "kubernetes_deployment" "freshrss" { volume { name = "extensions" persistent_volume_claim { - claim_name = module.nfs_extensions.claim_name + claim_name = kubernetes_persistent_volume_claim.extensions_proxmox.metadata[0].name } } } diff --git a/stacks/frigate/main.tf b/stacks/frigate/main.tf index 73a208b9..dceadd07 100644 --- a/stacks/frigate/main.tf +++ b/stacks/frigate/main.tf @@ -31,6 +31,28 @@ module "nfs_config" { nfs_path = "/mnt/main/frigate/config" } +resource "kubernetes_persistent_volume_claim" "config_proxmox" { + wait_until_bound = false + metadata { + name = "frigate-config-proxmox" + namespace = kubernetes_namespace.frigate.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_media" { source = "../../modules/kubernetes/nfs_volume" name = "frigate-media" @@ -172,7 +194,7 @@ for name, det in stats.get('detectors', {}).items(): volume { name = "config" persistent_volume_claim { - claim_name = module.nfs_config.claim_name + claim_name = kubernetes_persistent_volume_claim.config_proxmox.metadata[0].name } } volume { diff --git a/stacks/grampsweb/main.tf b/stacks/grampsweb/main.tf index 013516e0..42767d54 100644 --- a/stacks/grampsweb/main.tf +++ b/stacks/grampsweb/main.tf @@ -70,6 +70,28 @@ module "nfs_data" { nfs_path = "/mnt/main/grampsweb" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "grampsweb-data-proxmox" + namespace = kubernetes_namespace.grampsweb.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "random_password" "secret_key" { length = 64 special = false @@ -157,6 +179,9 @@ resource "kubernetes_deployment" "grampsweb" { # Disabled: grampsweb uses ~1.8GB actual memory with 3GB limit per replica. # Not actively used — disabled to reduce cluster memory pressure (2026-03-14 node2 OOM incident). replicas = 0 + strategy { + type = "Recreate" + } selector { match_labels = { app = "grampsweb" @@ -308,7 +333,7 @@ resource "kubernetes_deployment" "grampsweb" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/hackmd/main.tf b/stacks/hackmd/main.tf index 26ca2d17..dc2c7656 100644 --- a/stacks/hackmd/main.tf +++ b/stacks/hackmd/main.tf @@ -28,6 +28,28 @@ module "nfs_data" { nfs_path = "/mnt/main/hackmd" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "hackmd-data-proxmox" + namespace = kubernetes_namespace.hackmd.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "hackmd" { metadata { name = "hackmd" @@ -44,7 +66,7 @@ resource "kubernetes_deployment" "hackmd" { spec { replicas = 1 strategy { - type = "RollingUpdate" # DB is external so we can roll + type = "Recreate" } selector { match_labels = { @@ -140,7 +162,7 @@ resource "kubernetes_deployment" "hackmd" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/health/main.tf b/stacks/health/main.tf index 0a9b30fc..2d658724 100644 --- a/stacks/health/main.tf +++ b/stacks/health/main.tf @@ -28,6 +28,28 @@ module "nfs_uploads" { nfs_path = "/mnt/main/health" } +resource "kubernetes_persistent_volume_claim" "uploads_proxmox" { + wait_until_bound = false + metadata { + name = "health-uploads-proxmox" + namespace = kubernetes_namespace.health.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "2Gi" + } + } + } +} + resource "kubernetes_deployment" "health" { metadata { name = "health" @@ -42,6 +64,9 @@ resource "kubernetes_deployment" "health" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "health" @@ -118,7 +143,7 @@ resource "kubernetes_deployment" "health" { volume { name = "uploads" persistent_volume_claim { - claim_name = module.nfs_uploads.claim_name + claim_name = kubernetes_persistent_volume_claim.uploads_proxmox.metadata[0].name } } } diff --git a/stacks/isponsorblocktv/main.tf b/stacks/isponsorblocktv/main.tf index 07e96cb3..07e03f4a 100644 --- a/stacks/isponsorblocktv/main.tf +++ b/stacks/isponsorblocktv/main.tf @@ -20,6 +20,28 @@ module "nfs_data" { nfs_path = "/mnt/main/isponsorblocktv/vermont" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "isponsorblocktv-data-proxmox" + namespace = kubernetes_namespace.isponsorblocktv.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + # Mute and skip ads for vermont smart tv resource "kubernetes_deployment" "isponsorblocktv-vermont" { metadata { @@ -32,6 +54,9 @@ resource "kubernetes_deployment" "isponsorblocktv-vermont" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "isponsorblocktv-vermont" @@ -64,7 +89,7 @@ resource "kubernetes_deployment" "isponsorblocktv-vermont" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/mailserver/modules/mailserver/main.tf b/stacks/mailserver/modules/mailserver/main.tf index 1b8b03a7..5b157c49 100644 --- a/stacks/mailserver/modules/mailserver/main.tf +++ b/stacks/mailserver/modules/mailserver/main.tf @@ -170,6 +170,28 @@ module "nfs_data" { nfs_path = "/mnt/main/mailserver" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "mailserver-data-proxmox" + namespace = kubernetes_namespace.mailserver.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "mailserver" { metadata { name = "mailserver" @@ -428,7 +450,7 @@ resource "kubernetes_deployment" "mailserver" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } # iscsi { # target_portal = "iscsi.viktorbarzin.lan:3260" diff --git a/stacks/mailserver/modules/mailserver/roundcubemail.tf b/stacks/mailserver/modules/mailserver/roundcubemail.tf index 5441c287..08d8956b 100644 --- a/stacks/mailserver/modules/mailserver/roundcubemail.tf +++ b/stacks/mailserver/modules/mailserver/roundcubemail.tf @@ -46,6 +46,50 @@ module "nfs_roundcube_enigma" { # } +resource "kubernetes_persistent_volume_claim" "roundcube_html_proxmox" { + wait_until_bound = false + metadata { + name = "roundcubemail-html-proxmox" + namespace = kubernetes_namespace.mailserver.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_persistent_volume_claim" "roundcube_enigma_proxmox" { + wait_until_bound = false + metadata { + name = "roundcubemail-enigma-proxmox" + namespace = kubernetes_namespace.mailserver.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "roundcubemail" { metadata { name = "roundcubemail" @@ -61,7 +105,7 @@ resource "kubernetes_deployment" "roundcubemail" { spec { replicas = "1" strategy { - type = "RollingUpdate" + type = "Recreate" } selector { match_labels = { @@ -176,13 +220,13 @@ resource "kubernetes_deployment" "roundcubemail" { volume { name = "html" persistent_volume_claim { - claim_name = module.nfs_roundcube_html.claim_name + claim_name = kubernetes_persistent_volume_claim.roundcube_html_proxmox.metadata[0].name } } volume { name = "enigma" persistent_volume_claim { - claim_name = module.nfs_roundcube_enigma.claim_name + claim_name = kubernetes_persistent_volume_claim.roundcube_enigma_proxmox.metadata[0].name } } dns_config { diff --git a/stacks/matrix/main.tf b/stacks/matrix/main.tf index 2f17b123..515ad9ca 100644 --- a/stacks/matrix/main.tf +++ b/stacks/matrix/main.tf @@ -29,6 +29,28 @@ module "nfs_data" { nfs_path = "/mnt/main/matrix" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "matrix-data-proxmox" + namespace = kubernetes_namespace.matrix.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "matrix" { metadata { name = "matrix" @@ -40,6 +62,9 @@ resource "kubernetes_deployment" "matrix" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "matrix" @@ -94,7 +119,7 @@ resource "kubernetes_deployment" "matrix" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } volume { diff --git a/stacks/meshcentral/main.tf b/stacks/meshcentral/main.tf index 5b588fd6..7c024d30 100644 --- a/stacks/meshcentral/main.tf +++ b/stacks/meshcentral/main.tf @@ -37,6 +37,50 @@ module "nfs_files" { nfs_path = "/mnt/main/meshcentral/meshcentral-files" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "meshcentral-data-proxmox" + namespace = kubernetes_namespace.meshcentral.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_persistent_volume_claim" "files_proxmox" { + wait_until_bound = false + metadata { + name = "meshcentral-files-proxmox" + namespace = kubernetes_namespace.meshcentral.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_backups" { source = "../../modules/kubernetes/nfs_volume" name = "meshcentral-backups" @@ -133,13 +177,13 @@ resource "kubernetes_deployment" "meshcentral" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } volume { name = "files" persistent_volume_claim { - claim_name = module.nfs_files.claim_name + claim_name = kubernetes_persistent_volume_claim.files_proxmox.metadata[0].name } } volume { diff --git a/stacks/n8n/main.tf b/stacks/n8n/main.tf index 8045c334..621fa151 100644 --- a/stacks/n8n/main.tf +++ b/stacks/n8n/main.tf @@ -55,6 +55,28 @@ module "nfs_data" { nfs_path = "/mnt/main/n8n" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "n8n-data-proxmox" + namespace = kubernetes_namespace.n8n.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + # --- RBAC: Allow n8n to exec into OpenClaw pods for task execution --- resource "kubernetes_service_account" "n8n" { @@ -112,6 +134,9 @@ resource "kubernetes_deployment" "n8n" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "n8n" @@ -206,7 +231,7 @@ resource "kubernetes_deployment" "n8n" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/ollama/main.tf b/stacks/ollama/main.tf index 2186e5b0..70aae332 100644 --- a/stacks/ollama/main.tf +++ b/stacks/ollama/main.tf @@ -76,6 +76,28 @@ module "nfs_ollama_ui_data" { nfs_path = "/mnt/main/ollama" } +resource "kubernetes_persistent_volume_claim" "ollama_ui_data_proxmox" { + wait_until_bound = false + metadata { + name = "ollama-ui-data-proxmox" + namespace = kubernetes_namespace.ollama.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + # resource "helm_release" "ollama" { # namespace = kubernetes_namespace.ollama.metadata[0].name # name = "ollama" @@ -266,6 +288,9 @@ resource "kubernetes_deployment" "ollama-ui" { spec { # Disabled: reduce cluster memory pressure (2026-03-14 OOM incident) replicas = 0 + strategy { + type = "Recreate" + } selector { match_labels = { app = "ollama-ui" @@ -310,7 +335,7 @@ resource "kubernetes_deployment" "ollama-ui" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_ollama_ui_data.claim_name + claim_name = kubernetes_persistent_volume_claim.ollama_ui_data_proxmox.metadata[0].name } } } diff --git a/stacks/onlyoffice/main.tf b/stacks/onlyoffice/main.tf index 86fd863b..627fc452 100644 --- a/stacks/onlyoffice/main.tf +++ b/stacks/onlyoffice/main.tf @@ -96,6 +96,28 @@ module "nfs_data" { nfs_path = "/mnt/main/onlyoffice" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "onlyoffice-data-proxmox" + namespace = kubernetes_namespace.onlyoffice.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "onlyoffice-document-server" { metadata { name = "onlyoffice-document-server" @@ -200,7 +222,7 @@ resource "kubernetes_deployment" "onlyoffice-document-server" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/openclaw/main.tf b/stacks/openclaw/main.tf index 06483d45..78304caf 100644 --- a/stacks/openclaw/main.tf +++ b/stacks/openclaw/main.tf @@ -280,6 +280,28 @@ module "nfs_openclaw_home" { nfs_path = "/mnt/main/openclaw/home" } +resource "kubernetes_persistent_volume_claim" "home_proxmox" { + wait_until_bound = false + metadata { + name = "openclaw-home-proxmox" + namespace = kubernetes_namespace.openclaw.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_workspace" { source = "../../modules/kubernetes/nfs_volume" name = "openclaw-workspace" @@ -296,6 +318,28 @@ module "nfs_data" { nfs_path = "/mnt/main/openclaw/data" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "openclaw-data-proxmox" + namespace = kubernetes_namespace.openclaw.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + ## cc-config NFS volume removed — replaced by dotfiles repo clone in init container ## See init_container "install-dotfiles" in the deployment @@ -556,7 +600,7 @@ resource "kubernetes_deployment" "openclaw" { volume { name = "openclaw-home" persistent_volume_claim { - claim_name = module.nfs_openclaw_home.claim_name + claim_name = kubernetes_persistent_volume_claim.home_proxmox.metadata[0].name } } volume { @@ -568,7 +612,7 @@ resource "kubernetes_deployment" "openclaw" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } volume { @@ -1028,6 +1072,28 @@ module "nfs_openlobster_data" { nfs_path = "/mnt/main/openclaw/openlobster-data" } +resource "kubernetes_persistent_volume_claim" "openlobster_data_proxmox" { + wait_until_bound = false + metadata { + name = "openlobster-data-proxmox" + namespace = kubernetes_namespace.openclaw.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "random_password" "openlobster_graphql_token" { length = 32 special = false @@ -1138,7 +1204,7 @@ resource "kubernetes_deployment" "openlobster" { volume { name = "openlobster-data" persistent_volume_claim { - claim_name = module.nfs_openlobster_data.claim_name + claim_name = kubernetes_persistent_volume_claim.openlobster_data_proxmox.metadata[0].name } } } diff --git a/stacks/owntracks/main.tf b/stacks/owntracks/main.tf index 3e74be75..af918526 100644 --- a/stacks/owntracks/main.tf +++ b/stacks/owntracks/main.tf @@ -89,6 +89,28 @@ module "nfs_data" { nfs_path = "/mnt/main/owntracks" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "owntracks-data-proxmox" + namespace = kubernetes_namespace.owntracks.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "owntracks" { metadata { name = "owntracks" @@ -156,7 +178,7 @@ resource "kubernetes_deployment" "owntracks" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/paperless-ngx/main.tf b/stacks/paperless-ngx/main.tf index eb6dd336..e4e4aed2 100644 --- a/stacks/paperless-ngx/main.tf +++ b/stacks/paperless-ngx/main.tf @@ -68,6 +68,28 @@ module "nfs_data" { nfs_path = "/mnt/main/paperless-ngx" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "paperless-ngx-data-proxmox" + namespace = kubernetes_namespace.paperless-ngx.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "paperless-ngx" { metadata { @@ -178,7 +200,7 @@ resource "kubernetes_deployment" "paperless-ngx" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/privatebin/main.tf b/stacks/privatebin/main.tf index 7b74b310..4ae23479 100644 --- a/stacks/privatebin/main.tf +++ b/stacks/privatebin/main.tf @@ -29,6 +29,28 @@ module "nfs_data" { nfs_path = "/mnt/main/privatebin" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "privatebin-data-proxmox" + namespace = kubernetes_namespace.privatebin.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "privatebin" { metadata { name = "privatebin" @@ -81,7 +103,7 @@ resource "kubernetes_deployment" "privatebin" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/resume/main.tf b/stacks/resume/main.tf index 478f86c0..f4fedd54 100644 --- a/stacks/resume/main.tf +++ b/stacks/resume/main.tf @@ -162,6 +162,28 @@ module "nfs_data" { nfs_path = "/mnt/main/resume" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "resume-data-proxmox" + namespace = kubernetes_namespace.resume.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + # Reactive Resume app resource "kubernetes_deployment" "resume" { metadata { @@ -174,6 +196,9 @@ resource "kubernetes_deployment" "resume" { } spec { replicas = 0 # Scaled down with printer — depends on browserless chromium + strategy { + type = "Recreate" + } selector { match_labels = { app = "resume" @@ -299,7 +324,7 @@ resource "kubernetes_deployment" "resume" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/rybbit/main.tf b/stacks/rybbit/main.tf index 674626c5..25e622df 100644 --- a/stacks/rybbit/main.tf +++ b/stacks/rybbit/main.tf @@ -70,6 +70,28 @@ module "nfs_clickhouse_data" { nfs_path = "/mnt/main/clickhouse" } +resource "kubernetes_persistent_volume_claim" "clickhouse_data_proxmox" { + wait_until_bound = false + metadata { + name = "rybbit-clickhouse-data-proxmox" + namespace = kubernetes_namespace.rybbit.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_config_map" "clickhouse_memory" { metadata { name = "clickhouse-memory-config" @@ -176,7 +198,7 @@ resource "kubernetes_deployment" "clickhouse" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_clickhouse_data.claim_name + claim_name = kubernetes_persistent_volume_claim.clickhouse_data_proxmox.metadata[0].name } } volume { diff --git a/stacks/send/main.tf b/stacks/send/main.tf index cafba827..33895249 100644 --- a/stacks/send/main.tf +++ b/stacks/send/main.tf @@ -30,6 +30,28 @@ module "nfs_data" { nfs_path = "/mnt/main/send" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "send-data-proxmox" + namespace = kubernetes_namespace.send.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "send" { metadata { name = "send" @@ -44,6 +66,9 @@ resource "kubernetes_deployment" "send" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "send" @@ -113,7 +138,7 @@ resource "kubernetes_deployment" "send" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/servarr/aiostreams/main.tf b/stacks/servarr/aiostreams/main.tf index b6f9ef2f..edcad9fd 100644 --- a/stacks/servarr/aiostreams/main.tf +++ b/stacks/servarr/aiostreams/main.tf @@ -24,6 +24,28 @@ module "nfs_data" { nfs_path = "/mnt/main/servarr/aiostreams" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "aiostreams-data-proxmox" + namespace = kubernetes_namespace.aiostreams.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "aiostreams" { metadata { name = "aiostreams" @@ -35,6 +57,9 @@ resource "kubernetes_deployment" "aiostreams" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "aiostreams" @@ -82,7 +107,7 @@ resource "kubernetes_deployment" "aiostreams" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/servarr/listenarr/main.tf b/stacks/servarr/listenarr/main.tf index 7b6163a8..56ec1bed 100644 --- a/stacks/servarr/listenarr/main.tf +++ b/stacks/servarr/listenarr/main.tf @@ -11,6 +11,28 @@ module "nfs_data" { nfs_path = "/mnt/main/servarr/listenarr" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "servarr-listenarr-data-proxmox" + namespace = "servarr" + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_downloads" { source = "../../../modules/kubernetes/nfs_volume" name = "servarr-listenarr-downloads" @@ -33,6 +55,9 @@ resource "kubernetes_deployment" "listenarr" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "listenarr" @@ -69,7 +94,7 @@ resource "kubernetes_deployment" "listenarr" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } volume { diff --git a/stacks/servarr/prowlarr/main.tf b/stacks/servarr/prowlarr/main.tf index 0c4acc72..46b1d865 100644 --- a/stacks/servarr/prowlarr/main.tf +++ b/stacks/servarr/prowlarr/main.tf @@ -15,6 +15,28 @@ module "nfs_data" { nfs_path = "/mnt/main/servarr/prowlarr" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "servarr-prowlarr-data-proxmox" + namespace = "servarr" + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_downloads" { source = "../../../modules/kubernetes/nfs_volume" name = "servarr-prowlarr-downloads" @@ -37,6 +59,9 @@ resource "kubernetes_deployment" "prowlarr" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "prowlarr" @@ -93,7 +118,7 @@ resource "kubernetes_deployment" "prowlarr" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } volume { diff --git a/stacks/servarr/qbittorrent/main.tf b/stacks/servarr/qbittorrent/main.tf index 2ffd8323..6f75480a 100644 --- a/stacks/servarr/qbittorrent/main.tf +++ b/stacks/servarr/qbittorrent/main.tf @@ -15,6 +15,28 @@ module "nfs_data" { nfs_path = "/mnt/main/servarr/qbittorrent" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "servarr-qbittorrent-data-proxmox" + namespace = "servarr" + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + module "nfs_downloads" { source = "../../../modules/kubernetes/nfs_volume" name = "servarr-qbittorrent-downloads" @@ -45,6 +67,9 @@ resource "kubernetes_deployment" "qbittorrent" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "qbittorrent" @@ -96,7 +121,7 @@ resource "kubernetes_deployment" "qbittorrent" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } volume { @@ -172,6 +197,192 @@ resource "kubernetes_service" "qbittorrent-torrenting" { } +resource "kubernetes_cron_job_v1" "qbittorrent_ratio_monitor" { + metadata { + name = "qbittorrent-ratio-monitor" + namespace = "servarr" + } + spec { + concurrency_policy = "Replace" + failed_jobs_history_limit = 3 + successful_jobs_history_limit = 3 + schedule = "*/5 * * * *" + job_template { + metadata {} + spec { + backoff_limit = 2 + ttl_seconds_after_finished = 300 + template { + metadata {} + spec { + container { + name = "ratio-monitor" + image = "docker.io/library/python:3.12-alpine" + command = ["/bin/sh", "-c", "set -euo pipefail; pip install -q requests > /dev/null 2>&1; python3 /tmp/monitor.py"] + volume_mount { + name = "script" + mount_path = "/tmp/monitor.py" + sub_path = "monitor.py" + } + resources { + requests = { + memory = "64Mi" + cpu = "10m" + } + limits = { + memory = "128Mi" + } + } + } + volume { + name = "script" + config_map { + name = kubernetes_config_map.ratio_monitor_script.metadata[0].name + } + } + dns_config { + option { + name = "ndots" + value = "2" + } + } + } + } + } + } + } +} + +resource "kubernetes_config_map" "ratio_monitor_script" { + metadata { + name = "qbt-ratio-monitor-script" + namespace = "servarr" + } + data = { + "monitor.py" = <<-PYEOF +import requests, json, sys +from collections import defaultdict +from urllib.parse import urlparse + +QB_URL = "http://qbittorrent.servarr.svc.cluster.local" +PUSHGW = "http://prometheus-prometheus-pushgateway.monitoring:9091" + +try: + torrents = requests.get(f"{QB_URL}/api/v2/torrents/info", timeout=10).json() +except Exception as e: + print(f"ERROR: {e}", file=sys.stderr) + sys.exit(1) + +try: + transfer = requests.get(f"{QB_URL}/api/v2/transfer/info", timeout=10).json() +except Exception: + transfer = {} + +tracker_stats = defaultdict(lambda: { + "uploaded": 0, "downloaded": 0, "size": 0, + "count": 0, "seeding": 0, "downloading": 0, + "seed_time_total": 0, "unsatisfied": 0 +}) + +for t in torrents: + tracker_url = t.get("tracker", "") + if not tracker_url: + domain = "unknown" + else: + try: + domain = urlparse(tracker_url).hostname or "unknown" + except Exception: + domain = "unknown" + + if "myanonamouse" in domain or "mam" in domain.lower(): + label = "mam" + elif "audiobookbay" in domain or "abb" in domain.lower(): + label = "audiobookbay" + else: + label = domain.replace(".", "_") + + s = tracker_stats[label] + s["uploaded"] += t.get("uploaded", 0) + s["downloaded"] += t.get("downloaded", 0) + s["size"] += t.get("size", 0) + s["count"] += 1 + s["seed_time_total"] += t.get("seeding_time", 0) + + state = t.get("state", "") + if state in ("uploading", "stalledUP", "forcedUP", "queuedUP"): + s["seeding"] += 1 + elif state in ("downloading", "stalledDL", "forcedDL", "queuedDL"): + s["downloading"] += 1 + + if t.get("seeding_time", 0) < 259200 and t.get("progress", 0) >= 1.0: + s["unsatisfied"] += 1 + +for tracker, stats in tracker_stats.items(): + dl = stats["downloaded"] + ul = stats["uploaded"] + ratio = ul / dl if dl > 0 else 0.0 + + metrics = f"""# HELP qbt_tracker_uploaded_bytes Total bytes uploaded for tracker +# TYPE qbt_tracker_uploaded_bytes gauge +qbt_tracker_uploaded_bytes {ul} +# HELP qbt_tracker_downloaded_bytes Total bytes downloaded for tracker +# TYPE qbt_tracker_downloaded_bytes gauge +qbt_tracker_downloaded_bytes {dl} +# HELP qbt_tracker_ratio Upload/download ratio for tracker +# TYPE qbt_tracker_ratio gauge +qbt_tracker_ratio {ratio:.4f} +# HELP qbt_tracker_torrents_total Total torrents for tracker +# TYPE qbt_tracker_torrents_total gauge +qbt_tracker_torrents_total {stats['count']} +# HELP qbt_tracker_seeding Torrents currently seeding +# TYPE qbt_tracker_seeding gauge +qbt_tracker_seeding {stats['seeding']} +# HELP qbt_tracker_downloading Torrents currently downloading +# TYPE qbt_tracker_downloading gauge +qbt_tracker_downloading {stats['downloading']} +# HELP qbt_tracker_seed_time_total_seconds Total seed time across all torrents +# TYPE qbt_tracker_seed_time_total_seconds gauge +qbt_tracker_seed_time_total_seconds {stats['seed_time_total']} +# HELP qbt_tracker_unsatisfied Torrents not yet seeded 72h +# TYPE qbt_tracker_unsatisfied gauge +qbt_tracker_unsatisfied {stats['unsatisfied']} +# HELP qbt_tracker_size_bytes Total size of all torrents +# TYPE qbt_tracker_size_bytes gauge +qbt_tracker_size_bytes {stats['size']} +""" + resp = requests.post( + f"{PUSHGW}/metrics/job/qbt-ratio-monitor/tracker/{tracker}", + data=metrics, timeout=10 + ) + print(f"Tracker {tracker}: ratio={ratio:.3f} ul={ul} dl={dl} count={stats['count']} seeding={stats['seeding']} unsatisfied={stats['unsatisfied']} -> {resp.status_code}") + +connected = 1 if transfer.get("connection_status") == "connected" else 0 +dht = transfer.get("dht_nodes", 0) +dl_speed = transfer.get("dl_info_speed", 0) +ul_speed = transfer.get("up_info_speed", 0) + +global_metrics = f"""# HELP qbt_connected Whether qBittorrent is connected +# TYPE qbt_connected gauge +qbt_connected {connected} +# HELP qbt_dht_nodes Number of DHT nodes +# TYPE qbt_dht_nodes gauge +qbt_dht_nodes {dht} +# HELP qbt_dl_speed_bytes Current download speed +# TYPE qbt_dl_speed_bytes gauge +qbt_dl_speed_bytes {dl_speed} +# HELP qbt_ul_speed_bytes Current upload speed +# TYPE qbt_ul_speed_bytes gauge +qbt_ul_speed_bytes {ul_speed} +""" +resp = requests.post( + f"{PUSHGW}/metrics/job/qbt-ratio-monitor/tracker/global", + data=global_metrics, timeout=10 +) +print(f"Global: connected={connected} dht={dht} dl_speed={dl_speed} ul_speed={ul_speed} -> {resp.status_code}") + PYEOF + } +} + module "ingress" { source = "../../../modules/kubernetes/ingress_factory" namespace = "servarr" diff --git a/stacks/speedtest/main.tf b/stacks/speedtest/main.tf index 8c9ea0b2..b17cf876 100644 --- a/stacks/speedtest/main.tf +++ b/stacks/speedtest/main.tf @@ -61,6 +61,28 @@ module "nfs_config" { nfs_path = "/mnt/main/speedtest" } +resource "kubernetes_persistent_volume_claim" "config_proxmox" { + wait_until_bound = false + metadata { + name = "speedtest-config-proxmox" + namespace = kubernetes_namespace.speedtest.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "speedtest" { metadata { name = "speedtest" @@ -75,6 +97,9 @@ resource "kubernetes_deployment" "speedtest" { } spec { replicas = 1 + strategy { + type = "Recreate" + } selector { match_labels = { app = "speedtest" @@ -171,7 +196,7 @@ resource "kubernetes_deployment" "speedtest" { volume { name = "config" persistent_volume_claim { - claim_name = module.nfs_config.claim_name + claim_name = kubernetes_persistent_volume_claim.config_proxmox.metadata[0].name } } } diff --git a/stacks/stirling-pdf/main.tf b/stacks/stirling-pdf/main.tf index 86fa258f..41b4f636 100644 --- a/stacks/stirling-pdf/main.tf +++ b/stacks/stirling-pdf/main.tf @@ -29,6 +29,28 @@ module "nfs_configs" { nfs_path = "/mnt/main/stirling-pdf" } +resource "kubernetes_persistent_volume_claim" "configs_proxmox" { + wait_until_bound = false + metadata { + name = "stirling-pdf-configs-proxmox" + namespace = kubernetes_namespace.stirling-pdf.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "stirling-pdf" { metadata { name = "stirling-pdf" @@ -79,7 +101,7 @@ resource "kubernetes_deployment" "stirling-pdf" { volume { name = "configs" persistent_volume_claim { - claim_name = module.nfs_configs.claim_name + claim_name = kubernetes_persistent_volume_claim.configs_proxmox.metadata[0].name } } } diff --git a/stacks/tandoor/main.tf b/stacks/tandoor/main.tf index 308cb111..141df7c6 100644 --- a/stacks/tandoor/main.tf +++ b/stacks/tandoor/main.tf @@ -62,6 +62,28 @@ module "nfs_data" { nfs_path = "/mnt/main/tandoor" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "tandoor-data-proxmox" + namespace = kubernetes_namespace.tandoor.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "tandoor" { metadata { name = "tandoor" @@ -201,7 +223,7 @@ resource "kubernetes_deployment" "tandoor" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } diff --git a/stacks/technitium/modules/technitium/main.tf b/stacks/technitium/modules/technitium/main.tf index d1f05412..41a9934c 100644 --- a/stacks/technitium/modules/technitium/main.tf +++ b/stacks/technitium/modules/technitium/main.tf @@ -92,6 +92,28 @@ module "nfs_config" { nfs_path = "/mnt/main/technitium" } +resource "kubernetes_persistent_volume_claim" "config_proxmox" { + wait_until_bound = false + metadata { + name = "technitium-config-proxmox" + namespace = kubernetes_namespace.technitium.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "technitium" { # resource "kubernetes_daemonset" "technitium" { metadata { @@ -104,11 +126,7 @@ resource "kubernetes_deployment" "technitium" { } spec { strategy { - type = "RollingUpdate" - rolling_update { - max_unavailable = "0" - max_surge = "1" - } + type = "Recreate" } # replicas = 1 selector { @@ -207,7 +225,7 @@ resource "kubernetes_deployment" "technitium" { volume { name = "nfs-config" persistent_volume_claim { - claim_name = module.nfs_config.claim_name + claim_name = kubernetes_persistent_volume_claim.config_proxmox.metadata[0].name } } volume { diff --git a/stacks/tor-proxy/main.tf b/stacks/tor-proxy/main.tf index 623ea4cb..59237cca 100644 --- a/stacks/tor-proxy/main.tf +++ b/stacks/tor-proxy/main.tf @@ -137,6 +137,28 @@ module "nfs_torrserver_data" { nfs_path = "/mnt/main/tor-proxy/torrserver" } +resource "kubernetes_persistent_volume_claim" "torrserver_data_proxmox" { + wait_until_bound = false + metadata { + name = "tor-proxy-torrserver-data-proxmox" + namespace = kubernetes_namespace.tor-proxy.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "torrserver" { metadata { name = "torrserver" @@ -204,7 +226,7 @@ resource "kubernetes_deployment" "torrserver" { volume { name = "torrserver-data" persistent_volume_claim { - claim_name = module.nfs_torrserver_data.claim_name + claim_name = kubernetes_persistent_volume_claim.torrserver_data_proxmox.metadata[0].name } } } diff --git a/stacks/whisper/main.tf b/stacks/whisper/main.tf index 4d98346e..cb9235d4 100644 --- a/stacks/whisper/main.tf +++ b/stacks/whisper/main.tf @@ -28,6 +28,28 @@ module "nfs_data" { nfs_path = "/mnt/main/whisper" } +resource "kubernetes_persistent_volume_claim" "data_proxmox" { + wait_until_bound = false + metadata { + name = "whisper-data-proxmox" + namespace = kubernetes_namespace.whisper.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_deployment" "whisper" { metadata { name = "whisper" @@ -92,7 +114,7 @@ resource "kubernetes_deployment" "whisper" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } } @@ -210,7 +232,7 @@ resource "kubernetes_deployment" "piper" { volume { name = "data" persistent_volume_claim { - claim_name = module.nfs_data.claim_name + claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } }