feat(terraform): remove websites module and add m3uproxy and nginx modules

- Deleted the websites module from the Terraform configuration.
- Introduced a new m3uproxy module with resources for Kubernetes namespace, secrets, config maps, deployments, services, and cron jobs.
- Added configuration files for m3uproxy, including JSON and playlist resources.
- Created a new nginx module with resources for Kubernetes namespace, services, config maps, deployments, and ingress.
- Added necessary variables for both m3uproxy and nginx modules.
- Updated provider configurations for m3uproxy and nginx modules to specify required versions.
This commit is contained in:
2025-04-27 19:40:16 +02:00
parent 654cf500f9
commit 58fb1ea167
20 changed files with 192 additions and 68 deletions
+37
View File
@@ -0,0 +1,37 @@
module "websites" {
source = "../modules/nginx"
persistent_folder = local.persistent_folder
primary_domain = local.primary_domain
websites = local.websites
}
module "rustdesk" {
source = "../modules/rustdesk"
persistent_folder = local.persistent_folder
fqdn = local.rustdesk_fqdn
public_key = var.rustdesk_public_key
private_key = var.rustdesk_private_key
}
module "m3uproxy" {
source = "../modules/m3uproxy"
persistent_folder = local.persistent_folder
tag = local.m3uproxy_version
fqdn = local.m3uproxy_fqdn
# Secret and password for M3UProxy
secret = var.m3uproxy_secret
admin_password = var.m3uproxy_admin_password
# GeoIP settings
maxmind_account_id = var.maxmind_account_id
maxmind_license_key = var.maxmind_license_key
geoip_cron_schedule = local.geoip_cron_schedule
geoip_editions = local.geoip_editions
# Wireguard settings (VPN)
wireguard_private_key = var.wireguard_private_key
wireguard_public_key = var.wireguard_public_key
}
-574
View File
@@ -1,574 +0,0 @@
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
}
}
}
}
}
}
}
}
@@ -1,32 +0,0 @@
{
"port": 8080,
"playlist": "conf/playlist.json",
"epg": "https://m3upt.com/epg",
"default_timeout": 3,
"num_workers": 4,
"scan_time": 43200,
"security": {
"geoip": {
"database": "GeoIP/GeoLite2-Country.mmdb",
"whitelist": [
"PT",
"ST",
"BE"
],
"internal_networks": [
"10.1.0.0/16",
"10.152.183.0/24"
]
},
"allowed_cors_domains": [
"tv.alexpires.me"
]
},
"auth": {
"provider": "file",
"secret_key": "${m3uproxy_secret}",
"settings": {
"file_path": "cache/auth.json"
}
}
}
@@ -1,146 +0,0 @@
{
"providers": {
"m3upt": {
"provider": "file",
"config": {
"source": "https://m3upt.com/iptv"
},
"ignore_tags": {
"media": "true",
"group-title": "Beachcam",
"radio": "true"
}
},
"iptv_be": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/be.m3u"
}
},
"iptv_nl": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/nl.m3u"
}
},
"iptv_pt_samsung": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/pt_samsung.m3u"
}
},
"iptv_nl_samsung": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/nl_samsung.m3u"
}
},
"iptv_uk_rakuten": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/uk_rakuten.m3u"
}
},
"iptv_uk_samsung": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/uk_samsung.m3u"
}
},
"iptv_us_pluto": {
"provider": "file",
"config": {
"source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/us_pluto.m3u"
}
}
},
"providers_priority": [
"m3upt",
"iptv_be",
"iptv_nl",
"iptv_nl_samsung",
"iptv_pt_samsung",
"iptv_uk_rakuten",
"iptv_uk_samsung",
"iptv_us_pluto"
],
"overrides": {
"RTP1.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTP2.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"SIC.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"TVI.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTP3.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"SICNoticias.pt": {
"headers": {
"Origin": "https://sicnoticias.pt",
"DNT": "1",
"Connection": "keep-alive"
},
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"CNNPortugal.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTPMemoria.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTPAcores.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTPMadeira.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTPAfrica.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"SICNovelas.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"SICReplay.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"CNNBrasil.br": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"RTPInternacional.pt": {
"http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128"
},
"Rádio Comercial": {
"disable_remap": true
},
"Rádio Comercial Dance": {
"disable_remap": true
},
"Rádio Comercial By Night": {
"disable_remap": true
},
"Rádio Comercial One Hit Wonders": {
"disable_remap": true
},
"Rádio Comercial Kids": {
"disable_remap": true
},
"Rádio Comercial Portugal": {
"disable_remap": true
},
"Rádio Comercial Brasil": {
"disable_remap": true
},
"Rádio Comercial 90s": {
"disable_remap": true
},
"Rádio Comercial 2000s": {
"disable_remap": true
}
}
}
@@ -1,47 +0,0 @@
acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
acl localnet src fc00::/7 # RFC 4193 local private network range
acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
acl SSL_ports port 443
acl Safe_ports port 80 # http
acl Safe_ports port 21 # ftp
acl Safe_ports port 443 # https
acl Safe_ports port 70 # gopher
acl Safe_ports port 210 # wais
acl Safe_ports port 1025-65535 # unregistered ports
acl Safe_ports port 280 # http-mgmt
acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access allow localhost manager
http_access deny manager
http_access allow localhost
http_access allow localnet
http_access deny all
http_port 3128
dns_v4_first on
coredump_dir /var/spool/squid
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern . 0 20% 4320
logfile_rotate 0
via off
forwarded_for delete
follow_x_forwarded_for deny all
pipeline_prefetch on
request_header_access From deny all
request_header_access Server deny all
pid_filename none
access_log stdio:/dev/stdout
cache_log stdio:/dev/stderr
@@ -1,9 +0,0 @@
[Interface]
PrivateKey = ${wireguard_private_key}
Address = ${wireguard_address}
DNS = ${wireguard_dns}
[Peer]
PublicKey = ${wireguard_public_key}
AllowedIPs = ${wireguard_allowed_ips}
Endpoint = ${wireguard_endpoint}
-211
View File
@@ -1,211 +0,0 @@
locals {
rustdesk_path = "${local.persistent_folder}/rustdesk"
}
resource "kubernetes_namespace" "rustdesk" {
metadata {
name = "rustdesk"
}
}
resource "kubernetes_config_map" "rustdesk_config" {
metadata {
name = "rustdesk-config"
namespace = kubernetes_namespace.rustdesk.metadata[0].name
}
data = {
RELAY = local.rustdesk_fqdn
ENCRYPTED_ONLY = "1"
}
}
resource "kubernetes_secret" "rustdesk_keys" {
metadata {
name = "rustdesk-keys"
namespace = kubernetes_namespace.rustdesk.metadata[0].name
}
data = {
"id_ed25519" = var.rustdesk_private_key
"id_ed25519.pub" = var.rustdesk_public_key
}
}
resource "kubernetes_deployment" "rustdesk_deploy" {
metadata {
name = "rustdesk"
namespace = kubernetes_namespace.rustdesk.metadata[0].name
labels = {
app = "rustdesk-app"
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "rustdesk-app"
}
}
strategy {
type = "Recreate"
}
template {
metadata {
labels = {
app = "rustdesk-app"
}
}
spec {
container {
name = "rustdesk-container"
image = "rustdesk/rustdesk-server-s6:${local.rustdesk_version}"
env_from {
config_map_ref {
name = kubernetes_config_map.rustdesk_config.metadata[0].name
}
}
security_context {
allow_privilege_escalation = false
}
port {
name = "tcp-port-1"
container_port = 21115
host_port = 21115
protocol = "TCP"
}
port {
name = "tcp-port-2"
container_port = 21116
host_port = 21116
protocol = "TCP"
}
port {
name = "udp-port-1"
container_port = 21116
host_port = 21116
protocol = "UDP"
}
port {
name = "tcp-port-3"
container_port = 21117
host_port = 21117
protocol = "TCP"
}
volume_mount {
name = "rustdesk-data"
mount_path = "/data"
}
volume_mount {
name = "rust-keys"
mount_path = "/data/id_ed25519"
sub_path = "id_ed25519"
read_only = true
}
volume_mount {
name = "rust-keys"
mount_path = "/data/id_ed25519.pub"
sub_path = "id_ed25519.pub"
read_only = true
}
}
volume {
name = "rustdesk-data"
host_path {
path = local.rustdesk_path
}
}
volume {
name = "rust-keys"
secret {
secret_name = kubernetes_secret.rustdesk_keys.metadata[0].name
}
}
}
}
}
}
resource "kubernetes_service" "rustdesk_tcp_ports" {
metadata {
name = "rustdesk-tcp-ports"
namespace = kubernetes_namespace.rustdesk.metadata[0].name
}
spec {
selector = {
app = "rustdesk-app"
}
port {
name = "tcp-port-1"
port = 21115
target_port = 21115
}
port {
name = "tcp-port-2"
port = 21116
target_port = 21116
}
port {
name = "tcp-port-3"
port = 21117
target_port = 21117
}
type = "LoadBalancer"
}
lifecycle {
ignore_changes = [
metadata[0].annotations
]
}
}
resource "kubernetes_service" "rustdesk_udp_ports" {
metadata {
name = "rustdesk-udp-ports"
namespace = kubernetes_namespace.rustdesk.metadata[0].name
}
spec {
selector = {
app = "rustdesk-app"
}
port {
name = "udp-port-1"
port = 21116
target_port = 21116
protocol = "UDP"
}
type = "LoadBalancer"
}
lifecycle {
ignore_changes = [
metadata[0].annotations
]
}
}
-36
View File
@@ -1,36 +0,0 @@
module "websites" {
source = "../modules/websites"
persistent_folder = local.persistent_folder
primary_domain = local.primary_domain
websites = local.websites
}
moved {
from = kubernetes_namespace.website
to = module.websites.kubernetes_namespace.website
}
moved {
from = kubernetes_service_account.website_service_account
to = module.websites.kubernetes_service_account.website_service_account
}
moved {
from = kubernetes_service.website
to = module.websites.kubernetes_service.website
}
moved {
from = kubernetes_config_map.nginx_config
to = module.websites.kubernetes_config_map.nginx_config
}
moved {
from = kubernetes_ingress_v1.website_ingress
to = module.websites.kubernetes_ingress_v1.website_ingress
}
moved {
from = kubernetes_deployment.website
to = module.websites.kubernetes_deployment.website
}