From 2d919c4d34e544fbdc0748b1b32c35ca408a87bb Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Mon, 23 Feb 2026 22:05:28 +0000 Subject: [PATCH] [ci skip] Infrastructure hardening: security, monitoring, reliability, maintainability Phase 1 - Critical Security: - Netbox: move hardcoded DB/superuser passwords to variables - MeshCentral: disable public registration, add Authentik auth - Traefik: disable insecure API dashboard (api.insecure=false) - Traefik: configure forwarded headers with Cloudflare trusted IPs Phase 2 - Security Hardening: - Add security headers middleware (HSTS, X-Frame-Options, nosniff, etc.) - Add Kyverno pod security policies in audit mode (privileged, host namespaces, SYS_ADMIN, trusted registries) - Tighten rate limiting (avg=10, burst=50) - Add Authentik protection to grampsweb Phase 3 - Monitoring & Alerting: - Add critical service alerts (PostgreSQL, MySQL, Redis, Headscale, Authentik, Loki) - Increase Loki retention from 7 to 30 days (720h) - Add predictive PV filling alert (predict_linear) - Re-enable Hackmd and Privatebin down alerts Phase 4 - Reliability: - Add resource requests/limits to Redis, DBaaS, Technitium, Headscale, Vaultwarden, Uptime Kuma - Increase Alloy DaemonSet memory to 512Mi/1Gi Phase 6 - Maintainability: - Extract duplicated tiers locals to terragrunt.hcl generate block (removed from 67 stacks) - Replace hardcoded NFS IP 10.0.10.15 with var.nfs_server (114 instances across 63 files) - Replace hardcoded Redis/PostgreSQL/MySQL/Ollama/mail host references with variables across ~35 stacks - Migrate xray raw ingress resources to ingress_factory modules --- stacks/actualbudget/factory/main.tf | 3 +- stacks/actualbudget/main.tf | 9 - stacks/affine/main.tf | 21 +- stacks/audiobookshelf/main.tf | 18 +- stacks/blog/main.tf | 9 - stacks/calibre/main.tf | 22 +- stacks/changedetection/main.tf | 12 +- stacks/city-guesser/main.tf | 9 - stacks/coturn/main.tf | 9 - stacks/cyberchef/main.tf | 9 - stacks/dashy/main.tf | 9 - stacks/dawarich/main.tf | 18 +- stacks/diun/main.tf | 12 +- stacks/ebook2audiobook/main.tf | 18 +- stacks/echo/main.tf | 9 - stacks/excalidraw/main.tf | 12 +- stacks/f1-stream/main.tf | 12 +- stacks/forgejo/main.tf | 12 +- stacks/freedify/main.tf | 9 - stacks/freshrss/main.tf | 14 +- stacks/frigate/main.tf | 14 +- stacks/grampsweb/main.tf | 26 +-- stacks/hackmd/main.tf | 15 +- stacks/health/main.tf | 15 +- stacks/homepage/main.tf | 9 - stacks/immich/main.tf | 33 ++- stacks/isponsorblocktv/main.tf | 12 +- stacks/jsoncrack/main.tf | 9 - stacks/k8s-dashboard/main.tf | 9 - stacks/kms/main.tf | 9 - stacks/linkwarden/main.tf | 12 +- stacks/matrix/main.tf | 12 +- stacks/meshcentral/main.tf | 19 +- stacks/n8n/main.tf | 15 +- stacks/navidrome/main.tf | 14 +- stacks/netbox/main.tf | 24 +-- stacks/networking-toolbox/main.tf | 9 - stacks/nextcloud/chart_values.yaml | 4 +- stacks/nextcloud/main.tf | 20 +- stacks/ntfy/main.tf | 12 +- stacks/ollama/main.tf | 19 +- stacks/onlyoffice/main.tf | 18 +- stacks/openclaw/main.tf | 17 +- stacks/osm_routing/main.tf | 16 +- stacks/owntracks/main.tf | 12 +- stacks/paperless-ngx/main.tf | 18 +- stacks/platform/main.tf | 38 ++-- stacks/platform/modules/authentik/main.tf | 3 +- stacks/platform/modules/authentik/values.yaml | 2 +- stacks/platform/modules/crowdsec/main.tf | 3 +- stacks/platform/modules/crowdsec/values.yaml | 4 +- stacks/platform/modules/dbaas/main.tf | 35 ++- stacks/platform/modules/headscale/main.tf | 27 ++- .../modules/infra-maintenance/main.tf | 3 +- .../modules/kyverno/security-policies.tf | 203 ++++++++++++++++++ stacks/platform/modules/mailserver/main.tf | 5 +- .../modules/mailserver/roundcubemail.tf | 7 +- stacks/platform/modules/monitoring/alloy.yaml | 4 +- stacks/platform/modules/monitoring/grafana.tf | 7 +- .../monitoring/grafana_chart_values.yaml | 2 +- stacks/platform/modules/monitoring/loki.tf | 4 +- stacks/platform/modules/monitoring/loki.yaml | 2 +- stacks/platform/modules/monitoring/main.tf | 1 + .../platform/modules/monitoring/prometheus.tf | 3 +- .../monitoring/prometheus_chart_values.tpl | 79 +++++-- stacks/platform/modules/nvidia/main.tf | 2 +- stacks/platform/modules/redis/main.tf | 14 +- stacks/platform/modules/technitium/main.tf | 22 +- stacks/platform/modules/traefik/main.tf | 9 +- stacks/platform/modules/traefik/middleware.tf | 29 ++- stacks/platform/modules/uptime-kuma/main.tf | 16 +- stacks/platform/modules/vaultwarden/main.tf | 18 +- stacks/platform/modules/xray/main.tf | 129 +++-------- stacks/plotting-book/main.tf | 9 - stacks/poison-fountain/main.tf | 14 +- stacks/privatebin/main.tf | 12 +- stacks/real-estate-crawler/main.tf | 60 ++++-- stacks/reloader/main.tf | 10 - stacks/resume/main.tf | 15 +- stacks/rybbit/main.tf | 15 +- stacks/send/main.tf | 15 +- stacks/servarr/aiostreams/main.tf | 3 +- stacks/servarr/lidarr/main.tf | 7 +- stacks/servarr/listenarr/main.tf | 5 +- stacks/servarr/main.tf | 9 - stacks/servarr/prowlarr/main.tf | 5 +- stacks/servarr/qbittorrent/main.tf | 5 +- stacks/servarr/readarr/main.tf | 5 +- stacks/servarr/soulseek/main.tf | 5 +- stacks/shadowsocks/main.tf | 9 - stacks/speedtest/main.tf | 15 +- stacks/stirling-pdf/main.tf | 12 +- stacks/tandoor/main.tf | 18 +- stacks/tor-proxy/main.tf | 9 - stacks/travel_blog/main.tf | 9 - stacks/tuya-bridge/main.tf | 9 - stacks/url/main.tf | 12 +- stacks/wealthfolio/main.tf | 12 +- stacks/webhook_handler/main.tf | 9 - stacks/whisper/main.tf | 14 +- stacks/woodpecker/main.tf | 22 +- stacks/woodpecker/values.yaml | 2 +- stacks/ytdlp/main.tf | 20 +- terraform.tfvars | Bin 48755 -> 49184 bytes terragrunt.hcl | 18 ++ 105 files changed, 773 insertions(+), 920 deletions(-) create mode 100644 stacks/platform/modules/kyverno/security-policies.tf diff --git a/stacks/actualbudget/factory/main.tf b/stacks/actualbudget/factory/main.tf index 35d2e722..6728f548 100644 --- a/stacks/actualbudget/factory/main.tf +++ b/stacks/actualbudget/factory/main.tf @@ -12,6 +12,7 @@ variable "budget_encryption_password" { type = string default = null # If not passed, we won't run banksync ;known after initial installation } +variable "nfs_server" { type = string } resource "kubernetes_deployment" "actualbudget" { metadata { @@ -59,7 +60,7 @@ resource "kubernetes_deployment" "actualbudget" { name = "data" nfs { path = "/mnt/main/actualbudget/${var.name}" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/actualbudget/main.tf b/stacks/actualbudget/main.tf index a9a99446..40339b02 100644 --- a/stacks/actualbudget/main.tf +++ b/stacks/actualbudget/main.tf @@ -1,15 +1,6 @@ variable "tls_secret_name" { type = string } variable "actualbudget_credentials" { type = map(any) } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} # To create a new deployment: /** diff --git a/stacks/affine/main.tf b/stacks/affine/main.tf index 07406113..0e5c3742 100644 --- a/stacks/affine/main.tf +++ b/stacks/affine/main.tf @@ -1,16 +1,11 @@ variable "tls_secret_name" { type = string } variable "affine_postgresql_password" { type = string } variable "mailserver_accounts" { type = map(any) } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "postgresql_host" { type = string } +variable "mail_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "affine" { metadata { @@ -31,11 +26,11 @@ locals { common_env = [ { name = "DATABASE_URL" - value = "postgresql://affine:${var.affine_postgresql_password}@postgresql.dbaas.svc.cluster.local:5432/affine" + value = "postgresql://affine:${var.affine_postgresql_password}@${var.postgresql_host}:5432/affine" }, { name = "REDIS_SERVER_HOST" - value = "redis.redis.svc.cluster.local" + value = var.redis_host }, { name = "AFFINE_INDEXER_ENABLED" @@ -57,7 +52,7 @@ locals { # Email/SMTP configuration { name = "MAILER_HOST" - value = "mailserver.viktorbarzin.me" + value = var.mail_host }, { name = "MAILER_PORT" @@ -187,7 +182,7 @@ resource "kubernetes_deployment" "affine" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/affine" } } diff --git a/stacks/audiobookshelf/main.tf b/stacks/audiobookshelf/main.tf index b3015430..04d77295 100644 --- a/stacks/audiobookshelf/main.tf +++ b/stacks/audiobookshelf/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "audiobookshelf" { metadata { @@ -83,28 +75,28 @@ resource "kubernetes_deployment" "audiobookshelf" { name = "audiobooks" nfs { path = "/mnt/main/audiobookshelf/audiobooks" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "podcasts" nfs { path = "/mnt/main/audiobookshelf/podcasts" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "config" nfs { path = "/mnt/main/audiobookshelf/config" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "metadata" nfs { path = "/mnt/main/audiobookshelf/metadata" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/blog/main.tf b/stacks/blog/main.tf index 0235cb53..018eff14 100644 --- a/stacks/blog/main.tf +++ b/stacks/blog/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "website" { metadata { diff --git a/stacks/calibre/main.tf b/stacks/calibre/main.tf index b3f26691..d330925a 100644 --- a/stacks/calibre/main.tf +++ b/stacks/calibre/main.tf @@ -1,15 +1,7 @@ variable "tls_secret_name" { type = string } variable "homepage_credentials" { type = map(any) } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "calibre" { metadata { @@ -94,7 +86,7 @@ module "tls_secret" { # name = "data" # nfs { # path = "/mnt/main/calibre" -# server = "10.0.10.15" +# server = var.nfs_server # } # } # } @@ -181,21 +173,21 @@ resource "kubernetes_deployment" "calibre-web-automated" { name = "library" nfs { path = "/mnt/main/calibre-web-automated/calibre-library" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "config" nfs { path = "/mnt/main/calibre-web-automated/config" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "ingest" nfs { path = "/mnt/main/calibre-web-automated/cwa-book-ingest" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -292,14 +284,14 @@ resource "kubernetes_deployment" "annas-archive-stacks" { name = "config" nfs { path = "/mnt/main/calibre-web-automated/stacks" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "ingest" nfs { path = "/mnt/main/calibre-web-automated/cwa-book-ingest" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/changedetection/main.tf b/stacks/changedetection/main.tf index b05307ed..76f1fe15 100644 --- a/stacks/changedetection/main.tf +++ b/stacks/changedetection/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "changedetection" { metadata { @@ -104,7 +96,7 @@ resource "kubernetes_deployment" "changedetection" { name = "data" nfs { path = "/mnt/main/changedetection" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/city-guesser/main.tf b/stacks/city-guesser/main.tf index cd402610..695e1f08 100644 --- a/stacks/city-guesser/main.tf +++ b/stacks/city-guesser/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "city-guesser" { metadata { diff --git a/stacks/coturn/main.tf b/stacks/coturn/main.tf index 4085dabc..511ca0f3 100644 --- a/stacks/coturn/main.tf +++ b/stacks/coturn/main.tf @@ -2,15 +2,6 @@ variable "tls_secret_name" { type = string } variable "coturn_turn_secret" { type = string } variable "public_ip" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} locals { turn_realm = "viktorbarzin.me" diff --git a/stacks/cyberchef/main.tf b/stacks/cyberchef/main.tf index 4fcd450c..225c5454 100644 --- a/stacks/cyberchef/main.tf +++ b/stacks/cyberchef/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "cyberchef" { metadata { diff --git a/stacks/dashy/main.tf b/stacks/dashy/main.tf index 8b9bbbe5..1830b515 100644 --- a/stacks/dashy/main.tf +++ b/stacks/dashy/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" diff --git a/stacks/dawarich/main.tf b/stacks/dawarich/main.tf index 1a3e351b..f63e65c7 100644 --- a/stacks/dawarich/main.tf +++ b/stacks/dawarich/main.tf @@ -2,20 +2,14 @@ variable "tls_secret_name" { type = string } variable "dawarich_database_password" { type = string } variable "geoapify_api_key" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} variable "image_version" { type = string default = "0.37.1" } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "postgresql_host" { type = string } resource "kubernetes_namespace" "dawarich" { metadata { @@ -82,11 +76,11 @@ resource "kubernetes_deployment" "dawarich" { args = ["bin/dev"] env { name = "REDIS_URL" - value = "redis://redis.redis.svc.cluster.local:6379" + value = "redis://${var.redis_host}:6379" } env { name = "DATABASE_HOST" - value = "postgresql.dbaas" + value = var.postgresql_host } env { name = "DATABASE_USERNAME" @@ -272,7 +266,7 @@ resource "kubernetes_deployment" "dawarich" { # name = "data" # nfs { # path = "/mnt/main/photon" -# server = "10.0.10.15" +# server = var.nfs_server # } # } # } diff --git a/stacks/diun/main.tf b/stacks/diun/main.tf index 8f0d2d1b..f756b708 100644 --- a/stacks/diun/main.tf +++ b/stacks/diun/main.tf @@ -1,16 +1,8 @@ variable "tls_secret_name" { type = string } variable "diun_nfty_token" { type = string } variable "diun_slack_url" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "diun" { metadata { @@ -176,7 +168,7 @@ resource "kubernetes_deployment" "diun" { name = "data" nfs { path = "/mnt/main/diun" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/ebook2audiobook/main.tf b/stacks/ebook2audiobook/main.tf index 8fd9864d..32914d99 100644 --- a/stacks/ebook2audiobook/main.tf +++ b/stacks/ebook2audiobook/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" @@ -98,7 +90,7 @@ resource "kubernetes_deployment" "ebook2audiobook" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/ebook2audiobook" } } @@ -199,7 +191,7 @@ resource "kubernetes_service" "ebook2audiobook" { # volume { # name = "data" # nfs { -# server = "10.0.10.15" +# server = var.nfs_server # path = "/mnt/main/piper" # } # } @@ -288,7 +280,7 @@ resource "kubernetes_deployment" "audiblez" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/audiblez" } } @@ -376,7 +368,7 @@ resource "kubernetes_deployment" "audiblez-web" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/audiblez" } } diff --git a/stacks/echo/main.tf b/stacks/echo/main.tf index cfc98271..0d6ff2d0 100644 --- a/stacks/echo/main.tf +++ b/stacks/echo/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "echo" { metadata { diff --git a/stacks/excalidraw/main.tf b/stacks/excalidraw/main.tf index f13d8039..39de7e27 100644 --- a/stacks/excalidraw/main.tf +++ b/stacks/excalidraw/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "excalidraw" { metadata { @@ -77,7 +69,7 @@ resource "kubernetes_deployment" "excalidraw" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/excalidraw" } } diff --git a/stacks/f1-stream/main.tf b/stacks/f1-stream/main.tf index 27650931..8216a509 100644 --- a/stacks/f1-stream/main.tf +++ b/stacks/f1-stream/main.tf @@ -1,16 +1,8 @@ variable "tls_secret_name" { type = string } variable "coturn_turn_secret" { type = string } variable "public_ip" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "f1-stream" { metadata { @@ -97,7 +89,7 @@ resource "kubernetes_deployment" "f1-stream" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/f1-stream" } } diff --git a/stacks/forgejo/main.tf b/stacks/forgejo/main.tf index 1fbc0cad..5852d346 100644 --- a/stacks/forgejo/main.tf +++ b/stacks/forgejo/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "forgejo" { metadata { @@ -77,7 +69,7 @@ resource "kubernetes_deployment" "forgejo" { name = "data" nfs { path = "/mnt/main/forgejo" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/freedify/main.tf b/stacks/freedify/main.tf index 1ab6ffe6..2a319de6 100644 --- a/stacks/freedify/main.tf +++ b/stacks/freedify/main.tf @@ -1,15 +1,6 @@ variable "tls_secret_name" { type = string } variable "freedify_credentials" { type = map(any) } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} # To create a new deployment: /** diff --git a/stacks/freshrss/main.tf b/stacks/freshrss/main.tf index 8ba17aa6..8fdd3fcb 100644 --- a/stacks/freshrss/main.tf +++ b/stacks/freshrss/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" @@ -88,14 +80,14 @@ resource "kubernetes_deployment" "freshrss" { name = "data" nfs { path = "/mnt/main/freshrss/data" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "extensions" nfs { path = "/mnt/main/freshrss/extensions" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/frigate/main.tf b/stacks/frigate/main.tf index 264850d8..8e476044 100644 --- a/stacks/frigate/main.tf +++ b/stacks/frigate/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "frigate" { metadata { @@ -120,7 +112,7 @@ resource "kubernetes_deployment" "frigate" { name = "config" nfs { path = "/mnt/main/frigate/config" - server = "10.0.10.15" + server = var.nfs_server } } volume { @@ -134,7 +126,7 @@ resource "kubernetes_deployment" "frigate" { name = "media" nfs { path = "/mnt/main/frigate/media" - server = "10.0.10.15" + server = var.nfs_server } } volume { diff --git a/stacks/grampsweb/main.tf b/stacks/grampsweb/main.tf index f4e827fb..3c817b04 100644 --- a/stacks/grampsweb/main.tf +++ b/stacks/grampsweb/main.tf @@ -1,15 +1,10 @@ variable "tls_secret_name" { type = string } variable "mailserver_accounts" { type = map(any) } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "ollama_host" { type = string } +variable "mail_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "grampsweb" { metadata { @@ -43,15 +38,15 @@ locals { }, { name = "GRAMPSWEB_CELERY_CONFIG__broker_url" - value = "redis://redis.redis.svc.cluster.local:6379/2" + value = "redis://${var.redis_host}:6379/2" }, { name = "GRAMPSWEB_CELERY_CONFIG__result_backend" - value = "redis://redis.redis.svc.cluster.local:6379/2" + value = "redis://${var.redis_host}:6379/2" }, { name = "GRAMPSWEB_RATELIMIT_STORAGE_URI" - value = "redis://redis.redis.svc.cluster.local:6379/3" + value = "redis://${var.redis_host}:6379/3" }, { name = "GRAMPSWEB_BASE_URL" @@ -63,7 +58,7 @@ locals { }, { name = "GRAMPSWEB_EMAIL_HOST" - value = "mail.viktorbarzin.me" + value = var.mail_host }, { name = "GRAMPSWEB_EMAIL_PORT" @@ -91,7 +86,7 @@ locals { }, { name = "GRAMPSWEB_LLM_BASE_URL" - value = "http://ollama.ollama.svc.cluster.local:11434/v1" + value = "http://${var.ollama_host}:11434/v1" }, { name = "GRAMPSWEB_LLM_MODEL" @@ -239,7 +234,7 @@ resource "kubernetes_deployment" "grampsweb" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/grampsweb" } } @@ -276,4 +271,5 @@ module "ingress" { service_name = "grampsweb" tls_secret_name = var.tls_secret_name max_body_size = "500m" + protected = true } diff --git a/stacks/hackmd/main.tf b/stacks/hackmd/main.tf index fb5f8d86..b026eb80 100644 --- a/stacks/hackmd/main.tf +++ b/stacks/hackmd/main.tf @@ -1,15 +1,8 @@ variable "hackmd_db_password" { type = string } variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "hackmd" { metadata { @@ -97,7 +90,7 @@ resource "kubernetes_deployment" "hackmd" { env { name = "CMD_DB_URL" # value = format("%s%s%s", "postgres://codimd:", var.hackmd_db_password, "@localhost/codimd") - value = format("%s%s%s", "mysql://codimd:", var.hackmd_db_password, "@mysql.dbaas/codimd") + value = format("%s%s%s", "mysql://codimd:", var.hackmd_db_password, "@${var.mysql_host}/codimd") } env { name = "CMD_USECDN" @@ -121,7 +114,7 @@ resource "kubernetes_deployment" "hackmd" { name = "data" nfs { path = "/mnt/main/hackmd" - server = "10.0.10.15" + server = var.nfs_server } # iscsi { # target_portal = "iscsi.viktorbarzin.lan:3260" diff --git a/stacks/health/main.tf b/stacks/health/main.tf index 428a865e..4e8c93fd 100644 --- a/stacks/health/main.tf +++ b/stacks/health/main.tf @@ -1,16 +1,9 @@ variable "tls_secret_name" { type = string } variable "health_postgresql_password" { type = string } variable "health_secret_key" { type = string } +variable "nfs_server" { type = string } +variable "postgresql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "health" { metadata { @@ -60,7 +53,7 @@ resource "kubernetes_deployment" "health" { env { name = "DATABASE_URL" - value = "postgresql+asyncpg://health:${var.health_postgresql_password}@postgresql.dbaas.svc.cluster.local:5432/health" + value = "postgresql+asyncpg://health:${var.health_postgresql_password}@${var.postgresql_host}:5432/health" } env { name = "SECRET_KEY" @@ -102,7 +95,7 @@ resource "kubernetes_deployment" "health" { volume { name = "uploads" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/health" } } diff --git a/stacks/homepage/main.tf b/stacks/homepage/main.tf index 107da13c..b88e6f53 100644 --- a/stacks/homepage/main.tf +++ b/stacks/homepage/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" diff --git a/stacks/immich/main.tf b/stacks/immich/main.tf index 4769ff40..2f05ec1e 100644 --- a/stacks/immich/main.tf +++ b/stacks/immich/main.tf @@ -3,21 +3,14 @@ variable "immich_postgresql_password" { type = string } variable "immich_frame_api_key" { type = string } variable "homepage_credentials" { type = map(any) } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} variable "immich_version" { type = string # Change me to upgrade default = "v2.5.6" } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } module "tls_secret" { @@ -104,7 +97,7 @@ resource "kubernetes_deployment" "immich_server" { } env { name = "REDIS_HOSTNAME" - value = "redis.redis.svc.cluster.local" + value = var.redis_host } liveness_probe { @@ -176,7 +169,7 @@ resource "kubernetes_deployment" "immich_server" { # volume { # name = "library-old" # nfs { - # server = "10.0.10.15" + # server = var.nfs_server # path = "/mnt/main/immich/immich/" # } # } @@ -184,42 +177,42 @@ resource "kubernetes_deployment" "immich_server" { volume { name = "backups" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/immich/immich/backups" } } volume { name = "encoded-video" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/immich/immich/encoded-video" } } volume { name = "library" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/immich/immich/library" } } volume { name = "profile" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/immich/immich/profile" } } volume { name = "thumbs" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/ssd/immich/thumbs" } } volume { name = "upload" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/immich/immich/upload" } } @@ -305,7 +298,7 @@ resource "kubernetes_deployment" "immich-postgres" { name = "postgresql-persistent-storage" nfs { path = "/mnt/main/immich/data-immich-postgresql" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -442,7 +435,7 @@ resource "kubernetes_deployment" "immich-machine-learning" { nfs { # path = "/mnt/main/immich/machine-learning" path = "/mnt/ssd/immich/machine-learning" # load cache from ssd - server = "10.0.10.15" + server = var.nfs_server } } } @@ -533,7 +526,7 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" { name = "postgresql-backup" nfs { path = "/mnt/main/immich/data-immich-postgresql" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/isponsorblocktv/main.tf b/stacks/isponsorblocktv/main.tf index 21b555ba..e5bff04d 100644 --- a/stacks/isponsorblocktv/main.tf +++ b/stacks/isponsorblocktv/main.tf @@ -1,12 +1,4 @@ -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} +variable "nfs_server" { type = string } resource "kubernetes_namespace" "isponsorblocktv" { metadata { @@ -55,7 +47,7 @@ resource "kubernetes_deployment" "isponsorblocktv-vermont" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/isponsorblocktv/vermont" } } diff --git a/stacks/jsoncrack/main.tf b/stacks/jsoncrack/main.tf index 16777af7..bbf573ac 100644 --- a/stacks/jsoncrack/main.tf +++ b/stacks/jsoncrack/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "jsoncrack" { metadata { diff --git a/stacks/k8s-dashboard/main.tf b/stacks/k8s-dashboard/main.tf index 0061837a..17915274 100644 --- a/stacks/k8s-dashboard/main.tf +++ b/stacks/k8s-dashboard/main.tf @@ -1,15 +1,6 @@ variable "tls_secret_name" { type = string } variable "client_certificate_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "random_password" "csrf_token" { length = 16 diff --git a/stacks/kms/main.tf b/stacks/kms/main.tf index f9a52f46..f79a5e3e 100644 --- a/stacks/kms/main.tf +++ b/stacks/kms/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "kms" { metadata { diff --git a/stacks/linkwarden/main.tf b/stacks/linkwarden/main.tf index 5cb025d7..10ccdfae 100644 --- a/stacks/linkwarden/main.tf +++ b/stacks/linkwarden/main.tf @@ -2,16 +2,8 @@ variable "tls_secret_name" { type = string } variable "linkwarden_postgresql_password" { type = string } variable "linkwarden_authentik_client_id" { type = string } variable "linkwarden_authentik_client_secret" { type = string } +variable "postgresql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "linkwarden" { metadata { @@ -73,7 +65,7 @@ resource "kubernetes_deployment" "linkwarden" { } env { name = "DATABASE_URL" - value = "postgresql://linkwarden:${var.linkwarden_postgresql_password}@postgresql.dbaas.svc.cluster.local:5432/linkwarden" + value = "postgresql://linkwarden:${var.linkwarden_postgresql_password}@${var.postgresql_host}:5432/linkwarden" } env { name = "NEXT_PUBLIC_AUTHENTIK_ENABLED" diff --git a/stacks/matrix/main.tf b/stacks/matrix/main.tf index 8e3c4087..7f172b11 100644 --- a/stacks/matrix/main.tf +++ b/stacks/matrix/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "matrix" { metadata { @@ -71,7 +63,7 @@ resource "kubernetes_deployment" "matrix" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/matrix" } } diff --git a/stacks/meshcentral/main.tf b/stacks/meshcentral/main.tf index 6ccd8bc9..ba777b51 100644 --- a/stacks/meshcentral/main.tf +++ b/stacks/meshcentral/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "meshcentral" { metadata { @@ -82,7 +74,7 @@ resource "kubernetes_deployment" "meshcentral" { } env { name = "ALLOW_NEW_ACCOUNTS" - value = "true" + value = "false" } env { name = "WEBRTC" @@ -106,21 +98,21 @@ resource "kubernetes_deployment" "meshcentral" { name = "data" nfs { path = "/mnt/main/meshcentral/meshcentral-data" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "files" nfs { path = "/mnt/main/meshcentral/meshcentral-files" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "backups" nfs { path = "/mnt/main/meshcentral/meshcentral-backups" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -156,4 +148,5 @@ module "ingress" { name = "meshcentral" tls_secret_name = var.tls_secret_name port = 443 + protected = true } diff --git a/stacks/n8n/main.tf b/stacks/n8n/main.tf index 9410d432..a5bd7411 100644 --- a/stacks/n8n/main.tf +++ b/stacks/n8n/main.tf @@ -1,15 +1,8 @@ variable "tls_secret_name" { type = string } variable "n8n_postgresql_password" { type = string } +variable "nfs_server" { type = string } +variable "postgresql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" @@ -62,7 +55,7 @@ resource "kubernetes_deployment" "n8n" { } env { name = "DB_POSTGRESDB_HOST" - value = "postgresql.dbaas" + value = var.postgresql_host } env { name = "DB_POSTGRESDB_PORT" @@ -114,7 +107,7 @@ resource "kubernetes_deployment" "n8n" { name = "data" nfs { path = "/mnt/main/n8n" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/navidrome/main.tf b/stacks/navidrome/main.tf index 5927ed12..e48300ec 100644 --- a/stacks/navidrome/main.tf +++ b/stacks/navidrome/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "navidrome" { metadata { @@ -79,7 +71,7 @@ resource "kubernetes_deployment" "navidrome" { name = "data" nfs { path = "/mnt/main/navidrome" - server = "10.0.10.15" + server = var.nfs_server } } volume { @@ -93,7 +85,7 @@ resource "kubernetes_deployment" "navidrome" { name = "lidarr" nfs { path = "/mnt/main/servarr/lidarr" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/netbox/main.tf b/stacks/netbox/main.tf index 1796fdd8..457ab7e0 100644 --- a/stacks/netbox/main.tf +++ b/stacks/netbox/main.tf @@ -1,14 +1,10 @@ variable "tls_secret_name" { type = string } +variable "netbox_db_password" { type = string } +variable "netbox_superuser_password" { type = string } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "postgresql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "netbox" { metadata { @@ -75,11 +71,11 @@ resource "kubernetes_deployment" "netbox" { } env { name = "DB_PASSWORD" - value = "ttPSBjF9oPLb49XZst3sGF" + value = var.netbox_db_password } env { name = "DB_HOST" - value = "postgresql.dbaas.svc.cluster.local" + value = var.postgresql_host } env { name = "DB_NAME" @@ -99,7 +95,7 @@ resource "kubernetes_deployment" "netbox" { } env { name = "REDIS_HOST" - value = "redis.redis" + value = var.redis_host } env { name = "ALLOWED_HOST" @@ -111,7 +107,7 @@ resource "kubernetes_deployment" "netbox" { } env { name = "SUPERUSER_PASSWORD" - value = "ttPSBjF9oPLb49XZst3sGFasdf" + value = var.netbox_superuser_password } env { name = "REMOTE_AUTH_ENABLED" @@ -147,7 +143,7 @@ resource "kubernetes_deployment" "netbox" { # name = "data" # nfs { # path = "/mnt/main/netbox" - # server = "10.0.10.15" + # server = var.nfs_server # } # } } diff --git a/stacks/networking-toolbox/main.tf b/stacks/networking-toolbox/main.tf index c4744cc3..0c129dcb 100644 --- a/stacks/networking-toolbox/main.tf +++ b/stacks/networking-toolbox/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "networking-toolbox" { metadata { diff --git a/stacks/nextcloud/chart_values.yaml b/stacks/nextcloud/chart_values.yaml index cda04812..2825c518 100644 --- a/stacks/nextcloud/chart_values.yaml +++ b/stacks/nextcloud/chart_values.yaml @@ -28,13 +28,13 @@ nextcloud: externalRedis: enabled: true - host: redis.redis.svc.cluster.local + host: ${redis_host} # Currently not in use; we use the nextcloud.db sqlite3 externalDatabase: enabled: false type: mysql - host: mysql.dbaas.svc.cluster.local + host: ${mysql_host} user: nextcloud password: ${db_password} databse: nextcloud diff --git a/stacks/nextcloud/main.tf b/stacks/nextcloud/main.tf index b8a07e4e..b3c1a757 100644 --- a/stacks/nextcloud/main.tf +++ b/stacks/nextcloud/main.tf @@ -1,15 +1,9 @@ variable "tls_secret_name" { type = string } variable "nextcloud_db_password" { type = string } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" @@ -36,7 +30,7 @@ resource "helm_release" "nextcloud" { atomic = true version = "8.8.1" - values = [templatefile("${path.module}/chart_values.yaml", { tls_secret_name = var.tls_secret_name, db_password = var.nextcloud_db_password })] + values = [templatefile("${path.module}/chart_values.yaml", { tls_secret_name = var.tls_secret_name, db_password = var.nextcloud_db_password, redis_host = var.redis_host, mysql_host = var.mysql_host })] timeout = 6000 } @@ -136,7 +130,7 @@ resource "kubernetes_persistent_volume" "nextcloud-data-pv" { persistent_volume_source { nfs { path = "/mnt/main/nextcloud" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -298,7 +292,7 @@ resource "kubernetes_cron_job_v1" "nextcloud-backup" { volume { name = "nextcloud-data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/nextcloud" } } @@ -306,7 +300,7 @@ resource "kubernetes_cron_job_v1" "nextcloud-backup" { volume { name = "backup" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/nextcloud-backup" } } diff --git a/stacks/ntfy/main.tf b/stacks/ntfy/main.tf index a01ebf84..1d556c9c 100644 --- a/stacks/ntfy/main.tf +++ b/stacks/ntfy/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "ntfy" { metadata { @@ -99,7 +91,7 @@ resource "kubernetes_deployment" "ntfy" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/ntfy" } } diff --git a/stacks/ollama/main.tf b/stacks/ollama/main.tf index b336bab0..e22075fb 100644 --- a/stacks/ollama/main.tf +++ b/stacks/ollama/main.tf @@ -1,15 +1,8 @@ variable "tls_secret_name" { type = string } variable "ollama_api_credentials" { type = map(string) } +variable "nfs_server" { type = string } +variable "ollama_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "ollama" { metadata { @@ -54,7 +47,7 @@ resource "kubernetes_persistent_volume" "ollama-pv" { persistent_volume_source { nfs { path = "/mnt/main/ollama" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -130,7 +123,7 @@ resource "kubernetes_deployment" "ollama" { nfs { # path = "/mnt/main/ollama" path = "/mnt/ssd/ollama" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -254,7 +247,7 @@ resource "kubernetes_deployment" "ollama-ui" { name = "ollama-ui" env { name = "OLLAMA_BASE_URL" - value = "http://ollama.ollama.svc.cluster.local:11434" + value = "http://${var.ollama_host}:11434" } port { @@ -269,7 +262,7 @@ resource "kubernetes_deployment" "ollama-ui" { name = "data" nfs { path = "/mnt/main/ollama" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/onlyoffice/main.tf b/stacks/onlyoffice/main.tf index e9df99e2..0b55d1a1 100644 --- a/stacks/onlyoffice/main.tf +++ b/stacks/onlyoffice/main.tf @@ -1,16 +1,10 @@ variable "tls_secret_name" { type = string } variable "onlyoffice_db_password" { type = string } variable "onlyoffice_jwt_token" { type = string } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "onlyoffice" { metadata { @@ -75,7 +69,7 @@ resource "kubernetes_deployment" "onlyoffice-document-server" { } env { name = "DB_HOST" - value = "mysql.dbaas" + value = var.mysql_host } env { name = "DB_PORT" @@ -95,7 +89,7 @@ resource "kubernetes_deployment" "onlyoffice-document-server" { } env { name = "REDIS_SERVER_HOST" - value = "redis.redis" + value = var.redis_host } env { name = "REDIS_SERVER_PORT" @@ -115,7 +109,7 @@ resource "kubernetes_deployment" "onlyoffice-document-server" { name = "data" nfs { path = "/mnt/main/onlyoffice" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/openclaw/main.tf b/stacks/openclaw/main.tf index a9739929..ede9990e 100644 --- a/stacks/openclaw/main.tf +++ b/stacks/openclaw/main.tf @@ -5,16 +5,9 @@ variable "gemini_api_key" { type = string } variable "llama_api_key" { type = string } variable "brave_api_key" { type = string } variable "modal_api_key" { type = string } +variable "nfs_server" { type = string } +variable "ollama_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "openclaw" { metadata { @@ -148,7 +141,7 @@ resource "kubernetes_config_map" "openclaw_config" { ] } ollama = { - baseUrl = "http://ollama.ollama.svc.cluster.local:11434/v1" + baseUrl = "http://${var.ollama_host}:11434/v1" api = "openai-completions" apiKey = "ollama" models = [ @@ -429,14 +422,14 @@ resource "kubernetes_deployment" "openclaw" { volume { name = "workspace" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/openclaw/workspace" } } volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/openclaw/data" } } diff --git a/stacks/osm_routing/main.tf b/stacks/osm_routing/main.tf index 1bb8d9b6..9e65b045 100644 --- a/stacks/osm_routing/main.tf +++ b/stacks/osm_routing/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "osm-routing" { metadata { @@ -64,7 +56,7 @@ resource "kubernetes_deployment" "osrm-foot" { volume { name = "osrm-data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/osm-routing/osrm-data" } } @@ -136,7 +128,7 @@ resource "kubernetes_deployment" "osrm-bicycle" { volume { name = "osrm-data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/osm-routing/osrm-data" } } @@ -208,7 +200,7 @@ resource "kubernetes_deployment" "otp" { volume { name = "otp-data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/osm-routing/otp-data" } } diff --git a/stacks/owntracks/main.tf b/stacks/owntracks/main.tf index b28c0151..82e37c9b 100644 --- a/stacks/owntracks/main.tf +++ b/stacks/owntracks/main.tf @@ -1,15 +1,7 @@ variable "tls_secret_name" { type = string } variable "owntracks_credentials" { type = map(string) } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "owntracks" { metadata { @@ -107,7 +99,7 @@ resource "kubernetes_deployment" "owntracks" { name = "data" nfs { path = "/mnt/main/owntracks" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/paperless-ngx/main.tf b/stacks/paperless-ngx/main.tf index c61db63e..144a4f74 100644 --- a/stacks/paperless-ngx/main.tf +++ b/stacks/paperless-ngx/main.tf @@ -1,16 +1,10 @@ variable "tls_secret_name" { type = string } variable "paperless_db_password" { type = string } variable "homepage_credentials" { type = map(any) } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "paperless-ngx" { metadata { @@ -69,7 +63,7 @@ resource "kubernetes_deployment" "paperless-ngx" { env { name = "PAPERLESS_REDIS" // If redis gets stuck, try deleting the locks files in log dir - value = "redis://redis.redis" + value = "redis://${var.redis_host}" } env { name = "PAPERLESS_REDIS_PREFIX" @@ -81,7 +75,7 @@ resource "kubernetes_deployment" "paperless-ngx" { } env { name = "PAPERLESS_DBHOST" - value = "mysql.dbaas" + value = var.mysql_host } env { name = "PAPERLESS_DBNAME" @@ -124,7 +118,7 @@ resource "kubernetes_deployment" "paperless-ngx" { name = "data" nfs { path = "/mnt/main/paperless-ngx" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/platform/main.tf b/stacks/platform/main.tf index b03dd6ca..2862be4b 100644 --- a/stacks/platform/main.tf +++ b/stacks/platform/main.tf @@ -16,15 +16,6 @@ # ----------------------------------------------------------------------------- # Tier Definitions # ----------------------------------------------------------------------------- -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} # ============================================================================= # Variable Declarations @@ -32,6 +23,12 @@ locals { # --- Core --- variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "postgresql_host" { type = string } +variable "mysql_host" { type = string } +variable "ollama_host" { type = string } +variable "mail_host" { type = string } variable "prod" { type = bool default = false @@ -140,6 +137,7 @@ module "dbaas" { source = "./modules/dbaas" prod = var.prod tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server dbaas_root_password = var.dbaas_root_password postgresql_root_password = var.dbaas_postgresql_root_password pgadmin_password = var.dbaas_pgadmin_password @@ -152,6 +150,7 @@ module "dbaas" { module "redis" { source = "./modules/redis" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server tier = local.tiers.cluster } @@ -171,6 +170,8 @@ module "traefik" { module "technitium" { source = "./modules/technitium" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server + mysql_host = var.mysql_host homepage_token = var.homepage_credentials["technitium"]["token"] technitium_db_password = var.technitium_db_password tier = local.tiers.core @@ -182,6 +183,7 @@ module "technitium" { module "headscale" { source = "./modules/headscale" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server headscale_config = var.headscale_config headscale_acl = var.headscale_acl tier = local.tiers.core @@ -196,6 +198,7 @@ module "authentik" { tls_secret_name = var.tls_secret_name secret_key = var.authentik_secret_key postgres_password = var.authentik_postgres_password + redis_host = var.redis_host } # ----------------------------------------------------------------------------- @@ -225,6 +228,7 @@ module "crowdsec" { source = "./modules/crowdsec" tier = local.tiers.cluster tls_secret_name = var.tls_secret_name + mysql_host = var.mysql_host homepage_username = var.homepage_credentials["crowdsec"]["username"] homepage_password = var.homepage_credentials["crowdsec"]["password"] enroll_key = var.crowdsec_enroll_key @@ -241,6 +245,8 @@ module "crowdsec" { module "monitoring" { source = "./modules/monitoring" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server + mysql_host = var.mysql_host alertmanager_account_password = var.alertmanager_account_password idrac_username = var.monitoring_idrac_username idrac_password = var.monitoring_idrac_password @@ -259,6 +265,8 @@ module "monitoring" { module "vaultwarden" { source = "./modules/vaultwarden" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server + mail_host = var.mail_host smtp_password = var.vaultwarden_smtp_password tier = local.tiers.edge } @@ -304,6 +312,7 @@ module "kyverno" { module "uptime-kuma" { source = "./modules/uptime-kuma" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server tier = local.tiers.cluster } @@ -338,6 +347,8 @@ module "xray" { module "mailserver" { source = "./modules/mailserver" tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server + mysql_host = var.mysql_host mailserver_accounts = var.mailserver_accounts postfix_account_aliases = var.mailserver_aliases opendkim_key = var.mailserver_opendkim_key @@ -370,6 +381,7 @@ module "cloudflared" { # ----------------------------------------------------------------------------- module "infra-maintenance" { source = "./modules/infra-maintenance" + nfs_server = var.nfs_server git_user = var.webhook_handler_git_user git_token = var.webhook_handler_git_token technitium_username = var.technitium_username @@ -385,11 +397,11 @@ output "tls_secret_name" { } output "redis_host" { - value = "redis.redis.svc.cluster.local" + value = var.redis_host } output "postgresql_host" { - value = "postgresql.dbaas.svc.cluster.local" + value = var.postgresql_host } output "postgresql_port" { @@ -397,7 +409,7 @@ output "postgresql_port" { } output "mysql_host" { - value = "mysql.dbaas.svc.cluster.local" + value = var.mysql_host } output "mysql_port" { @@ -405,7 +417,7 @@ output "mysql_port" { } output "smtp_host" { - value = "mail.viktorbarzin.me" + value = var.mail_host } output "smtp_port" { diff --git a/stacks/platform/modules/authentik/main.tf b/stacks/platform/modules/authentik/main.tf index 2087ea18..a52cde34 100644 --- a/stacks/platform/modules/authentik/main.tf +++ b/stacks/platform/modules/authentik/main.tf @@ -2,6 +2,7 @@ variable "tls_secret_name" {} variable "secret_key" {} variable "postgres_password" {} variable "tier" { type = string } +variable "redis_host" { type = string } module "tls_secret" { @@ -48,7 +49,7 @@ resource "helm_release" "authentik" { atomic = true timeout = 6000 - values = [templatefile("${path.module}/values.yaml", { postgres_password = var.postgres_password, secret_key = var.secret_key })] + values = [templatefile("${path.module}/values.yaml", { postgres_password = var.postgres_password, secret_key = var.secret_key, redis_host = var.redis_host })] } diff --git a/stacks/platform/modules/authentik/values.yaml b/stacks/platform/modules/authentik/values.yaml index c94d4694..2b267407 100644 --- a/stacks/platform/modules/authentik/values.yaml +++ b/stacks/platform/modules/authentik/values.yaml @@ -13,7 +13,7 @@ authentik: user: authentik password: ${postgres_password} redis: - host: redis.redis + host: ${redis_host} server: replicas: 3 diff --git a/stacks/platform/modules/crowdsec/main.tf b/stacks/platform/modules/crowdsec/main.tf index d27a8d92..0f640614 100644 --- a/stacks/platform/modules/crowdsec/main.tf +++ b/stacks/platform/modules/crowdsec/main.tf @@ -8,6 +8,7 @@ variable "crowdsec_dash_machine_id" { type = string } # used for web dash variable "crowdsec_dash_machine_password" { type = string } # used for web dash variable "tier" { type = string } variable "slack_webhook_url" { type = string } +variable "mysql_host" { type = string } module "tls_secret" { source = "../../../../modules/kubernetes/setup_tls_secret" @@ -99,7 +100,7 @@ resource "helm_release" "crowdsec" { repository = "https://crowdsecurity.github.io/helm-charts" chart = "crowdsec" - values = [templatefile("${path.module}/values.yaml", { homepage_username = var.homepage_username, homepage_password = var.homepage_password, DB_PASSWORD = var.db_password, ENROLL_KEY = var.enroll_key, SLACK_WEBHOOK_URL = var.slack_webhook_url })] + values = [templatefile("${path.module}/values.yaml", { homepage_username = var.homepage_username, homepage_password = var.homepage_password, DB_PASSWORD = var.db_password, ENROLL_KEY = var.enroll_key, SLACK_WEBHOOK_URL = var.slack_webhook_url, mysql_host = var.mysql_host })] timeout = 3600 } diff --git a/stacks/platform/modules/crowdsec/values.yaml b/stacks/platform/modules/crowdsec/values.yaml index c991536f..0029993e 100644 --- a/stacks/platform/modules/crowdsec/values.yaml +++ b/stacks/platform/modules/crowdsec/values.yaml @@ -81,7 +81,7 @@ lapi: - name: MB_DB_PASS value: "${DB_PASSWORD}" - name: MB_DB_HOST - value: "mysql.dbaas.svc.cluster.local" + value: "${mysql_host}" - name: MB_EMAIL_SMTP_USERNAME value: "info@viktorbarzin.me" @@ -166,7 +166,7 @@ config: user: crowdsec password: ${DB_PASSWORD} db_name: crowdsec - host: mysql.dbaas.svc.cluster.local + host: ${mysql_host} port: 3306 api: server: diff --git a/stacks/platform/modules/dbaas/main.tf b/stacks/platform/modules/dbaas/main.tf index 3a964132..1c42b64a 100644 --- a/stacks/platform/modules/dbaas/main.tf +++ b/stacks/platform/modules/dbaas/main.tf @@ -11,6 +11,7 @@ variable "prod" { default = false type = bool } +variable "nfs_server" { type = string } resource "kubernetes_namespace" "dbaas" { metadata { @@ -131,6 +132,18 @@ resource "kubernetes_deployment" "mysql" { container { image = "mysql:9.2.0" name = "mysql" + + resources { + requests = { + cpu = "250m" + memory = "512Mi" + } + limits = { + cpu = "1" + memory = "2Gi" + } + } + env { name = "MYSQL_ROOT_PASSWORD" value = var.dbaas_root_password @@ -153,7 +166,7 @@ resource "kubernetes_deployment" "mysql" { name = "mysql-persistent-storage" nfs { path = "/mnt/main/mysql" - server = "10.0.10.15" + server = var.nfs_server } } @@ -219,7 +232,7 @@ resource "kubernetes_cron_job_v1" "mysql-backup" { name = "mysql-backup" nfs { path = "/mnt/main/mysql-backup" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -717,6 +730,18 @@ resource "kubernetes_deployment" "postgres" { image = "viktorbarzin/postgres:16-master" # mix of postgis + pgvector # image = "postgres:17.2-bullseye" # needs pg_upgrade to data dir name = "postgresql" + + resources { + requests = { + cpu = "250m" + memory = "512Mi" + } + limits = { + cpu = "1" + memory = "2Gi" + } + } + env { name = "POSTGRES_PASSWORD" value = var.postgresql_root_password @@ -744,7 +769,7 @@ resource "kubernetes_deployment" "postgres" { name = "postgresql-persistent-storage" nfs { path = "/mnt/main/postgresql/data" - server = "10.0.10.15" + server = var.nfs_server } } # volume { @@ -830,7 +855,7 @@ resource "kubernetes_deployment" "pgadmin" { # } nfs { path = "/mnt/main/postgresql/pgadmin" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -905,7 +930,7 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" { name = "postgresql-backup" nfs { path = "/mnt/main/postgresql-backup" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/platform/modules/headscale/main.tf b/stacks/platform/modules/headscale/main.tf index 1cbcbeaa..60bf2e4d 100644 --- a/stacks/platform/modules/headscale/main.tf +++ b/stacks/platform/modules/headscale/main.tf @@ -3,6 +3,7 @@ variable "tls_secret_name" {} variable "tier" { type = string } variable "headscale_config" {} variable "headscale_acl" {} +variable "nfs_server" { type = string } resource "kubernetes_namespace" "headscale" { metadata { @@ -61,6 +62,18 @@ resource "kubernetes_deployment" "headscale" { # image = "headscale/headscale:0.23.0-debug" # -debug is for debug images name = "headscale" command = ["headscale", "serve"] + + resources { + requests = { + cpu = "50m" + memory = "64Mi" + } + limits = { + cpu = "200m" + memory = "256Mi" + } + } + port { container_port = 8080 } @@ -100,7 +113,7 @@ resource "kubernetes_deployment" "headscale" { name = "nfs-config" nfs { path = "/mnt/main/headscale" - server = "10.0.10.15" + server = var.nfs_server } } # container { @@ -114,6 +127,18 @@ resource "kubernetes_deployment" "headscale" { image = "ghcr.io/gurucomputing/headscale-ui:latest" # image = "ghcr.io/tale/headplane:0.3.2" name = "headscale-ui" + + resources { + requests = { + cpu = "25m" + memory = "32Mi" + } + limits = { + cpu = "100m" + memory = "128Mi" + } + } + port { container_port = 8081 # container_port = 3000 diff --git a/stacks/platform/modules/infra-maintenance/main.tf b/stacks/platform/modules/infra-maintenance/main.tf index 27a92a96..1f572630 100644 --- a/stacks/platform/modules/infra-maintenance/main.tf +++ b/stacks/platform/modules/infra-maintenance/main.tf @@ -3,6 +3,7 @@ variable "git_user" {} variable "git_token" {} variable "technitium_username" {} variable "technitium_password" {} +variable "nfs_server" { type = string } # DISABLED WHILST USING CLOUDFLARE NS @@ -124,7 +125,7 @@ resource "kubernetes_cron_job_v1" "backup-etcd" { name = "backup" nfs { path = "/mnt/main/etcd-backup" - server = "10.0.10.15" + server = var.nfs_server } } volume { diff --git a/stacks/platform/modules/kyverno/security-policies.tf b/stacks/platform/modules/kyverno/security-policies.tf new file mode 100644 index 00000000..1f1c83a8 --- /dev/null +++ b/stacks/platform/modules/kyverno/security-policies.tf @@ -0,0 +1,203 @@ +# ============================================================================= +# Pod Security Policies (Audit Mode) +# ============================================================================= +# Kyverno validate policies for pod security standards. +# All policies start in Audit mode - violations are logged but not blocked. + +resource "kubernetes_manifest" "policy_deny_privileged" { + manifest = { + apiVersion = "kyverno.io/v1" + kind = "ClusterPolicy" + metadata = { + name = "deny-privileged-containers" + annotations = { + "policies.kyverno.io/title" = "Deny Privileged Containers" + "policies.kyverno.io/category" = "Pod Security" + "policies.kyverno.io/severity" = "high" + "policies.kyverno.io/description" = "Privileged containers have full host access. Deny unless explicitly exempted." + } + } + spec = { + validationFailureAction = "Audit" + background = true + rules = [{ + name = "deny-privileged" + match = { + any = [{ + resources = { + kinds = ["Pod"] + } + }] + } + exclude = { + any = [{ + resources = { + namespaces = ["frigate", "nvidia", "monitoring"] + } + }] + } + validate = { + message = "Privileged containers are not allowed. Use specific capabilities instead." + pattern = { + spec = { + containers = [{ + "=(securityContext)" = { + "=(privileged)" = false + } + }] + "=(initContainers)" = [{ + "=(securityContext)" = { + "=(privileged)" = false + } + }] + } + } + } + }] + } + } + + depends_on = [helm_release.kyverno] +} + +resource "kubernetes_manifest" "policy_deny_host_namespaces" { + manifest = { + apiVersion = "kyverno.io/v1" + kind = "ClusterPolicy" + metadata = { + name = "deny-host-namespaces" + annotations = { + "policies.kyverno.io/title" = "Deny Host Namespaces" + "policies.kyverno.io/category" = "Pod Security" + "policies.kyverno.io/severity" = "high" + "policies.kyverno.io/description" = "Sharing host namespaces enables container escapes. Deny hostNetwork, hostPID, hostIPC." + } + } + spec = { + validationFailureAction = "Audit" + background = true + rules = [{ + name = "deny-host-namespaces" + match = { + any = [{ + resources = { + kinds = ["Pod"] + } + }] + } + exclude = { + any = [{ + resources = { + namespaces = ["frigate", "monitoring"] + } + }] + } + validate = { + message = "Host namespaces (hostNetwork, hostPID, hostIPC) are not allowed." + pattern = { + spec = { + "=(hostNetwork)" = false + "=(hostPID)" = false + "=(hostIPC)" = false + } + } + } + }] + } + } + + depends_on = [helm_release.kyverno] +} + +resource "kubernetes_manifest" "policy_restrict_capabilities" { + manifest = { + apiVersion = "kyverno.io/v1" + kind = "ClusterPolicy" + metadata = { + name = "restrict-sys-admin" + annotations = { + "policies.kyverno.io/title" = "Restrict SYS_ADMIN Capability" + "policies.kyverno.io/category" = "Pod Security" + "policies.kyverno.io/severity" = "high" + "policies.kyverno.io/description" = "SYS_ADMIN is nearly equivalent to root. Restrict to explicitly exempted namespaces." + } + } + spec = { + validationFailureAction = "Audit" + background = true + rules = [{ + name = "restrict-sys-admin" + match = { + any = [{ + resources = { + kinds = ["Pod"] + } + }] + } + exclude = { + any = [{ + resources = { + namespaces = ["nvidia", "monitoring"] + } + }] + } + validate = { + message = "Adding SYS_ADMIN capability is not allowed." + deny = { + conditions = { + any = [{ + key = "{{ request.object.spec.containers[].securityContext.capabilities.add[] || `[]` }}" + operator = "AnyIn" + value = ["SYS_ADMIN"] + }] + } + } + } + }] + } + } + + depends_on = [helm_release.kyverno] +} + +resource "kubernetes_manifest" "policy_require_trusted_registries" { + manifest = { + apiVersion = "kyverno.io/v1" + kind = "ClusterPolicy" + metadata = { + name = "require-trusted-registries" + annotations = { + "policies.kyverno.io/title" = "Require Trusted Image Registries" + "policies.kyverno.io/category" = "Pod Security" + "policies.kyverno.io/severity" = "medium" + "policies.kyverno.io/description" = "Images must come from trusted registries to prevent supply chain attacks." + } + } + spec = { + validationFailureAction = "Audit" + background = true + rules = [{ + name = "validate-registries" + match = { + any = [{ + resources = { + kinds = ["Pod"] + } + }] + } + validate = { + message = "Images must be from trusted registries (docker.io, ghcr.io, quay.io, registry.k8s.io, or local cache)." + pattern = { + spec = { + containers = [{ + image = "docker.io/* | ghcr.io/* | quay.io/* | registry.k8s.io/* | 10.0.20.10* | */*" + }] + } + } + } + }] + } + } + + depends_on = [helm_release.kyverno] +} diff --git a/stacks/platform/modules/mailserver/main.tf b/stacks/platform/modules/mailserver/main.tf index 4fa39309..7d95672d 100644 --- a/stacks/platform/modules/mailserver/main.tf +++ b/stacks/platform/modules/mailserver/main.tf @@ -4,6 +4,7 @@ variable "mailserver_accounts" {} variable "postfix_account_aliases" {} variable "opendkim_key" {} variable "sasl_passwd" {} # For sendgrid i.e relayhost +variable "nfs_server" { type = string } resource "kubernetes_namespace" "mailserver" { metadata { @@ -106,7 +107,7 @@ resource "kubernetes_config_map" "mailserver_config" { } } EOF - fail2ban_conf = <<-EOF + fail2ban_conf = <<-EOF [DEFAULT] #logtarget = /var/log/fail2ban.log @@ -393,7 +394,7 @@ resource "kubernetes_deployment" "mailserver" { name = "data" nfs { path = "/mnt/main/mailserver" - server = "10.0.10.15" + server = var.nfs_server } # iscsi { # target_portal = "iscsi.viktorbarzin.lan:3260" diff --git a/stacks/platform/modules/mailserver/roundcubemail.tf b/stacks/platform/modules/mailserver/roundcubemail.tf index ce77f0d2..2f26c9d7 100644 --- a/stacks/platform/modules/mailserver/roundcubemail.tf +++ b/stacks/platform/modules/mailserver/roundcubemail.tf @@ -1,4 +1,5 @@ variable "roundcube_db_password" { type = string } +variable "mysql_host" { type = string } # If you want to override settings mount this in /var/roundcube/config # more info in https://github.com/roundcube/roundcubemail-docker?tab=readme-ov-file @@ -89,7 +90,7 @@ resource "kubernetes_deployment" "roundcubemail" { } env { name = "ROUNDCUBEMAIL_DB_HOST" - value = "mysql.dbaas" + value = var.mysql_host } env { name = "ROUNDCUBEMAIL_DB_USER" @@ -148,14 +149,14 @@ resource "kubernetes_deployment" "roundcubemail" { name = "html" nfs { path = "/mnt/main/roundcubemail/html" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "enigma" nfs { path = "/mnt/main/roundcubemail/enigma" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/platform/modules/monitoring/alloy.yaml b/stacks/platform/modules/monitoring/alloy.yaml index b68c8d91..ac3148e8 100644 --- a/stacks/platform/modules/monitoring/alloy.yaml +++ b/stacks/platform/modules/monitoring/alloy.yaml @@ -125,7 +125,7 @@ alloy: resources: requests: cpu: 50m - memory: 256Mi + memory: 512Mi limits: cpu: 200m - memory: 768Mi + memory: 1Gi diff --git a/stacks/platform/modules/monitoring/grafana.tf b/stacks/platform/modules/monitoring/grafana.tf index 899f3478..aa3c22df 100644 --- a/stacks/platform/modules/monitoring/grafana.tf +++ b/stacks/platform/modules/monitoring/grafana.tf @@ -1,4 +1,5 @@ + # resource "kubernetes_persistent_volume" "prometheus_grafana_pv" { # metadata { # name = "grafana-pv" @@ -11,7 +12,7 @@ # persistent_volume_source { # nfs { # path = "/mnt/main/grafana" -# server = "10.0.10.15" +# server = var.nfs_server # } # # iscsi { # # target_portal = "iscsi.viktorbarzin.lan:3260" @@ -35,7 +36,7 @@ resource "kubernetes_persistent_volume" "alertmanager_pv" { persistent_volume_source { nfs { path = "/mnt/main/alertmanager" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -65,5 +66,5 @@ resource "helm_release" "grafana" { repository = "https://grafana.github.io/helm-charts" chart = "grafana" - values = [templatefile("${path.module}/grafana_chart_values.yaml", { db_password = var.grafana_db_password, grafana_admin_password = var.grafana_admin_password })] + values = [templatefile("${path.module}/grafana_chart_values.yaml", { db_password = var.grafana_db_password, grafana_admin_password = var.grafana_admin_password, mysql_host = var.mysql_host })] } diff --git a/stacks/platform/modules/monitoring/grafana_chart_values.yaml b/stacks/platform/modules/monitoring/grafana_chart_values.yaml index 8cfc207f..d7d8b2f2 100644 --- a/stacks/platform/modules/monitoring/grafana_chart_values.yaml +++ b/stacks/platform/modules/monitoring/grafana_chart_values.yaml @@ -48,7 +48,7 @@ env: grafana.ini: database: type: mysql - host: mysql.dbaas.svc.cluster.local:3306 + host: ${mysql_host}:3306 name: grafana user: grafana password: $__env{GF_DATABASE_PASSWORD} diff --git a/stacks/platform/modules/monitoring/loki.tf b/stacks/platform/modules/monitoring/loki.tf index 14ecd1a8..9bcef976 100644 --- a/stacks/platform/modules/monitoring/loki.tf +++ b/stacks/platform/modules/monitoring/loki.tf @@ -1,3 +1,5 @@ +variable "nfs_server" { type = string } + resource "helm_release" "loki" { namespace = kubernetes_namespace.monitoring.metadata[0].name create_namespace = true @@ -24,7 +26,7 @@ resource "kubernetes_persistent_volume" "loki" { persistent_volume_source { nfs { path = "/mnt/main/loki/loki" - server = "10.0.10.15" + server = var.nfs_server } } persistent_volume_reclaim_policy = "Retain" diff --git a/stacks/platform/modules/monitoring/loki.yaml b/stacks/platform/modules/monitoring/loki.yaml index 639bf0b3..63be79f8 100644 --- a/stacks/platform/modules/monitoring/loki.yaml +++ b/stacks/platform/modules/monitoring/loki.yaml @@ -22,7 +22,7 @@ loki: limits_config: allow_structured_metadata: true volume_enabled: true - retention_period: 168h + retention_period: 720h compactor: retention_enabled: true working_directory: /var/loki/compactor diff --git a/stacks/platform/modules/monitoring/main.tf b/stacks/platform/modules/monitoring/main.tf index 5d92740a..24d69dd0 100644 --- a/stacks/platform/modules/monitoring/main.tf +++ b/stacks/platform/modules/monitoring/main.tf @@ -16,6 +16,7 @@ variable "pve_password" { type = string } variable "grafana_db_password" { type = string } variable "grafana_admin_password" { type = string } variable "tier" { type = string } +variable "mysql_host" { type = string } resource "kubernetes_namespace" "monitoring" { metadata { diff --git a/stacks/platform/modules/monitoring/prometheus.tf b/stacks/platform/modules/monitoring/prometheus.tf index 12a00b66..472603e7 100644 --- a/stacks/platform/modules/monitoring/prometheus.tf +++ b/stacks/platform/modules/monitoring/prometheus.tf @@ -1,4 +1,5 @@ + resource "kubernetes_persistent_volume_claim" "prometheus_server_pvc" { metadata { name = "prometheus-iscsi-pvc" @@ -29,7 +30,7 @@ resource "kubernetes_persistent_volume" "prometheus_server_pvc" { persistent_volume_source { nfs { path = "/mnt/main/prometheus" - server = "10.0.10.15" + server = var.nfs_server } # iscsi { # fs_type = "ext4" diff --git a/stacks/platform/modules/monitoring/prometheus_chart_values.tpl b/stacks/platform/modules/monitoring/prometheus_chart_values.tpl index ed020448..caba0ef3 100755 --- a/stacks/platform/modules/monitoring/prometheus_chart_values.tpl +++ b/stacks/platform/modules/monitoring/prometheus_chart_values.tpl @@ -316,6 +316,13 @@ serverFiles: severity: warning annotations: summary: "PV {{ $labels.persistentvolumeclaim }} in {{ $labels.namespace }}: {{ $value | printf \"%.0f\" }}% used (threshold: 85%)" + - alert: PVPredictedFull + expr: predict_linear(kubelet_volume_stats_used_bytes[6h], 3600*24) > kubelet_volume_stats_capacity_bytes + for: 1h + labels: + severity: warning + annotations: + summary: "PV {{ $labels.persistentvolumeclaim }} in {{ $labels.namespace }} predicted to fill within 24h" - name: K8s Health rules: - alert: PodCrashLooping @@ -389,6 +396,50 @@ serverFiles: severity: warning annotations: summary: "Prometheus notification errors: {{ $value | printf \"%.2f\" }}/s" + - name: Critical Services + rules: + - alert: PostgreSQLDown + expr: (kube_deployment_status_replicas_available{namespace="dbaas", deployment=~"postgresql.*"} or on() vector(0)) < 1 + for: 5m + labels: + severity: critical + annotations: + summary: "PostgreSQL has no available replicas" + - alert: MySQLDown + expr: (kube_deployment_status_replicas_available{namespace="dbaas", deployment=~"mysql.*"} or on() vector(0)) < 1 + for: 5m + labels: + severity: critical + annotations: + summary: "MySQL has no available replicas" + - alert: RedisDown + expr: (kube_deployment_status_replicas_available{namespace="redis"} or on() vector(0)) < 1 + for: 5m + labels: + severity: critical + annotations: + summary: "Redis has no available replicas" + - alert: HeadscaleDown + expr: (kube_deployment_status_replicas_available{namespace="headscale"} or on() vector(0)) < 1 + for: 5m + labels: + severity: critical + annotations: + summary: "Headscale VPN has no available replicas" + - alert: AuthentikDown + expr: (kube_deployment_status_replicas_available{namespace="authentik", deployment="authentik-server"} or on() vector(0)) < 1 + for: 5m + labels: + severity: critical + annotations: + summary: "Authentik auth server has no available replicas" + - alert: LokiDown + expr: (kube_statefulset_status_replicas_ready{namespace="monitoring", statefulset=~"loki.*"} or on() vector(0)) < 1 + for: 5m + labels: + severity: warning + annotations: + summary: "Loki log aggregation has no ready replicas" - name: Cluster rules: - alert: NodeDown @@ -548,20 +599,20 @@ serverFiles: severity: page annotations: summary: Mail server has no available replicas. This means mail may not be received. - # - alert: Hackmd has no replicas available - # expr: (kube_deployment_status_replicas_available{namespace="hackmd"} or on() vector(0)) < 1 - # for: 1m - # labels: - # severity: page - # annotations: - # summary: Hackmd has no available replicas. - # - alert: Privatebin has no replicas available - # expr: (kube_deployment_status_replicas_available{namespace="privatebin"} or on() vector(0)) < 1 - # for: 10m - # labels: - # severity: page - # annotations: - # summary: Privatebin has no available replicas. + - alert: HackmdDown + expr: (kube_deployment_status_replicas_available{namespace="hackmd"} or on() vector(0)) < 1 + for: 5m + labels: + severity: warning + annotations: + summary: "Hackmd has no available replicas" + - alert: PrivatebinDown + expr: (kube_deployment_status_replicas_available{namespace="privatebin"} or on() vector(0)) < 1 + for: 10m + labels: + severity: warning + annotations: + summary: "Privatebin has no available replicas" # - name: London OpenWRT Down # rules: # - alert: OpenWRT client unreachable diff --git a/stacks/platform/modules/nvidia/main.tf b/stacks/platform/modules/nvidia/main.tf index 57ab63cd..097c7dfd 100644 --- a/stacks/platform/modules/nvidia/main.tf +++ b/stacks/platform/modules/nvidia/main.tf @@ -12,7 +12,7 @@ resource "kubernetes_namespace" "nvidia" { name = "nvidia" labels = { "istio-injection" : "disabled" - tier = var.tier + tier = var.tier "resource-governance/custom-quota" = "true" } } diff --git a/stacks/platform/modules/redis/main.tf b/stacks/platform/modules/redis/main.tf index 5db3cbe5..79088543 100644 --- a/stacks/platform/modules/redis/main.tf +++ b/stacks/platform/modules/redis/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_namespace" "redis" { metadata { @@ -49,6 +50,17 @@ resource "kubernetes_deployment" "redis" { image = "redis/redis-stack:latest" name = "redis" + resources { + requests = { + cpu = "100m" + memory = "128Mi" + } + limits = { + cpu = "500m" + memory = "512Mi" + } + } + port { container_port = 6379 } @@ -64,7 +76,7 @@ resource "kubernetes_deployment" "redis" { name = "data" nfs { path = "/mnt/main/redis" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/platform/modules/technitium/main.tf b/stacks/platform/modules/technitium/main.tf index 633abf77..a5a459ab 100644 --- a/stacks/platform/modules/technitium/main.tf +++ b/stacks/platform/modules/technitium/main.tf @@ -2,6 +2,8 @@ variable "tls_secret_name" {} variable "tier" { type = string } variable "homepage_token" {} variable "technitium_db_password" {} +variable "nfs_server" { type = string } +variable "mysql_host" { type = string } resource "kubernetes_namespace" "technitium" { metadata { @@ -131,14 +133,14 @@ resource "kubernetes_deployment" "technitium" { image = "technitium/dns-server:latest" name = "technitium" resources { - # limits = { - # cpu = "1" - # memory = "1Gi" - # } - # requests = { - # cpu = "1" - # memory = "1Gi" - # } + requests = { + cpu = "100m" + memory = "128Mi" + } + limits = { + cpu = "500m" + memory = "512Mi" + } } port { container_port = 5380 @@ -162,7 +164,7 @@ resource "kubernetes_deployment" "technitium" { name = "nfs-config" nfs { path = "/mnt/main/technitium" - server = "10.0.10.15" + server = var.nfs_server } } volume { @@ -278,7 +280,7 @@ resource "kubernetes_config_map" "grafana_technitium_datasource" { name = "Technitium MySQL" type = "mysql" access = "proxy" - url = "mysql.dbaas.svc.cluster.local:3306" + url = "${var.mysql_host}:3306" database = "technitium" user = "technitium" uid = "technitium-mysql" diff --git a/stacks/platform/modules/traefik/main.tf b/stacks/platform/modules/traefik/main.tf index ab836a27..d7a9da5a 100644 --- a/stacks/platform/modules/traefik/main.tf +++ b/stacks/platform/modules/traefik/main.tf @@ -80,7 +80,7 @@ resource "helm_release" "traefik" { # Enable dashboard API (accessible on port 8080 internally) api = { - insecure = true + insecure = false } # Entrypoints @@ -174,7 +174,6 @@ resource "helm_release" "traefik" { } additionalArguments = [ - "--api.insecure=true", "--global.checknewversion=false", "--global.sendanonymoususage=false", # Skip TLS verification for self-signed backend certs (proxmox, idrac, etc.) @@ -184,8 +183,10 @@ resource "helm_release" "traefik" { "--serversTransport.forwardingTimeouts.responseHeaderTimeout=0s", "--serversTransport.forwardingTimeouts.idleConnTimeout=90s", # Use forwarded headers from trusted proxies - "--entryPoints.websecure.forwardedHeaders.insecure=true", - "--entryPoints.web.forwardedHeaders.insecure=true", + "--entryPoints.websecure.forwardedHeaders.insecure=false", + "--entryPoints.web.forwardedHeaders.insecure=false", + "--entryPoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,10.0.0.0/8,192.168.0.0/16", + "--entryPoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,10.0.0.0/8,192.168.0.0/16", ] resources = { diff --git a/stacks/platform/modules/traefik/middleware.tf b/stacks/platform/modules/traefik/middleware.tf index 8cfba83c..0a5481b6 100644 --- a/stacks/platform/modules/traefik/middleware.tf +++ b/stacks/platform/modules/traefik/middleware.tf @@ -13,8 +13,8 @@ resource "kubernetes_manifest" "middleware_rate_limit" { } spec = { rateLimit = { - average = 5 - burst = 250 + average = 10 + burst = 50 } } } @@ -113,6 +113,31 @@ resource "kubernetes_manifest" "middleware_csp_headers" { depends_on = [helm_release.traefik] } +# Security headers middleware (HSTS, X-Frame-Options, etc.) +resource "kubernetes_manifest" "middleware_security_headers" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "security-headers" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + headers = { + stsSeconds = 31536000 + stsIncludeSubdomains = true + frameDeny = true + contentTypeNosniff = true + browserXssFilter = true + referrerPolicy = "strict-origin-when-cross-origin" + permissionsPolicy = "camera=(), microphone=(), geolocation=()" + } + } + } + + depends_on = [helm_release.traefik] +} + # CrowdSec bouncer plugin middleware resource "kubernetes_manifest" "middleware_crowdsec" { manifest = { diff --git a/stacks/platform/modules/uptime-kuma/main.tf b/stacks/platform/modules/uptime-kuma/main.tf index 0ecdc9dc..d93232c0 100644 --- a/stacks/platform/modules/uptime-kuma/main.tf +++ b/stacks/platform/modules/uptime-kuma/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_namespace" "uptime-kuma" { metadata { @@ -56,6 +57,17 @@ resource "kubernetes_deployment" "uptime-kuma" { image = "louislam/uptime-kuma:2" name = "uptime-kuma" + resources { + requests = { + cpu = "50m" + memory = "64Mi" + } + limits = { + cpu = "200m" + memory = "256Mi" + } + } + port { container_port = 3001 } @@ -67,7 +79,7 @@ resource "kubernetes_deployment" "uptime-kuma" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/uptime-kuma" } } @@ -160,7 +172,7 @@ module "ingress" { # volume { # name = "data" # nfs { -# server = "10.0.10.15" +# server = var.nfs_server # path = "/mnt/main/uptime-kuma" # } # } diff --git a/stacks/platform/modules/vaultwarden/main.tf b/stacks/platform/modules/vaultwarden/main.tf index f2f14d9c..c97cff8f 100644 --- a/stacks/platform/modules/vaultwarden/main.tf +++ b/stacks/platform/modules/vaultwarden/main.tf @@ -1,6 +1,8 @@ variable "tls_secret_name" {} variable "tier" { type = string } variable "smtp_password" {} +variable "nfs_server" { type = string } +variable "mail_host" { type = string } resource "kubernetes_namespace" "vaultwarden" { metadata { @@ -51,6 +53,18 @@ resource "kubernetes_deployment" "vaultwarden" { container { image = "vaultwarden/server:1.35.2" name = "vaultwarden" + + resources { + requests = { + cpu = "50m" + memory = "64Mi" + } + limits = { + cpu = "200m" + memory = "256Mi" + } + } + env { name = "DOMAIN" value = "https://vaultwarden.viktorbarzin.me" @@ -61,7 +75,7 @@ resource "kubernetes_deployment" "vaultwarden" { # } env { name = "SMTP_HOST" - value = "mail.viktorbarzin.me" + value = var.mail_host } env { name = "SMTP_FROM" @@ -96,7 +110,7 @@ resource "kubernetes_deployment" "vaultwarden" { name = "data" nfs { path = "/mnt/main/vaultwarden" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/platform/modules/xray/main.tf b/stacks/platform/modules/xray/main.tf index a87086e5..c5069e1a 100644 --- a/stacks/platform/modules/xray/main.tf +++ b/stacks/platform/modules/xray/main.tf @@ -186,109 +186,36 @@ resource "kubernetes_service" "xray-reality" { } } -resource "kubernetes_ingress_v1" "ingress" { - metadata { - namespace = kubernetes_namespace.xray.metadata[0].name - name = "xray" - annotations = { - "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" - "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" - } - } +module "ingress_ws" { + source = "../../../../modules/kubernetes/ingress_factory" + namespace = kubernetes_namespace.xray.metadata[0].name + name = "xray-ws" + service_name = "xray" + host = "xray-ws" + port = 8443 + tls_secret_name = var.tls_secret_name +} - spec { - ingress_class_name = "traefik" - tls { - hosts = ["xray-ws.viktorbarzin.me"] - secret_name = var.tls_secret_name - } - rule { - host = "xray-ws.viktorbarzin.me" - http { - path { - backend { - service { - name = "xray" - port { - number = 8443 - - } - } - } - } - } - } +module "ingress_grpc" { + source = "../../../../modules/kubernetes/ingress_factory" + namespace = kubernetes_namespace.xray.metadata[0].name + name = "xray-grpc" + service_name = "xray" + host = "xray-grpc" + port = 9443 + tls_secret_name = var.tls_secret_name + ingress_path = ["/grpc-vpn"] + extra_annotations = { + "traefik.ingress.kubernetes.io/service.serversscheme" = "h2c" } } -resource "kubernetes_ingress_v1" "ingress-grpc" { - metadata { - namespace = kubernetes_namespace.xray.metadata[0].name - name = "xray-grpc" - annotations = { - "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" - "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" - "traefik.ingress.kubernetes.io/service.serversscheme" = "h2c" - } - } - - spec { - ingress_class_name = "traefik" - tls { - hosts = ["xray-grpc.viktorbarzin.me"] - secret_name = var.tls_secret_name - } - rule { - host = "xray-grpc.viktorbarzin.me" - http { - path { - path = "/grpc-vpn" - path_type = "Prefix" - backend { - service { - name = "xray" - port { - number = 9443 - } - } - } - } - } - } - } -} - -resource "kubernetes_ingress_v1" "ingress-vless" { - metadata { - namespace = kubernetes_namespace.xray.metadata[0].name - name = "xray-vless" - annotations = { - "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd" - "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" - } - } - - spec { - ingress_class_name = "traefik" - tls { - hosts = ["xray-vless.viktorbarzin.me"] - secret_name = var.tls_secret_name - } - rule { - host = "xray-vless.viktorbarzin.me" - http { - path { - backend { - service { - name = "xray" - port { - number = 6443 - - } - } - } - } - } - } - } +module "ingress_vless" { + source = "../../../../modules/kubernetes/ingress_factory" + namespace = kubernetes_namespace.xray.metadata[0].name + name = "xray-vless" + service_name = "xray" + host = "xray-vless" + port = 6443 + tls_secret_name = var.tls_secret_name } diff --git a/stacks/plotting-book/main.tf b/stacks/plotting-book/main.tf index bc91ea0c..c24d16cf 100644 --- a/stacks/plotting-book/main.tf +++ b/stacks/plotting-book/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "plotting-book" { metadata { diff --git a/stacks/poison-fountain/main.tf b/stacks/poison-fountain/main.tf index add4832b..352cdc6d 100644 --- a/stacks/poison-fountain/main.tf +++ b/stacks/poison-fountain/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "poison_fountain" { metadata { @@ -152,7 +144,7 @@ resource "kubernetes_deployment" "poison_fountain" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/poison-fountain" } } @@ -259,7 +251,7 @@ resource "kubernetes_cron_job_v1" "poison_fetcher" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/poison-fountain" } } diff --git a/stacks/privatebin/main.tf b/stacks/privatebin/main.tf index 517324ee..7fe97aa0 100644 --- a/stacks/privatebin/main.tf +++ b/stacks/privatebin/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "privatebin" { metadata { @@ -70,7 +62,7 @@ resource "kubernetes_deployment" "privatebin" { name = "data" nfs { path = "/mnt/main/privatebin" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/real-estate-crawler/main.tf b/stacks/real-estate-crawler/main.tf index 6cf195c5..f8e9e993 100644 --- a/stacks/real-estate-crawler/main.tf +++ b/stacks/real-estate-crawler/main.tf @@ -1,23 +1,17 @@ variable "tls_secret_name" { type = string } variable "realestate_crawler_db_password" { type = string } variable "realestate_crawler_notification_settings" { type = map(string) } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "realestate-crawler" { metadata { name = "realestate-crawler" labels = { "istio-injection" : "disabled" - tier = local.tiers.aux + tier = local.tiers.aux "resource-governance/custom-quota" = "true" } } @@ -143,7 +137,7 @@ resource "kubernetes_deployment" "realestate-crawler-api" { } env { name = "DB_CONNECTION_STRING" - value = "mysql://wrongmove:${var.realestate_crawler_db_password}@mysql.dbaas.svc.cluster.local:3306/wrongmove" + value = "mysql://wrongmove:${var.realestate_crawler_db_password}@${var.mysql_host}:3306/wrongmove" } # env { @@ -156,11 +150,11 @@ resource "kubernetes_deployment" "realestate-crawler-api" { # } env { name = "CELERY_BROKER_URL" - value = "redis://redis.redis.svc.cluster.local:6379/0" + value = "redis://${var.redis_host}:6379/0" } env { name = "CELERY_RESULT_BACKEND" - value = "redis://redis.redis.svc.cluster.local:6379/1" + value = "redis://${var.redis_host}:6379/1" } env { @@ -196,6 +190,16 @@ resource "kubernetes_deployment" "realestate-crawler-api" { container_port = 5001 protocol = "TCP" } + resources { + requests = { + cpu = "50m" + memory = "128Mi" + } + limits = { + cpu = "2000m" + memory = "1Gi" + } + } volume_mount { name = "data" mount_path = "/app/data" @@ -205,7 +209,7 @@ resource "kubernetes_deployment" "realestate-crawler-api" { name = "data" nfs { path = "/mnt/main/real-estate-crawler" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -292,7 +296,7 @@ resource "kubernetes_deployment" "realestate-crawler-celery" { name = "celery-worker" image = "viktorbarzin/realestatecrawler:latest" image_pull_policy = "Always" - command = ["python", "-m", "celery", "-A", "celery_app", "worker", "--loglevel=info"] + command = ["python", "-m", "celery", "-A", "celery_app", "worker", "--loglevel=info", "--pool=threads"] port { name = "metrics" container_port = 9090 @@ -304,15 +308,15 @@ resource "kubernetes_deployment" "realestate-crawler-celery" { } env { name = "DB_CONNECTION_STRING" - value = "mysql://wrongmove:${var.realestate_crawler_db_password}@mysql.dbaas.svc.cluster.local:3306/wrongmove" + value = "mysql://wrongmove:${var.realestate_crawler_db_password}@${var.mysql_host}:3306/wrongmove" } env { name = "CELERY_BROKER_URL" - value = "redis://redis.redis.svc.cluster.local:6379/0" + value = "redis://${var.redis_host}:6379/0" } env { name = "CELERY_RESULT_BACKEND" - value = "redis://redis.redis.svc.cluster.local:6379/1" + value = "redis://${var.redis_host}:6379/1" } env { name = "SLACK_WEBHOOK_URL" @@ -339,7 +343,7 @@ resource "kubernetes_deployment" "realestate-crawler-celery" { name = "data" nfs { path = "/mnt/main/real-estate-crawler" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -398,21 +402,31 @@ resource "kubernetes_deployment" "realestate-crawler-celery-beat" { name = "celery-beat" image = "viktorbarzin/realestatecrawler:latest" command = ["python", "-m", "celery", "-A", "celery_app", "beat", "--loglevel=info"] + resources { + requests = { + cpu = "10m" + memory = "64Mi" + } + limits = { + cpu = "200m" + memory = "256Mi" + } + } env { name = "ENV" value = "prod" } env { name = "DB_CONNECTION_STRING" - value = "mysql://wrongmove:${var.realestate_crawler_db_password}@mysql.dbaas.svc.cluster.local:3306/wrongmove" + value = "mysql://wrongmove:${var.realestate_crawler_db_password}@${var.mysql_host}:3306/wrongmove" } env { name = "CELERY_BROKER_URL" - value = "redis://redis.redis.svc.cluster.local:6379/0" + value = "redis://${var.redis_host}:6379/0" } env { name = "CELERY_RESULT_BACKEND" - value = "redis://redis.redis.svc.cluster.local:6379/1" + value = "redis://${var.redis_host}:6379/1" } env { name = "SCRAPE_SCHEDULES" @@ -427,7 +441,7 @@ resource "kubernetes_deployment" "realestate-crawler-celery-beat" { name = "data" nfs { path = "/mnt/main/real-estate-crawler" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/reloader/main.tf b/stacks/reloader/main.tf index da1395ab..3d9e03a8 100644 --- a/stacks/reloader/main.tf +++ b/stacks/reloader/main.tf @@ -1,13 +1,3 @@ -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} - resource "kubernetes_namespace" "crowdsec" { metadata { name = "reloader" diff --git a/stacks/resume/main.tf b/stacks/resume/main.tf index f091fdc0..94661a17 100644 --- a/stacks/resume/main.tf +++ b/stacks/resume/main.tf @@ -2,16 +2,9 @@ variable "tls_secret_name" { type = string } variable "resume_database_url" { type = string } variable "resume_auth_secret" { type = string } variable "mailserver_accounts" { type = map(any) } +variable "nfs_server" { type = string } +variable "mail_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} locals { namespace = "resume" @@ -192,7 +185,7 @@ resource "kubernetes_deployment" "resume" { # SMTP config for password reset emails env { name = "SMTP_HOST" - value = "mail.viktorbarzin.me" + value = var.mail_host } env { name = "SMTP_PORT" @@ -259,7 +252,7 @@ resource "kubernetes_deployment" "resume" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/resume" } } diff --git a/stacks/rybbit/main.tf b/stacks/rybbit/main.tf index 794fbe36..896e5d69 100644 --- a/stacks/rybbit/main.tf +++ b/stacks/rybbit/main.tf @@ -1,16 +1,9 @@ variable "tls_secret_name" { type = string } variable "clickhouse_password" { type = string } variable "clickhouse_postgres_password" { type = string } +variable "nfs_server" { type = string } +variable "postgresql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "rybbit" { metadata { @@ -89,7 +82,7 @@ resource "kubernetes_deployment" "clickhouse" { name = "data" nfs { path = "/mnt/main/clickhouse" - server = "10.0.10.15" + server = var.nfs_server } } } @@ -168,7 +161,7 @@ resource "kubernetes_deployment" "rybbit" { } env { name = "POSTGRES_HOST" - value = "postgresql.dbaas.svc.cluster.local" + value = var.postgresql_host } env { name = "POSTGRES_PORT" diff --git a/stacks/send/main.tf b/stacks/send/main.tf index f1b43931..663337cf 100644 --- a/stacks/send/main.tf +++ b/stacks/send/main.tf @@ -1,14 +1,7 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "send" { metadata { @@ -81,7 +74,7 @@ resource "kubernetes_deployment" "send" { } env { name = "REDIS_HOST" - value = "redis.redis.svc.cluster.local" + value = var.redis_host } volume_mount { name = "data" @@ -92,7 +85,7 @@ resource "kubernetes_deployment" "send" { name = "data" nfs { path = "/mnt/main/send" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/servarr/aiostreams/main.tf b/stacks/servarr/aiostreams/main.tf index 1ec5e4ae..a97af8bf 100644 --- a/stacks/servarr/aiostreams/main.tf +++ b/stacks/servarr/aiostreams/main.tf @@ -1,6 +1,7 @@ variable "tls_secret_name" {} variable "tier" { type = string } variable "aiostreams_database_connection_string" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_namespace" "aiostreams" { metadata { @@ -64,7 +65,7 @@ resource "kubernetes_deployment" "aiostreams" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/servarr/aiostreams" } } diff --git a/stacks/servarr/lidarr/main.tf b/stacks/servarr/lidarr/main.tf index b851ca18..7816592d 100644 --- a/stacks/servarr/lidarr/main.tf +++ b/stacks/servarr/lidarr/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_deployment" "lidarr" { @@ -77,21 +78,21 @@ resource "kubernetes_deployment" "lidarr" { name = "data" nfs { path = "/mnt/main/servarr/lidarr" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "downloads" nfs { path = "/mnt/main/servarr/downloads" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "deemix-config" nfs { path = "/mnt/main/servarr/lidarr" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/servarr/listenarr/main.tf b/stacks/servarr/listenarr/main.tf index 2b371421..035971d7 100644 --- a/stacks/servarr/listenarr/main.tf +++ b/stacks/servarr/listenarr/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_deployment" "listenarr" { @@ -44,14 +45,14 @@ resource "kubernetes_deployment" "listenarr" { name = "data" nfs { path = "/mnt/main/servarr/listenarr" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "downloads" nfs { path = "/mnt/main/servarr/downloads" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/servarr/main.tf b/stacks/servarr/main.tf index 8498fcc8..105c55d3 100644 --- a/stacks/servarr/main.tf +++ b/stacks/servarr/main.tf @@ -1,15 +1,6 @@ variable "tls_secret_name" { type = string } variable "aiostreams_database_connection_string" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "servarr" { metadata { diff --git a/stacks/servarr/prowlarr/main.tf b/stacks/servarr/prowlarr/main.tf index 78bfebfc..a1ea2416 100644 --- a/stacks/servarr/prowlarr/main.tf +++ b/stacks/servarr/prowlarr/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_deployment" "prowlarr" { @@ -64,14 +65,14 @@ resource "kubernetes_deployment" "prowlarr" { name = "data" nfs { path = "/mnt/main/servarr/prowlarr" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "downloads" nfs { path = "/mnt/main/servarr/downloads" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/servarr/qbittorrent/main.tf b/stacks/servarr/qbittorrent/main.tf index 41976bfd..4d972e28 100644 --- a/stacks/servarr/qbittorrent/main.tf +++ b/stacks/servarr/qbittorrent/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_deployment" "qbittorrent" { @@ -64,14 +65,14 @@ resource "kubernetes_deployment" "qbittorrent" { name = "data" nfs { path = "/mnt/main/servarr/qbittorrent" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "downloads" nfs { path = "/mnt/main/servarr/downloads" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/servarr/readarr/main.tf b/stacks/servarr/readarr/main.tf index e58dc4db..7310495e 100644 --- a/stacks/servarr/readarr/main.tf +++ b/stacks/servarr/readarr/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_namespace" "readarr" { metadata { name = "readarr" @@ -83,14 +84,14 @@ resource "kubernetes_deployment" "readarr" { name = "data" nfs { path = "/mnt/main/servarr/readarr" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "qbittorrent" nfs { path = "/mnt/main/servarr/qbittorrent" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/servarr/soulseek/main.tf b/stacks/servarr/soulseek/main.tf index eaf11b79..fc623565 100644 --- a/stacks/servarr/soulseek/main.tf +++ b/stacks/servarr/soulseek/main.tf @@ -1,5 +1,6 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "nfs_server" { type = string } resource "kubernetes_deployment" "soulseek" { @@ -59,14 +60,14 @@ resource "kubernetes_deployment" "soulseek" { name = "config" nfs { path = "/mnt/main/servarr/lidarr" - server = "10.0.10.15" + server = var.nfs_server } } volume { name = "downloads" nfs { path = "/mnt/main/servarr/lidarr" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/shadowsocks/main.tf b/stacks/shadowsocks/main.tf index 9a036079..0b13ac51 100644 --- a/stacks/shadowsocks/main.tf +++ b/stacks/shadowsocks/main.tf @@ -1,14 +1,5 @@ variable "shadowsocks_password" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} variable "method" { default = "chacha20-ietf-poly1305" diff --git a/stacks/speedtest/main.tf b/stacks/speedtest/main.tf index 0357f801..35cffd4b 100644 --- a/stacks/speedtest/main.tf +++ b/stacks/speedtest/main.tf @@ -1,15 +1,8 @@ variable "tls_secret_name" { type = string } variable "speedtest_db_password" { type = string } +variable "nfs_server" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "speedtest" { metadata { @@ -90,7 +83,7 @@ resource "kubernetes_deployment" "speedtest" { } env { name = "DB_HOST" - value = "mysql.dbaas.svc.cluster.local" + value = var.mysql_host } env { name = "DB_DATABASE" @@ -116,7 +109,7 @@ resource "kubernetes_deployment" "speedtest" { volume { name = "config" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/speedtest" } } diff --git a/stacks/stirling-pdf/main.tf b/stacks/stirling-pdf/main.tf index 48bf69bd..71a1175e 100644 --- a/stacks/stirling-pdf/main.tf +++ b/stacks/stirling-pdf/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "stirling-pdf" { metadata { @@ -63,7 +55,7 @@ resource "kubernetes_deployment" "stirling-pdf" { volume { name = "configs" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/stirling-pdf" } } diff --git a/stacks/tandoor/main.tf b/stacks/tandoor/main.tf index 54d3c09e..9f69e674 100644 --- a/stacks/tandoor/main.tf +++ b/stacks/tandoor/main.tf @@ -4,16 +4,10 @@ variable "tandoor_email_password" { type = string default = "" } +variable "nfs_server" { type = string } +variable "postgresql_host" { type = string } +variable "mail_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "tandoor" { metadata { @@ -75,7 +69,7 @@ resource "kubernetes_deployment" "tandoor" { } env { name = "POSTGRES_HOST" - value = "postgresql.dbaas.svc.cluster.local" + value = var.postgresql_host } env { name = "POSTGRES_PORT" @@ -107,7 +101,7 @@ resource "kubernetes_deployment" "tandoor" { } env { name = "EMAIL_HOST" - value = "mail.viktorbarzin.me" + value = var.mail_host } env { name = "EMAIL_HOST_USER" @@ -148,7 +142,7 @@ resource "kubernetes_deployment" "tandoor" { name = "data" nfs { path = "/mnt/main/tandoor" - server = "10.0.10.15" + server = var.nfs_server } } } diff --git a/stacks/tor-proxy/main.tf b/stacks/tor-proxy/main.tf index 0c48104d..f43a55af 100644 --- a/stacks/tor-proxy/main.tf +++ b/stacks/tor-proxy/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "tor-proxy" { metadata { diff --git a/stacks/travel_blog/main.tf b/stacks/travel_blog/main.tf index 8e6d699c..5781369c 100644 --- a/stacks/travel_blog/main.tf +++ b/stacks/travel_blog/main.tf @@ -1,14 +1,5 @@ variable "tls_secret_name" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "travel-blog" { metadata { diff --git a/stacks/tuya-bridge/main.tf b/stacks/tuya-bridge/main.tf index 54fadc53..918e5c7f 100644 --- a/stacks/tuya-bridge/main.tf +++ b/stacks/tuya-bridge/main.tf @@ -4,15 +4,6 @@ variable "tiny_tuya_api_secret" { type = string } variable "tiny_tuya_service_secret" { type = string } variable "tiny_tuya_slack_url" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "tuya-bridge" { metadata { diff --git a/stacks/url/main.tf b/stacks/url/main.tf index 421f21f2..76972471 100644 --- a/stacks/url/main.tf +++ b/stacks/url/main.tf @@ -2,16 +2,8 @@ variable "tls_secret_name" { type = string } variable "url_shortener_geolite_license_key" { type = string } variable "url_shortener_api_key" { type = string } variable "url_shortener_mysql_password" { type = string } +variable "mysql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} ## Setup ## Need to manually add @@ -128,7 +120,7 @@ resource "kubernetes_deployment" "shlink" { } env { name = "DB_HOST" - value = "mysql.dbaas.svc.cluster.local" + value = var.mysql_host } # env { # name = "DB_USER" diff --git a/stacks/wealthfolio/main.tf b/stacks/wealthfolio/main.tf index e458b89c..6f2be13f 100644 --- a/stacks/wealthfolio/main.tf +++ b/stacks/wealthfolio/main.tf @@ -1,15 +1,7 @@ variable "tls_secret_name" { type = string } variable "wealthfolio_password_hash" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} # To refresh transactions use finance db positions exporters: # @@ -100,7 +92,7 @@ resource "kubernetes_deployment" "wealthfolio" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/wealthfolio" } } diff --git a/stacks/webhook_handler/main.tf b/stacks/webhook_handler/main.tf index 79f5b1e6..3bb3d833 100644 --- a/stacks/webhook_handler/main.tf +++ b/stacks/webhook_handler/main.tf @@ -7,15 +7,6 @@ variable "webhook_handler_git_user" { type = string } variable "webhook_handler_git_token" { type = string } variable "webhook_handler_ssh_key" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "webhook-handler" { metadata { diff --git a/stacks/whisper/main.tf b/stacks/whisper/main.tf index 1c099c16..4f10e23c 100644 --- a/stacks/whisper/main.tf +++ b/stacks/whisper/main.tf @@ -1,14 +1,6 @@ variable "tls_secret_name" { type = string } +variable "nfs_server" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "whisper" { metadata { @@ -80,7 +72,7 @@ resource "kubernetes_deployment" "whisper" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/whisper" } } @@ -190,7 +182,7 @@ resource "kubernetes_deployment" "piper" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/whisper" } } diff --git a/stacks/woodpecker/main.tf b/stacks/woodpecker/main.tf index 25815fa7..90432b22 100644 --- a/stacks/woodpecker/main.tf +++ b/stacks/woodpecker/main.tf @@ -4,16 +4,9 @@ variable "woodpecker_github_client_secret" { type = string } variable "woodpecker_agent_secret" { type = string } variable "woodpecker_db_password" { type = string } variable "dbaas_postgresql_root_password" { type = string } +variable "nfs_server" { type = string } +variable "postgresql_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "woodpecker" { metadata { @@ -76,11 +69,11 @@ resource "kubernetes_job" "db_init" { <<-EOT set -e # Create user if not exists - PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h postgresql.dbaas.svc.cluster.local -U root -tc "SELECT 1 FROM pg_roles WHERE rolname='woodpecker'" | grep -q 1 || \ - PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h postgresql.dbaas.svc.cluster.local -U root -c "CREATE ROLE woodpecker WITH LOGIN PASSWORD '${var.woodpecker_db_password}'" + PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h ${var.postgresql_host} -U root -tc "SELECT 1 FROM pg_roles WHERE rolname='woodpecker'" | grep -q 1 || \ + PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h ${var.postgresql_host} -U root -c "CREATE ROLE woodpecker WITH LOGIN PASSWORD '${var.woodpecker_db_password}'" # Create database if not exists - PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h postgresql.dbaas.svc.cluster.local -U root -tc "SELECT 1 FROM pg_database WHERE datname='woodpecker'" | grep -q 1 || \ - PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h postgresql.dbaas.svc.cluster.local -U root -c "CREATE DATABASE woodpecker OWNER woodpecker" + PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h ${var.postgresql_host} -U root -tc "SELECT 1 FROM pg_database WHERE datname='woodpecker'" | grep -q 1 || \ + PGPASSWORD='${var.dbaas_postgresql_root_password}' psql -h ${var.postgresql_host} -U root -c "CREATE DATABASE woodpecker OWNER woodpecker" echo "Database init complete" EOT ] @@ -108,7 +101,7 @@ resource "kubernetes_persistent_volume" "woodpecker_server_data" { access_modes = ["ReadWriteOnce"] persistent_volume_source { nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/woodpecker" } } @@ -133,6 +126,7 @@ resource "helm_release" "woodpecker" { github_client_secret = var.woodpecker_github_client_secret agent_secret = var.woodpecker_agent_secret db_password = var.woodpecker_db_password + postgresql_host = var.postgresql_host }) ] diff --git a/stacks/woodpecker/values.yaml b/stacks/woodpecker/values.yaml index 6427930b..21f7f948 100644 --- a/stacks/woodpecker/values.yaml +++ b/stacks/woodpecker/values.yaml @@ -15,7 +15,7 @@ server: WOODPECKER_GITHUB_SECRET: "${github_client_secret}" WOODPECKER_AGENT_SECRET: "${agent_secret}" WOODPECKER_DATABASE_DRIVER: "postgres" - WOODPECKER_DATABASE_DATASOURCE: "postgres://woodpecker:${db_password}@postgresql.dbaas.svc.cluster.local:5432/woodpecker?sslmode=disable" + WOODPECKER_DATABASE_DATASOURCE: "postgres://woodpecker:${db_password}@${postgresql_host}:5432/woodpecker?sslmode=disable" WOODPECKER_PLUGINS_PRIVILEGED: "woodpeckerci/plugin-docker-buildx,plugins/docker" WOODPECKER_PLUGINS_TRUSTED_CLONE: "woodpeckerci/plugin-git,alpine" WOODPECKER_LOG_LEVEL: "info" diff --git a/stacks/ytdlp/main.tf b/stacks/ytdlp/main.tf index 484891d6..e8990ecf 100644 --- a/stacks/ytdlp/main.tf +++ b/stacks/ytdlp/main.tf @@ -2,16 +2,10 @@ variable "tls_secret_name" { type = string } variable "openrouter_api_key" { type = string } variable "slack_bot_token" { type = string } variable "slack_channel" { type = string } +variable "nfs_server" { type = string } +variable "redis_host" { type = string } +variable "ollama_host" { type = string } -locals { - tiers = { - core = "0-core" - cluster = "1-cluster" - gpu = "2-gpu" - edge = "3-edge" - aux = "4-aux" - } -} resource "kubernetes_namespace" "ytdlp" { metadata { @@ -100,7 +94,7 @@ resource "kubernetes_deployment" "ytdlp" { name = "data" nfs { path = "/mnt/main/ytdlp" - server = "10.0.10.15" + server = var.nfs_server } } # } @@ -247,7 +241,7 @@ resource "kubernetes_deployment" "yt_highlights" { } env { name = "REDIS_URL" - value = "redis://redis.redis.svc.cluster.local:6379/0" + value = "redis://${var.redis_host}:6379/0" } # Store model cache on NFS to avoid ephemeral storage eviction env { @@ -261,7 +255,7 @@ resource "kubernetes_deployment" "yt_highlights" { # Ollama fallback for when OpenRouter models fail env { name = "OLLAMA_URL" - value = "http://ollama.ollama.svc.cluster.local:11434" + value = "http://${var.ollama_host}:11434" } env { name = "OLLAMA_MODEL" @@ -290,7 +284,7 @@ resource "kubernetes_deployment" "yt_highlights" { volume { name = "data" nfs { - server = "10.0.10.15" + server = var.nfs_server path = "/mnt/main/ytdlp-highlights" } } diff --git a/terraform.tfvars b/terraform.tfvars index 0fe820b650c8b2c7ec3dd832610ee36b7d5208eb..770d5e4c3cf40ca4a5c8b1174e841a79794e7da1 100644 GIT binary patch literal 49184 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 diff --git a/terragrunt.hcl b/terragrunt.hcl index a75d0c13..b197928e 100644 --- a/terragrunt.hcl +++ b/terragrunt.hcl @@ -53,3 +53,21 @@ provider "helm" { } EOF } + +# Generate shared tiers locals for all stacks. +# Previously duplicated in 67+ stacks; now defined once here. +generate "tiers" { + path = "tiers.tf" + if_exists = "overwrite_terragrunt" + contents = <