diff --git a/main.tf b/main.tf index 7fb881e6..a6873c71 100644 --- a/main.tf +++ b/main.tf @@ -161,6 +161,7 @@ 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 "coturn_turn_secret" { type = string } variable "k8s_users" { type = map(any) @@ -703,12 +704,13 @@ module "kubernetes_cluster" { affine_postgresql_password = var.affine_postgresql_password health_postgresql_password = var.health_postgresql_password health_secret_key = var.health_secret_key - openclaw_ssh_key = var.openclaw_ssh_key - openclaw_skill_secrets = var.openclaw_skill_secrets + openclaw_ssh_key = var.openclaw_ssh_key + openclaw_skill_secrets = var.openclaw_skill_secrets gemini_api_key = var.gemini_api_key llama_api_key = var.llama_api_key brave_api_key = var.brave_api_key modal_api_key = var.modal_api_key + coturn_turn_secret = var.coturn_turn_secret k8s_users = var.k8s_users ssh_private_key = var.ssh_private_key diff --git a/modules/kubernetes/coturn/main.tf b/modules/kubernetes/coturn/main.tf new file mode 100644 index 00000000..5d18cc4e --- /dev/null +++ b/modules/kubernetes/coturn/main.tf @@ -0,0 +1,192 @@ +variable "tls_secret_name" {} +variable "tier" { type = string } +variable "turn_secret" { type = string } + +locals { + turn_realm = "viktorbarzin.me" + turn_port = 3478 + # Small relay range — 100 ports is plenty for a home lab (~50 concurrent streams) + min_port = 49152 + max_port = 49252 +} + +resource "kubernetes_namespace" "coturn" { + metadata { + name = "coturn" + labels = { + tier = var.tier + } + } +} + +module "tls_secret" { + source = "../setup_tls_secret" + namespace = kubernetes_namespace.coturn.metadata[0].name + tls_secret_name = var.tls_secret_name +} + +resource "kubernetes_config_map" "coturn_config" { + metadata { + name = "coturn-config" + namespace = kubernetes_namespace.coturn.metadata[0].name + } + + data = { + "turnserver.conf" = <<-EOF + # TURN server configuration + listening-port=${local.turn_port} + fingerprint + lt-cred-mech + use-auth-secret + static-auth-secret=${var.turn_secret} + realm=${local.turn_realm} + server-name=turn.${local.turn_realm} + + # Network — use 0.0.0.0, coturn auto-detects pod IP + listening-ip=0.0.0.0 + + # Media relay port range (narrow — 100 ports) + min-port=${local.min_port} + max-port=${local.max_port} + + # Logging + verbose + no-stdout-log + syslog + + # Security + no-multicast-peers + no-cli + no-tlsv1 + no-tlsv1_1 + + # Performance + total-quota=100 + stale-nonce=600 + max-bps=0 + EOF + } +} + +resource "kubernetes_deployment" "coturn" { + metadata { + name = "coturn" + namespace = kubernetes_namespace.coturn.metadata[0].name + labels = { + app = "coturn" + tier = var.tier + } + } + + spec { + replicas = 1 + strategy { + type = "RollingUpdate" + rolling_update { + max_unavailable = 0 + max_surge = 1 + } + } + selector { + match_labels = { + app = "coturn" + } + } + + template { + metadata { + labels = { + app = "coturn" + } + } + + spec { + container { + name = "coturn" + image = "coturn/coturn:latest" + args = ["-c", "/etc/turnserver/turnserver.conf"] + + # STUN/TURN signaling port + port { + name = "turn-udp" + container_port = local.turn_port + protocol = "UDP" + } + port { + name = "turn-tcp" + container_port = local.turn_port + protocol = "TCP" + } + + volume_mount { + name = "config" + mount_path = "/etc/turnserver" + read_only = true + } + + resources { + requests = { + cpu = "100m" + memory = "128Mi" + } + limits = { + cpu = "1" + memory = "512Mi" + } + } + } + + volume { + name = "config" + config_map { + name = kubernetes_config_map.coturn_config.metadata[0].name + } + } + } + } + } +} + +# LoadBalancer service with MetalLB — exposes STUN/TURN signaling + relay ports +resource "kubernetes_service" "coturn" { + metadata { + name = "coturn" + namespace = kubernetes_namespace.coturn.metadata[0].name + annotations = { + "metallb.universe.tf/loadBalancerIPs" = "10.0.20.200" + "metallb.universe.tf/allow-shared-ip" = "shared" + } + } + + spec { + type = "LoadBalancer" + selector = { + app = "coturn" + } + + # STUN/TURN signaling + port { + name = "turn-udp" + port = local.turn_port + target_port = local.turn_port + protocol = "UDP" + } + port { + name = "turn-tcp" + port = local.turn_port + target_port = local.turn_port + protocol = "TCP" + } + + # Relay port range (49152-49252) + dynamic "port" { + for_each = range(local.min_port, local.max_port + 1) + content { + name = "relay-${port.value}" + port = port.value + target_port = port.value + protocol = "UDP" + } + } + } +} diff --git a/modules/kubernetes/main.tf b/modules/kubernetes/main.tf index 11c8f469..45cba916 100644 --- a/modules/kubernetes/main.tf +++ b/modules/kubernetes/main.tf @@ -132,6 +132,7 @@ variable "modal_api_key" { type = string } variable "gemini_api_key" { type = string } variable "llama_api_key" { type = string } variable "brave_api_key" { type = string } +variable "coturn_turn_secret" { type = string } variable "k8s_users" { type = map(any) @@ -159,7 +160,7 @@ locals { 3 : ["reverse-proxy"], # Cluster admin services (k8s-dashboard chart repo still 404) 4 : [ "mailserver", "shadowsocks", "webhook_handler", "tuya-bridge", "dawarich", "owntracks", "nextcloud", - "calibre", "onlyoffice", "f1-stream", "rybbit", "isponsorblocktv", "actualbudget" + "calibre", "onlyoffice", "f1-stream", "rybbit", "isponsorblocktv", "actualbudget", "coturn" ], # Activel used services # Optional services 5 : [ @@ -256,6 +257,16 @@ module "f1-stream" { depends_on = [null_resource.core_services] } +module "coturn" { + source = "./coturn" + for_each = contains(local.active_modules, "coturn") ? { coturn = true } : {} + tls_secret_name = var.tls_secret_name + tier = local.tiers.edge + turn_secret = var.coturn_turn_secret + + depends_on = [null_resource.core_services] +} + module "hackmd" { source = "./hackmd" for_each = contains(local.active_modules, "hackmd") ? { hackmd = true } : {}