locals { m3uproxy_path = "${local.persistent_folder}/m3uproxy" m3uproxy_cache_path = "${local.m3uproxy_path}/cache" squid_app_path = "${local.m3uproxy_path}/squid" geoip_path = "${local.m3uproxy_path}/geoip" wireguard_address = "192.168.5.3/32" wireguard_dns = "192.168.5.1" wireguard_allowed_ips = [ "192.168.5.1/32", "192.168.5.3/32", "0.0.0.0/0" ] wireguard_endpoint = "188.82.170.183" wireguard_endpoint_port = 1195 m3uproxy_config = templatefile("${path.module}/resources/m3uproxy/m3uproxy.json", { m3uproxy_secret = var.m3uproxy_secret }) } resource "kubernetes_namespace" "m3uproxy" { metadata { annotations = { name = "m3uproxy" } labels = { name = "m3uproxy" } name = "m3uproxy" } } resource "kubernetes_secret" "m3uproxy_admin_credentials" { metadata { name = "m3uproxy-credentials" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } data = { username = "tv_admin" password = var.m3uproxy_admin_password } type = "Opaque" } resource "kubernetes_secret" "geoip_maxmind_credentials" { metadata { name = "geoip-maxmind-credentials" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } data = { account_id = var.maxmind_account_id license_key = var.maxmind_license_key } type = "Opaque" } resource "kubernetes_service_account" "m3uproxy_service_account" { metadata { name = "m3uproxy-app-sa" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } automount_service_account_token = false } resource "kubernetes_config_map" "wireguard_config" { metadata { name = "wireguard-config" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } data = { "wireguard.conf" = templatefile("${path.module}/resources/wireguard/wireguard.conf.tpl", { wireguard_private_key = var.wireguard_private_key wireguard_public_key = var.wireguard_public_key wireguard_address = local.wireguard_address wireguard_dns = local.wireguard_dns wireguard_allowed_ips = join(", ", local.wireguard_allowed_ips) wireguard_endpoint = "${local.wireguard_endpoint}:${local.wireguard_endpoint_port}" }) } } resource "kubernetes_config_map" "squid_config" { metadata { name = "squid-config" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } data = { "squid.conf" = file("${path.module}/resources/squid/squid.conf") } } resource "kubernetes_config_map" "m3uproxy_config" { metadata { name = "m3uproxy-config" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } data = { "m3uproxy.json" = local.m3uproxy_config "playlist.json" = file("${path.module}/resources/m3uproxy/playlist.json") } } resource "kubernetes_deployment" "proxy_pt" { metadata { name = "proxy-pt" namespace = kubernetes_namespace.m3uproxy.metadata[0].name annotations = { "security.alpha.kubernetes.io/unsafe-sysctls" = "net.ipv4.conf.all.src_valid_mark=1" } } spec { replicas = 1 selector { match_labels = { app = "proxy-pt" } } strategy { type = "Recreate" } template { metadata { labels = { app = "proxy-pt" } } spec { container { name = "wireguard" image = "linuxserver/wireguard:${local.wireguard_version}" resources { requests = { memory = "64Mi" } } liveness_probe { exec { command = [ "/bin/sh", "-c", "wg show | grep -q transfer" ] } initial_delay_seconds = 90 period_seconds = 90 } security_context { privileged = true capabilities { add = ["NET_ADMIN"] } } env { name = "PUID" value = "1000" } env { name = "PGID" value = "1000" } env { name = "TZ" value = "Europe/Berlin" } volume_mount { name = "wireguard-config" mount_path = "/config/wg_confs" read_only = true } volume_mount { name = "lib-modules" mount_path = "/lib/modules" read_only = true } } container { name = "squid" image = "ubuntu/squid:${local.squid_version}" port { container_port = 3128 name = "proxy" protocol = "TCP" } readiness_probe { tcp_socket { port = "proxy" } initial_delay_seconds = 10 timeout_seconds = 4 } liveness_probe { tcp_socket { port = "proxy" } initial_delay_seconds = 10 timeout_seconds = 4 } resources { requests = { memory = "256Mi" } } security_context { allow_privilege_escalation = false run_as_user = 1000 run_as_group = 1000 } volume_mount { name = "squid-config" mount_path = "/etc/squid" read_only = true } volume_mount { name = "squid-data" mount_path = "/var/spol/squid" } } volume { name = "wireguard-config" config_map { name = kubernetes_config_map.wireguard_config.metadata[0].name } } volume { name = "lib-modules" host_path { path = "/lib/modules" } } volume { name = "squid-config" config_map { name = kubernetes_config_map.squid_config.metadata[0].name } } volume { name = "squid-data" host_path { path = local.squid_app_path } } } } } lifecycle { ignore_changes = [spec[0].template[0].metadata[0].annotations] replace_triggered_by = [ kubernetes_config_map.wireguard_config, kubernetes_config_map.squid_config ] } } # Create the Deployment resource "kubernetes_deployment" "m3uproxy" { metadata { name = "m3uproxy" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } spec { replicas = 1 selector { match_labels = { app = "m3uproxy" } } strategy { type = "RollingUpdate" } template { metadata { labels = { app = "m3uproxy" } } spec { service_account_name = kubernetes_service_account.m3uproxy_service_account.metadata[0].name container { name = "m3uproxy" image = "a13labs/m3uproxy:${local.m3uproxy_version}" image_pull_policy = "Always" resources { requests = { memory = "256Mi" } } port { container_port = 8080 name = "http" } readiness_probe { http_get { path = "/health" port = "http" } initial_delay_seconds = 5 period_seconds = 10 failure_threshold = 10 } env { name = "USERNAME" value_from { secret_key_ref { name = kubernetes_secret.m3uproxy_admin_credentials.metadata[0].name key = "username" } } } env { name = "PASSWORD" value_from { secret_key_ref { name = kubernetes_secret.m3uproxy_admin_credentials.metadata[0].name key = "password" } } } security_context { allow_privilege_escalation = false run_as_user = 1000 run_as_group = 1000 } volume_mount { name = "m3uproxy-config" mount_path = "/app/conf" read_only = true } volume_mount { name = "m3uproxy-app-cache" mount_path = "/app/cache" } volume_mount { name = "geoip-db" mount_path = "/app/GeoIP" read_only = true } } volume { name = "m3uproxy-config" config_map { name = kubernetes_config_map.m3uproxy_config.metadata[0].name } } volume { name = "m3uproxy-app-cache" host_path { path = local.m3uproxy_cache_path } } volume { name = "geoip-db" host_path { path = local.geoip_path } } } } } lifecycle { ignore_changes = [spec[0].template[0].metadata[0].annotations] } } resource "kubernetes_cron_job_v1" "geoip_update" { metadata { name = "geoip-update" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } spec { schedule = local.geoip_cron_schedule job_template { metadata { name = "geoip-update" } spec { template { metadata { labels = { app = "geoip-update" } } spec { container { name = "geoip-update" image = "${local.geoip_image}:${local.geoip_version}" env { name = "GEOIPUPDATE_ACCOUNT_ID" value_from { secret_key_ref { name = kubernetes_secret.geoip_maxmind_credentials.metadata[0].name key = "account_id" } } } env { name = "GEOIPUPDATE_LICENSE_KEY" value_from { secret_key_ref { name = kubernetes_secret.geoip_maxmind_credentials.metadata[0].name key = "license_key" } } } env { name = "GEOIPUPDATE_EDITION_IDS" value = join(" ", local.geoip_editions) } resources { requests = { memory = "64Mi" } } volume_mount { name = "geoip-db" mount_path = "/usr/share/GeoIP" } security_context { run_as_user = 1000 run_as_group = 1000 } } service_account_name = kubernetes_service_account.m3uproxy_service_account.metadata[0].name restart_policy = "OnFailure" volume { name = "geoip-db" host_path { path = local.geoip_path } } } } } } } } resource "kubernetes_service" "m3uproxy" { metadata { name = "http" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } spec { selector = { app = "m3uproxy" } port { port = 8080 target_port = "http" } cluster_ip = "None" } } resource "kubernetes_service" "proxy_pt" { metadata { name = "proxy-pt" namespace = kubernetes_namespace.m3uproxy.metadata[0].name } spec { selector = { app = "proxy-pt" } port { port = 3128 } cluster_ip = "None" } } resource "kubernetes_ingress_v1" "m3uproxy_ingress_http" { metadata { name = "m3uproxy-ingress" namespace = kubernetes_namespace.m3uproxy.metadata[0].name annotations = { "kubernetes.io/ingress.class" = "public" "cert-manager.io/cluster-issuer" = "letsencrypt-prod" "kubernetes.io/tls-acme" = "true" "nginx.ingress.kubernetes.io/ssl-redirect" = "true" } } spec { tls { hosts = [local.m3uproxy_fqdn] secret_name = "m3uproxy-tls" } rule { host = local.m3uproxy_fqdn http { path { backend { service { name = "http" port { number = 8080 } } } } } } } }