From d1267f816bde51d8b8a90a286192368cc2bb1487 Mon Sep 17 00:00:00 2001 From: Alexandre Pires Date: Mon, 5 May 2025 19:56:43 +0200 Subject: [PATCH] feat(mail): add Dovecot and Getmail configurations with ingress setup for SFTPGo --- terraform/k8sapps/apps.tf | 3 +- terraform/k8sapps/config.tf | 1 + terraform/k8sapps/resources/alexpires.me.conf | 4 +- terraform/modules/apps/mail/dovecot.tf | 227 +++++++++ terraform/modules/apps/mail/getmail.tf | 79 +++ terraform/modules/apps/mail/main.tf | 482 ------------------ terraform/modules/apps/mail/postfix.tf | 172 +++++++ terraform/modules/apps/sftpgo/config.tf | 2 +- terraform/modules/apps/sftpgo/ingress.tf | 41 ++ terraform/modules/apps/sftpgo/variables.tf | 5 + 10 files changed, 530 insertions(+), 486 deletions(-) create mode 100644 terraform/modules/apps/mail/dovecot.tf create mode 100644 terraform/modules/apps/mail/getmail.tf create mode 100644 terraform/modules/apps/mail/postfix.tf create mode 100644 terraform/modules/apps/sftpgo/ingress.tf diff --git a/terraform/k8sapps/apps.tf b/terraform/k8sapps/apps.tf index 25743bc..fca8ef7 100644 --- a/terraform/k8sapps/apps.tf +++ b/terraform/k8sapps/apps.tf @@ -88,7 +88,8 @@ module "sftpgo" { admin_username = "admin" admin_password = var.sftpgo_admin_password - tag = local.sftpgo_version + tag = local.sftpgo_version + fqdn = local.sftpgo_fqdn smtp_host = local.scaleway_smtp_host smtp_port = local.scaleway_smtp_port diff --git a/terraform/k8sapps/config.tf b/terraform/k8sapps/config.tf index 62119f4..7b3e086 100644 --- a/terraform/k8sapps/config.tf +++ b/terraform/k8sapps/config.tf @@ -21,6 +21,7 @@ locals { # sftpgo sftpgo_version = "2.6.x" + sftpgo_fqdn = "webdav.${local.primary_domain}" # m3uproxy m3uproxy_version = "latest" diff --git a/terraform/k8sapps/resources/alexpires.me.conf b/terraform/k8sapps/resources/alexpires.me.conf index f59dc33..cc8f60a 100644 --- a/terraform/k8sapps/resources/alexpires.me.conf +++ b/terraform/k8sapps/resources/alexpires.me.conf @@ -20,8 +20,8 @@ server { application/rss+xml image/svg+xml; - location ^~ /storage { - proxy_pass http://http.sftpgo.svc.cluster.local:8080/storage; + location ^~ /config/dav { + proxy_pass http://http.sftpgo.svc.cluster.local:8080/config/dav; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/terraform/modules/apps/mail/dovecot.tf b/terraform/modules/apps/mail/dovecot.tf new file mode 100644 index 0000000..77f9b5c --- /dev/null +++ b/terraform/modules/apps/mail/dovecot.tf @@ -0,0 +1,227 @@ +resource "kubernetes_secret" "mail_crypt_certs" { + metadata { + name = "mail-crypt-certs" + namespace = kubernetes_namespace.mail.metadata[0].name + } + data = { + "ecprivkey.pem" = var.mail_crypt_private_key + "ecpubkey.pem" = var.mail_crypt_public_key + } + type = "Opaque" +} + +resource "kubernetes_secret" "dovecot_users" { + metadata { + name = "dovecot-users" + namespace = kubernetes_namespace.mail.metadata[0].name + } + data = { + "users" = local.dovecot_users + } +} + +resource "kubernetes_config_map" "dovecot_config" { + metadata { + name = "dovecot-config" + namespace = kubernetes_namespace.mail.metadata[0].name + } + + data = { + "dovecot.conf" = local.dovecot_config + } +} + +resource "kubernetes_config_map" "dovecot_sieve" { + metadata { + name = "dovecot-sieve" + namespace = kubernetes_namespace.mail.metadata[0].name + } + + data = { + "sieve" = local.dovecot_sieve + } +} + +resource "kubernetes_deployment" "dovecot" { + metadata { + namespace = kubernetes_namespace.mail.metadata[0].name + name = "dovecot" + labels = { + app = "dovecot" + } + } + spec { + replicas = 1 + selector { + match_labels = { + app = "dovecot" + } + } + strategy { + type = "Recreate" + } + template { + metadata { + labels = { + app = "dovecot" + } + annotations = { + "checksum/dovecot" = local.dovecot_config_checksum + "checksum/sieve" = local.dovecot_sieve_checksum + # Commented out to avoid unnecessary restarts + # "checksum/users" = local.dovecot_users_checksum + } + } + spec { + service_account_name = kubernetes_service_account.mail_app.metadata[0].name + container { + name = "dovecot" + image = "dovecot/dovecot:${var.dovecot_tag}" + port { + container_port = 31993 + host_port = 993 + } + port { + container_port = 31024 + } + port { + container_port = 31000 + } + volume_mount { + name = "email-storage" + mount_path = "/var/mail" + } + volume_mount { + name = "users" + mount_path = "/etc/dovecot/users" + sub_path = "users" + read_only = true + } + volume_mount { + name = "dovecot-config" + mount_path = "/etc/dovecot/dovecot.conf" + sub_path = "dovecot.conf" + read_only = true + } + volume_mount { + name = "dovecot-sieve" + mount_path = "/etc/dovecot/sieve/default.sieve" + sub_path = "sieve" + read_only = true + } + volume_mount { + name = "certs" + mount_path = "/etc/letsencrypt" + read_only = true + } + volume_mount { + name = "mail-crypt-certs" + mount_path = "/etc/mail-crypt" + read_only = true + } + } + volume { + name = "email-storage" + host_path { + path = local.mailboxes_path + } + } + volume { + name = "users" + secret { + secret_name = kubernetes_secret.dovecot_users.metadata[0].name + } + } + volume { + name = "dovecot-config" + config_map { + name = kubernetes_config_map.dovecot_config.metadata[0].name + } + } + volume { + name = "certs" + secret { + secret_name = "mail-tls" + } + } + volume { + name = "dovecot-sieve" + config_map { + name = kubernetes_config_map.dovecot_sieve.metadata[0].name + } + } + volume { + name = "mail-crypt-certs" + secret { + secret_name = kubernetes_secret.mail_crypt_certs.metadata[0].name + } + } + } + } + } + depends_on = [kubernetes_manifest.mail_certificate, kubernetes_service_account.mail_app] + lifecycle { + ignore_changes = [spec[0].template[0].metadata[0].annotations["kubectl.kubernetes.io/restartedAt"]] + } +} + +resource "kubernetes_service" "dovecot" { + metadata { + namespace = kubernetes_namespace.mail.metadata[0].name + name = "dovecot" + } + + spec { + selector = { + app = "dovecot" + } + + port { + name = "imap" + port = 993 + target_port = 31993 + } + + type = "LoadBalancer" + } + + lifecycle { + ignore_changes = [ + metadata[0].annotations + ] + } +} + +resource "kubernetes_service" "dovecot_lmtp" { + metadata { + name = "dovecot-lmtp" + namespace = kubernetes_namespace.mail.metadata[0].name + } + + spec { + selector = { + app = "dovecot" + } + port { + port = 31024 + } + cluster_ip = "None" + } +} + +resource "kubernetes_service" "dovecot_auth" { + metadata { + name = "dovecot-auth" + namespace = kubernetes_namespace.mail.metadata[0].name + } + + spec { + selector = { + app = "dovecot" + } + port { + port = 31000 + } + cluster_ip = "None" + } +} diff --git a/terraform/modules/apps/mail/getmail.tf b/terraform/modules/apps/mail/getmail.tf new file mode 100644 index 0000000..52ad3d3 --- /dev/null +++ b/terraform/modules/apps/mail/getmail.tf @@ -0,0 +1,79 @@ +resource "kubernetes_config_map" "getmail_config" { + metadata { + name = "getmail-config" + namespace = kubernetes_namespace.mail.metadata[0].name + } + + data = local.getmail_config +} + +resource "kubernetes_cron_job_v1" "fetch_emails" { + metadata { + namespace = kubernetes_namespace.mail.metadata[0].name + name = "fetch-emails" + } + + spec { + schedule = var.fetch_emails_schedule + + job_template { + metadata { + name = "fetch-emails" + } + spec { + backoff_limit = 1 + parallelism = 1 + completions = 1 + active_deadline_seconds = 300 + + template { + metadata { + labels = { + app = "fetch-emails" + } + } + + spec { + container { + name = "getmail" + image = "a13labs/getmail6:${var.getmail_tag}" + args = concat(["--getmaildir", "/var/getmail"], flatten([for job in local.getmail_jobs : ["-r", "/getmail/getmailrc_${job.name}"]])) + + volume_mount { + name = "getmail-config" + mount_path = "/getmail" + read_only = true + } + + security_context { + run_as_user = 1000 + run_as_group = 1000 + } + + volume_mount { + name = "getmail-cache" + mount_path = "/var/getmail" + } + } + + restart_policy = "Never" + + volume { + name = "getmail-config" + config_map { + name = "getmail-config" + } + } + + volume { + name = "getmail-cache" + host_path { + path = local.getmail_path + } + } + } + } + } + } + } +} diff --git a/terraform/modules/apps/mail/main.tf b/terraform/modules/apps/mail/main.tf index b2a7081..0484a77 100644 --- a/terraform/modules/apps/mail/main.tf +++ b/terraform/modules/apps/mail/main.tf @@ -10,16 +10,6 @@ resource "kubernetes_namespace" "mail" { } } -resource "kubernetes_secret" "dovecot_users" { - metadata { - name = "dovecot-users" - namespace = kubernetes_namespace.mail.metadata[0].name - } - data = { - "users" = local.dovecot_users - } -} - resource "kubernetes_secret" "relay_mail_credentials" { metadata { name = "relay-mail-credentials" @@ -32,19 +22,6 @@ resource "kubernetes_secret" "relay_mail_credentials" { type = "Opaque" } -resource "kubernetes_secret" "mail_crypt_certs" { - metadata { - name = "mail-crypt-certs" - namespace = kubernetes_namespace.mail.metadata[0].name - } - data = { - "ecprivkey.pem" = var.mail_crypt_private_key - "ecpubkey.pem" = var.mail_crypt_public_key - } - type = "Opaque" -} - - resource "kubernetes_service_account" "mail_app" { metadata { @@ -55,58 +32,6 @@ resource "kubernetes_service_account" "mail_app" { automount_service_account_token = false } -resource "kubernetes_config_map" "postfix_config" { - metadata { - name = "postfix-config" - namespace = kubernetes_namespace.mail.metadata[0].name - } - - data = { - "run.config" = local.postfix_run_config - } -} - -resource "kubernetes_secret" "bcc_map" { - metadata { - name = "bcc-map" - namespace = kubernetes_namespace.mail.metadata[0].name - } - data = { - "bcc_map" = local.postfix_bcc_map - } -} - -resource "kubernetes_config_map" "getmail_config" { - metadata { - name = "getmail-config" - namespace = kubernetes_namespace.mail.metadata[0].name - } - - data = local.getmail_config -} - -resource "kubernetes_config_map" "dovecot_config" { - metadata { - name = "dovecot-config" - namespace = kubernetes_namespace.mail.metadata[0].name - } - - data = { - "dovecot.conf" = local.dovecot_config - } -} - -resource "kubernetes_config_map" "dovecot_sieve" { - metadata { - name = "dovecot-sieve" - namespace = kubernetes_namespace.mail.metadata[0].name - } - - data = { - "sieve" = local.dovecot_sieve - } -} - resource "kubernetes_manifest" "mail_certificate" { manifest = { apiVersion = "cert-manager.io/v1" @@ -126,410 +51,3 @@ resource "kubernetes_manifest" "mail_certificate" { } } } - -resource "kubernetes_cron_job_v1" "fetch_emails" { - metadata { - namespace = kubernetes_namespace.mail.metadata[0].name - name = "fetch-emails" - } - - spec { - schedule = var.fetch_emails_schedule - - job_template { - metadata { - name = "fetch-emails" - } - spec { - backoff_limit = 1 - parallelism = 1 - completions = 1 - active_deadline_seconds = 300 - - template { - metadata { - labels = { - app = "fetch-emails" - } - } - - spec { - container { - name = "getmail" - image = "a13labs/getmail6:${var.getmail_tag}" - args = concat(["--getmaildir", "/var/getmail"], flatten([for job in local.getmail_jobs : ["-r", "/getmail/getmailrc_${job.name}"]])) - - volume_mount { - name = "getmail-config" - mount_path = "/getmail" - read_only = true - } - - security_context { - run_as_user = 1000 - run_as_group = 1000 - } - - volume_mount { - name = "getmail-cache" - mount_path = "/var/getmail" - } - } - - - restart_policy = "Never" - - volume { - name = "getmail-config" - config_map { - name = "getmail-config" - } - } - - volume { - name = "getmail-cache" - host_path { - path = local.getmail_path - } - } - } - } - } - } - } -} - -resource "kubernetes_deployment" "dovecot" { - metadata { - namespace = kubernetes_namespace.mail.metadata[0].name - name = "dovecot" - labels = { - app = "dovecot" - } - } - spec { - replicas = 1 - selector { - match_labels = { - app = "dovecot" - } - } - strategy { - type = "Recreate" - } - template { - metadata { - labels = { - app = "dovecot" - } - annotations = { - "checksum/dovecot" = local.dovecot_config_checksum - "checksum/sieve" = local.dovecot_sieve_checksum - # Commented out to avoid unnecessary restarts - # "checksum/users" = local.dovecot_users_checksum - } - } - spec { - service_account_name = kubernetes_service_account.mail_app.metadata[0].name - container { - name = "dovecot" - image = "dovecot/dovecot:${var.dovecot_tag}" - port { - container_port = 31993 - host_port = 993 - } - port { - container_port = 31024 - } - port { - container_port = 31000 - } - volume_mount { - name = "email-storage" - mount_path = "/var/mail" - } - volume_mount { - name = "users" - mount_path = "/etc/dovecot/users" - sub_path = "users" - read_only = true - } - volume_mount { - name = "dovecot-config" - mount_path = "/etc/dovecot/dovecot.conf" - sub_path = "dovecot.conf" - read_only = true - } - volume_mount { - name = "dovecot-sieve" - mount_path = "/etc/dovecot/sieve/default.sieve" - sub_path = "sieve" - read_only = true - } - volume_mount { - name = "certs" - mount_path = "/etc/letsencrypt" - read_only = true - } - volume_mount { - name = "mail-crypt-certs" - mount_path = "/etc/mail-crypt" - read_only = true - } - } - volume { - name = "email-storage" - host_path { - path = local.mailboxes_path - } - } - volume { - name = "users" - secret { - secret_name = kubernetes_secret.dovecot_users.metadata[0].name - } - } - volume { - name = "dovecot-config" - config_map { - name = kubernetes_config_map.dovecot_config.metadata[0].name - } - } - volume { - name = "certs" - secret { - secret_name = "mail-tls" - } - } - volume { - name = "dovecot-sieve" - config_map { - name = kubernetes_config_map.dovecot_sieve.metadata[0].name - } - } - volume { - name = "mail-crypt-certs" - secret { - secret_name = kubernetes_secret.mail_crypt_certs.metadata[0].name - } - } - } - } - } - depends_on = [kubernetes_manifest.mail_certificate, kubernetes_service_account.mail_app] - lifecycle { - ignore_changes = [spec[0].template[0].metadata[0].annotations["kubectl.kubernetes.io/restartedAt"]] - } -} - -resource "kubernetes_deployment" "postfix" { - metadata { - namespace = kubernetes_namespace.mail.metadata[0].name - name = "postfix" - labels = { - app = "postfix" - } - } - spec { - replicas = 1 - selector { - match_labels = { - app = "postfix" - } - } - strategy { - type = "Recreate" - } - template { - metadata { - labels = { - app = "postfix" - } - annotations = { - "checksum/runconfig" = local.postfix_run_config_checksum - "checksum/bccmap" = local.postfix_bcc_map_checksum - } - } - spec { - service_account_name = kubernetes_service_account.mail_app.metadata[0].name - container { - name = "postfix" - image = "tozd/postfix:${var.postfix_tag}" - port { - container_port = 465 - host_port = 465 - } - dynamic "env" { - for_each = local.postfix_env_vars - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = local.postfix_secrets - content { - name = env.key - value_from { - secret_key_ref { - key = env.value.key - name = env.value.name - } - } - } - } - - volume_mount { - name = "email-storage" - mount_path = "/var/spool/postfix" - } - volume_mount { - name = "postfix-run-config" - mount_path = "/etc/service/postfix/run.config" - sub_path = "run.config" - read_only = true - } - volume_mount { - name = "bcc-map" - mount_path = "/etc/postfix/bcc_map" - sub_path = "bcc_map" - read_only = true - } - volume_mount { - name = "certs" - mount_path = "/etc/letsencrypt" - read_only = true - } - volume_mount { - name = "postfix-logs" - mount_path = "/var/log/postfix" - read_only = false - } - } - volume { - name = "email-storage" - host_path { - path = local.postfix_path - } - } - volume { - name = "postfix-run-config" - config_map { - name = kubernetes_config_map.postfix_config.metadata[0].name - } - } - volume { - name = "bcc-map" - secret { - secret_name = kubernetes_secret.bcc_map.metadata[0].name - } - } - volume { - name = "certs" - secret { - secret_name = "mail-tls" - } - } - volume { - name = "postfix-logs" - host_path { - path = "/var/log/postfix" - } - } - } - } - } - depends_on = [kubernetes_manifest.mail_certificate, kubernetes_service_account.mail_app] - lifecycle { - ignore_changes = [spec[0].template[0].metadata[0].annotations["kubectl.kubernetes.io/restartedAt"]] - } -} - - -resource "kubernetes_service" "dovecot" { - metadata { - namespace = kubernetes_namespace.mail.metadata[0].name - name = "dovecot" - } - - spec { - selector = { - app = "dovecot" - } - - port { - name = "imap" - port = 993 - target_port = 31993 - } - - type = "LoadBalancer" - } - - lifecycle { - ignore_changes = [ - metadata[0].annotations - ] - } -} - -resource "kubernetes_service" "dovecot_lmtp" { - metadata { - name = "dovecot-lmtp" - namespace = kubernetes_namespace.mail.metadata[0].name - } - - spec { - selector = { - app = "dovecot" - } - port { - port = 31024 - } - cluster_ip = "None" - } -} - -resource "kubernetes_service" "dovecot_auth" { - metadata { - name = "dovecot-auth" - namespace = kubernetes_namespace.mail.metadata[0].name - } - - spec { - selector = { - app = "dovecot" - } - port { - port = 31000 - } - cluster_ip = "None" - } -} - -resource "kubernetes_service" "postfix" { - metadata { - namespace = kubernetes_namespace.mail.metadata[0].name - name = "postfix" - } - - spec { - selector = { - app = "postfix" - } - - port { - name = "smtp" - port = 465 - target_port = 465 - } - - type = "LoadBalancer" - } - - lifecycle { - ignore_changes = [ - metadata[0].annotations - ] - } -} diff --git a/terraform/modules/apps/mail/postfix.tf b/terraform/modules/apps/mail/postfix.tf new file mode 100644 index 0000000..aecdf93 --- /dev/null +++ b/terraform/modules/apps/mail/postfix.tf @@ -0,0 +1,172 @@ + +resource "kubernetes_config_map" "postfix_config" { + metadata { + name = "postfix-config" + namespace = kubernetes_namespace.mail.metadata[0].name + } + + data = { + "run.config" = local.postfix_run_config + } +} + +resource "kubernetes_secret" "bcc_map" { + metadata { + name = "bcc-map" + namespace = kubernetes_namespace.mail.metadata[0].name + } + data = { + "bcc_map" = local.postfix_bcc_map + } +} + + +resource "kubernetes_deployment" "postfix" { + metadata { + namespace = kubernetes_namespace.mail.metadata[0].name + name = "postfix" + labels = { + app = "postfix" + } + } + spec { + replicas = 1 + selector { + match_labels = { + app = "postfix" + } + } + strategy { + type = "Recreate" + } + template { + metadata { + labels = { + app = "postfix" + } + annotations = { + "checksum/runconfig" = local.postfix_run_config_checksum + "checksum/bccmap" = local.postfix_bcc_map_checksum + } + } + spec { + service_account_name = kubernetes_service_account.mail_app.metadata[0].name + container { + name = "postfix" + image = "tozd/postfix:${var.postfix_tag}" + port { + container_port = 465 + host_port = 465 + } + dynamic "env" { + for_each = local.postfix_env_vars + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = local.postfix_secrets + content { + name = env.key + value_from { + secret_key_ref { + key = env.value.key + name = env.value.name + } + } + } + } + + volume_mount { + name = "email-storage" + mount_path = "/var/spool/postfix" + } + volume_mount { + name = "postfix-run-config" + mount_path = "/etc/service/postfix/run.config" + sub_path = "run.config" + read_only = true + } + volume_mount { + name = "bcc-map" + mount_path = "/etc/postfix/bcc_map" + sub_path = "bcc_map" + read_only = true + } + volume_mount { + name = "certs" + mount_path = "/etc/letsencrypt" + read_only = true + } + volume_mount { + name = "postfix-logs" + mount_path = "/var/log/postfix" + read_only = false + } + } + volume { + name = "email-storage" + host_path { + path = local.postfix_path + } + } + volume { + name = "postfix-run-config" + config_map { + name = kubernetes_config_map.postfix_config.metadata[0].name + } + } + volume { + name = "bcc-map" + secret { + secret_name = kubernetes_secret.bcc_map.metadata[0].name + } + } + volume { + name = "certs" + secret { + secret_name = "mail-tls" + } + } + volume { + name = "postfix-logs" + host_path { + path = "/var/log/postfix" + } + } + } + } + } + depends_on = [kubernetes_manifest.mail_certificate, kubernetes_service_account.mail_app] + lifecycle { + ignore_changes = [spec[0].template[0].metadata[0].annotations["kubectl.kubernetes.io/restartedAt"]] + } +} + +resource "kubernetes_service" "postfix" { + metadata { + namespace = kubernetes_namespace.mail.metadata[0].name + name = "postfix" + } + + spec { + selector = { + app = "postfix" + } + + port { + name = "smtp" + port = 465 + target_port = 465 + } + + type = "LoadBalancer" + } + + lifecycle { + ignore_changes = [ + metadata[0].annotations + ] + } +} diff --git a/terraform/modules/apps/sftpgo/config.tf b/terraform/modules/apps/sftpgo/config.tf index e09ee7e..fed45a0 100644 --- a/terraform/modules/apps/sftpgo/config.tf +++ b/terraform/modules/apps/sftpgo/config.tf @@ -44,7 +44,7 @@ locals { } } ], - "web_root" : "/storage", + "web_root" : "/config/dav", }, "telemetry" : { "bind_port" : 10000, diff --git a/terraform/modules/apps/sftpgo/ingress.tf b/terraform/modules/apps/sftpgo/ingress.tf new file mode 100644 index 0000000..76dae08 --- /dev/null +++ b/terraform/modules/apps/sftpgo/ingress.tf @@ -0,0 +1,41 @@ +resource "kubernetes_ingress_v1" "ingress" { + depends_on = [ + kubernetes_deployment.sftpgo, + ] + metadata { + name = "webdav-ingress" + namespace = kubernetes_namespace.sftpgo.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" + "nginx.ingress.kubernetes.io/proxy-body-size" = "4G" + "nginx.ingress.kubernetes.io/proxy-request-buffering" = "on" + "nginx.ingress.kubernetes.io/proxy-buffer-size" = "32k" + "nginx.ingress.kubernetes.io/proxy-buffers-number" = "16" + } + } + spec { + tls { + hosts = [var.fqdn] + secret_name = "webdav-tls" + } + rule { + host = var.fqdn + http { + path { + backend { + service { + name = "webdav" + port { + number = 8081 + } + } + } + } + } + } + } +} diff --git a/terraform/modules/apps/sftpgo/variables.tf b/terraform/modules/apps/sftpgo/variables.tf index c004ace..278a9d9 100644 --- a/terraform/modules/apps/sftpgo/variables.tf +++ b/terraform/modules/apps/sftpgo/variables.tf @@ -66,3 +66,8 @@ variable "secret_key" { type = string sensitive = true } + +variable "fqdn" { + description = "The fully qualified domain name for the Rustdesk server" + type = string +}