diff --git a/.env.example b/.env.example index b8dae2d..acba987 100755 --- a/.env.example +++ b/.env.example @@ -28,6 +28,8 @@ TF_VAR_hcloud_token= TF_VAR_healthchecksio_api_key= +TF_VAR_mailcow_api_key= + TF_VAR_ovh_application_key= TF_VAR_ovh_application_secret= TF_VAR_ovh_consumer_key= diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl index e76c19b..d722e02 100644 --- a/.terraform.lock.hcl +++ b/.terraform.lock.hcl @@ -69,20 +69,20 @@ provider "registry.opentofu.org/goauthentik/authentik" { } provider "registry.opentofu.org/hashicorp/aws" { - version = "5.72.1" + version = "5.73.0" constraints = "~> 5.0" hashes = [ - "h1:ckDAOn6cqqO2pJ226GYs8gIZif9TRmAuQEqnPL+LCgg=", - "zh:02ee636137e5f8cc9d6900c55a3f3c85e99166b51d17cf96bd62b27182dc7449", - "zh:04877c5ce0a0fef6b355decbfe4941e7d5f22d2b7062cd87f70dafe845d635c7", - "zh:3d129024594dcf2edac180b8276decc946f4f33d653f44d3c04c4c28b3f85dab", - "zh:6fc7ecf746791211d64a38361ce12303dfc3ddf0e609e6d854bc8f3a7f242234", - "zh:8d65352eeba3fef611c90b5161336b0cccf3fed8dc2c710537d6578925e2b189", - "zh:9c99b31104c80d885aad1846e2b2f25371cbc9c23281fb0e213be0101a415f2c", - "zh:b220737d06dc8ef3a6aa32055c1c633d08c27e046b5fb730f93969ef11abb928", - "zh:b741b0e79001765c8d2ffdb569f70c0d8b877b870b660e573f3bb6f42dd55f28", - "zh:d2434f271f261ccd28a85aa15627ee9cfc9c319a48eb6c0aeb1ffaf80b6ede20", - "zh:df4b2338e3e89d66c1697fae9be378db94cb0d7d309c9dc537024cb755b7e21a", + "h1:pQKtkpKb4hzErmZakSW+HHXeJubUlBKu1/p0C/b1UuI=", + "zh:16a345cb7265937b13f999d644a38c68661844462f5ac7fda33b5aa35d3fdb9f", + "zh:1a28b36980e55c430faf6dfd93744b42fedd455ec53e5d848d89f503b8f7808e", + "zh:4303bc542d832ced373f64f8f154989affebc04704ae55f00b404167a512891d", + "zh:6079983b35df36c0980c6a7eabc17e9697e573ed0359bb07dc8004c40341ee5d", + "zh:711419826ce136e64fa172faffcdd23c25ad47e05a2b55be5deb5ae663ae399a", + "zh:7ac9ddb634dc98040b8db683b9d829c6654bcbd2cb6c15edaa85029780ea9d57", + "zh:8ce89e19e7e1ae84fa9eae98f79dd8654d774daabe2cc73e85d05d2dd00fcd08", + "zh:be420d05ec4d31a3a393c19831da10664a109a5ad527b7043f7a75059d694008", + "zh:c6552f1c4246afd1b8607edc4c29639846a530487c82663c51ddfb998a259eca", + "zh:d5a2a931f83625105c9502398f391165add5a886d950f193fc5721ad18dec273", ] } @@ -172,6 +172,29 @@ provider "registry.opentofu.org/kristofferahl/healthchecksio" { ] } +provider "registry.opentofu.org/l-with/mailcow" { + version = "0.7.5" + constraints = "~> 0.7.5" + hashes = [ + "h1:gEiN/SOJl+T1V585/Pqk/Y3FkX8+An/M3zbztdEfmWk=", + "zh:0919018dfdab37f86b61dfe2ecd8d4b6a6532983edc6deab9e7f3d5ec1a45375", + "zh:16e513369e37f2d8fab43545940991c3ce2b140bb37c92bc77ec84240235ad26", + "zh:19bcf3660ac7545103cf999e0066442f9d6350db9654e1496726520cef287246", + "zh:1f6d827f5c0a2253550def77d2473bf62b72355930b5d00f59dc1b0af5aff953", + "zh:242d5cb545f1b20be24672e984fb78c27bf21da27c25ccbac8cd8c3142d32d83", + "zh:40a17c3734c330f2d0e11adb377b04d8bf11e799e78f4bacf2797ee589312756", + "zh:475ac6440db8cb80df1e8e5bb475f7dd73548fabd50e60e78e66ccd2e6e63baf", + "zh:48a67a019575ca784275dbcd9f7ee209012c0b311db8b82b91511f7970e1f9d2", + "zh:6dc3f2a073264cf79230811f528d3a916b8753031c0dad80b9999f64aa6951ba", + "zh:71d64c63cb4abca1fc920d694785551dd9ef15b5b601a6682ec647bae4acc881", + "zh:7a7fa7621ac582802329565a010a96114a1c8a5638b8aefe62095bdbefc1c988", + "zh:a11f6332a9d5e2d1ca01a906576d48dcf99e9f75c6e376157e35c24aef1039b9", + "zh:bec618cd75e300a8ae98852a70b1b56cd0c2bc61e4e1b11178029822fffc32b4", + "zh:c8132e507938516f2595a00b1bc19e666fe8a3df0077ca3bbeb9107dacd4fd2d", + "zh:cfff5048bc75345eda1bc6067e4e92c8b7c24d5fdd985fdb5d2e30997d644d15", + ] +} + provider "registry.opentofu.org/ovh/ovh" { version = "0.48.0" constraints = "~> 0.48.0" diff --git a/main.tf b/main.tf index 7504d70..8597f73 100644 --- a/main.tf +++ b/main.tf @@ -33,6 +33,10 @@ terraform { source = "goauthentik/authentik" version = "~> 2024.8.0" } + mailcow = { + source = "l-with/mailcow" + version = "~> 0.7.5" + } postgresql = { source = "cyrilgdn/postgresql" version = "~> 1.23.0" @@ -132,6 +136,11 @@ provider "authentik" { token = var.authentik_token } +provider "mailcow" { + host_name = var.mailcow_host_name + api_key = var.mailcow_api_key +} + provider "postgresql" { host = var.postgresql_host port = var.postgresql_port diff --git a/modules/services/mailcow.tf b/modules/services/mailcow.tf new file mode 100644 index 0000000..9cf00bd --- /dev/null +++ b/modules/services/mailcow.tf @@ -0,0 +1,17 @@ +resource "random_password" "mailcow_service_passwords" { + for_each = local.services_mail + length = 32 + special = false +} + +resource "mailcow_mailbox" "services" { + for_each = local.services_mail + domain = "serguzim.me" + full_name = each.value.mail + local_part = each.value.mail + password = random_password.mailcow_service_passwords[each.key].result + imap_access = false + pop3_access = false + sogo_access = false + quota = 128 +} diff --git a/modules/services/main.tf b/modules/services/main.tf index 4337d11..02e0134 100644 --- a/modules/services/main.tf +++ b/modules/services/main.tf @@ -4,6 +4,10 @@ terraform { source = "goauthentik/authentik" version = "~> 2024.8.0" } + mailcow = { + source = "l-with/mailcow" + version = "~> 0.7.5" + } postgresql = { source = "cyrilgdn/postgresql" version = "~> 1.23.0" @@ -15,4 +19,5 @@ locals { services_auth = {for key, val in var.services : key => val if val.auth} services_database = {for key, val in var.services : key => val if val.database} services_s3 = {for key, val in var.services : key => val if val.s3} + services_mail = {for key, val in var.services : key => val if val.mail != null} } diff --git a/modules/services/output.tf b/modules/services/output.tf index 14edcb8..53816ba 100644 --- a/modules/services/output.tf +++ b/modules/services/output.tf @@ -19,3 +19,13 @@ output "postgresql_data" { } sensitive = true } + +output "mailcow_data" { + value = { + for key in keys(mailcow_mailbox.services) : key => { + "address" = mailcow_mailbox.services[key].address + "password" = mailcow_mailbox.services[key].password + } + } + sensitive = true +} diff --git a/modules/services/variables.tf b/modules/services/variables.tf index d7657e1..ae59dff 100644 --- a/modules/services/variables.tf +++ b/modules/services/variables.tf @@ -18,5 +18,6 @@ variable "services" { auth_redirects = optional(list(string)) s3 = bool database = bool + mail = optional(string) })) } diff --git a/output.tf b/output.tf index 922f4d6..5b4e5ca 100644 --- a/output.tf +++ b/output.tf @@ -17,6 +17,11 @@ output "healthchecksio" { sensitive = true } +output "mailcow_data" { + value = module.services.mailcow_data + sensitive = true +} + output "postgresql_data" { value = module.services.postgresql_data sensitive = true diff --git a/playbooks/roles/authentik/vars/main.yml b/playbooks/roles/authentik/vars/main.yml index d03b16d..adaeb84 100644 --- a/playbooks/roles/authentik/vars/main.yml +++ b/playbooks/roles/authentik/vars/main.yml @@ -14,12 +14,12 @@ authentik_env: AUTHENTIK_EMAIL__HOST: "{{ mailer.host }}" AUTHENTIK_EMAIL__PORT: "{{ mailer.port }}" - AUTHENTIK_EMAIL__USERNAME: "{{ vault_authentik.mail.user }}" - AUTHENTIK_EMAIL__PASSWORD: "{{ vault_authentik.mail.pass }}" + AUTHENTIK_EMAIL__USERNAME: "{{ opentofu.mailcow_data.authentik.address }}" + AUTHENTIK_EMAIL__PASSWORD: "{{ opentofu.mailcow_data.authentik.password }}" AUTHENTIK_EMAIL__USE_TLS: true AUTHENTIK_EMAIL__USE_SSL: false AUTHENTIK_EMAIL__TIMEOUT: 10 - AUTHENTIK_EMAIL__FROM: auth@serguzim.me + AUTHENTIK_EMAIL__FROM: "{{ opentofu.mailcow_data.authentik.address }}" AUTHENTIK_AVATARS: none diff --git a/playbooks/roles/forgejo/vars/main.yml b/playbooks/roles/forgejo/vars/main.yml index 558b01b..75ba21f 100644 --- a/playbooks/roles/forgejo/vars/main.yml +++ b/playbooks/roles/forgejo/vars/main.yml @@ -51,11 +51,11 @@ forgejo_env: FORGEJO__mailer__ENABLED: true FORGEJO__mailer__PROTOCOL: smtp+starttls - FORGEJO__mailer__SMTP_ADDR: mail.serguzim.me - FORGEJO__mailer__SMTP_PORT: 587 - FORGEJO__mailer__FROM: Forgejo - FORGEJO__mailer__USER: git@serguzim.me - FORGEJO__mailer__PASSWD: "{{ vault_forgejo.mailer_passwd }}" + FORGEJO__mailer__SMTP_ADDR: "{{ mailer.host }}" + FORGEJO__mailer__SMTP_PORT: "{{ mailer.post }}" + FORGEJO__mailer__FROM: "git <{{ opentofu.mailcow_data.forgejo.address }}>" + FORGEJO__mailer__USER: "{{ opentofu.mailcow_data.forgejo.address }}" + FORGEJO__mailer__PASSWD: "{{ opentofu.mailcow_data.forgejo.password }}" FORGEJO__mailer__SEND_AS_PLAIN_TEXT: true FORGEJO__picture__DISABLE_GRAVATAR: true diff --git a/playbooks/roles/homebox/vars/main.yml b/playbooks/roles/homebox/vars/main.yml index 1ae0a50..422476f 100644 --- a/playbooks/roles/homebox/vars/main.yml +++ b/playbooks/roles/homebox/vars/main.yml @@ -5,11 +5,11 @@ homebox_svc: homebox_env: HBOX_OPTIONS_ALLOW_REGISTRATION: false - HBOX_MAILER_HOST: mail.serguzim.me - HBOX_MAILER_PORT: 587 - HBOX_MAILER_USERNAME: inventory@serguzim.me - HBOX_MAILER_PASSWORD: "{{ vault_homebox.mailer_passwd }}" - HBOX_MAILER_FROM: Homebox + HBOX_MAILER_HOST: "{{ mailer.host }}" + HBOX_MAILER_PORT: "{{ mailer.port }}" + HBOX_MAILER_USERNAME: "{{ opentofu.mailcow_data.homebox.address }}" + HBOX_MAILER_PASSWORD: "{{ opentofu.mailcow_data.homebox.password }}" + HBOX_MAILER_FROM: "homebox <{{ opentofu.mailcow_data.homebox.address }}>" HBOX_SWAGGER_SCHEMA: https homebox_compose: diff --git a/playbooks/roles/synapse/vars/main.yml b/playbooks/roles/synapse/vars/main.yml index f1e62ed..621ec7a 100644 --- a/playbooks/roles/synapse/vars/main.yml +++ b/playbooks/roles/synapse/vars/main.yml @@ -92,10 +92,10 @@ synapse_yml: email: smtp_host: mail.serguzim.me smtp_port: 587 - smtp_user: matrix@serguzim.me - smtp_pass: "{{ vault_synapse.mail.pass }}" + smtp_user: "{{ opentofu.mailcow_data.synapse.address }}" + smtp_pass: "{{ opentofu.mailcow_data.synapse.password }}" require_transport_security: true - notif_from: Matrix + notif_from: "matrix <{{ opentofu.mailcow_data.synapse.address }}>" synapse_compose: watchtower: true diff --git a/playbooks/roles/vikunja/vars/main.yml b/playbooks/roles/vikunja/vars/main.yml index f5c6848..7cd6e8c 100644 --- a/playbooks/roles/vikunja/vars/main.yml +++ b/playbooks/roles/vikunja/vars/main.yml @@ -28,9 +28,9 @@ vikunja_yml: enabled: true host: "{{ mailer.host }}" port: "{{ mailer.port }}" - username: "{{ vault_vikunja.mailer.user }}" - password: "{{ vault_vikunja.mailer.pass }}" - fromemail: "{{ vault_vikunja.mailer.user }}" + username: "{{ opentofu.mailcow_data.vikunja.address }}" + password: "{{ opentofu.mailcow_data.vikunja.password }}" + fromemail: "{{ opentofu.mailcow_data.vikunja.address }}" auth: local: diff --git a/playbooks/roles/watchtower/vars/main.yml b/playbooks/roles/watchtower/vars/main.yml index 78f4900..bdd5bb7 100644 --- a/playbooks/roles/watchtower/vars/main.yml +++ b/playbooks/roles/watchtower/vars/main.yml @@ -8,12 +8,12 @@ watchtower_env: # WATCHTOWER_NO_PULL: true WATCHTOWER_NOTIFICATIONS: email - WATCHTOWER_NOTIFICATION_EMAIL_FROM: "watchtower@serguzim.me" + WATCHTOWER_NOTIFICATION_EMAIL_FROM: "{{ opentofu.mailcow_data.watchtower.address }}" WATCHTOWER_NOTIFICATION_EMAIL_TO: "{{ admin_email }}" WATCHTOWER_NOTIFICATION_EMAIL_SERVER: "{{ mailer.host }}" WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT: "{{ mailer.port }}" - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER: "watchtower@serguzim.me" - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD: "{{ vault_watchtower.mailer.pass }}" + WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER: "{{ opentofu.mailcow_data.watchtower.address }}" + WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD: "{{ opentofu.mailcow_data.watchtower.password }}" WATCHTOWER_NOTIFICATION_EMAIL_DELAY: 5 watchtower_compose: diff --git a/services.auto.tfvars b/services.auto.tfvars index d8590e8..4f07a6f 100644 --- a/services.auto.tfvars +++ b/services.auto.tfvars @@ -26,6 +26,7 @@ services = { auth = false database = true s3 = false + mail = "auth" }, "backup" = { @@ -119,6 +120,7 @@ services = { auth_redirects = ["https://git.serguzim.me/user/oauth2/auth.serguzim.me/callback"] database = true s3 = true + mail = "git" }, "forgejo_runner" = { @@ -148,6 +150,7 @@ services = { auth_redirects = ["https://status.serguzim.me/authorization-code/callback"] database = false s3 = false + mail = "status" }, "homebox" = { @@ -170,6 +173,7 @@ services = { auth = false database = false s3 = false + mail = "inventory" }, "immich" = { @@ -436,6 +440,7 @@ services = { auth_redirects = ["https://matrix.serguzim.me/_synapse/client/oidc/callback"] database = true s3 = false + mail = "matrix" }, "tandoor" = { @@ -532,6 +537,7 @@ services = { auth_redirects = ["https://todo.serguzim.me/auth/openid/authserguzimme"] database = true s3 = false + mail = "todo" }, "watchtower" = { @@ -539,6 +545,7 @@ services = { auth = false database = false s3 = false + mail = "watchtower" }, "wiki_js" = { @@ -553,6 +560,7 @@ services = { auth_redirects = ["https://wiki.serguzim.me/login/f792bc7d-1a25-4437-944e-55eaf0111102/callback"] database = true s3 = false + mail = "wiki" }, "woodpecker" = { diff --git a/templates/infrastructure.d2.j2 b/templates/infrastructure.d2.j2 index b78bd65..86d5670 100644 --- a/templates/infrastructure.d2.j2 +++ b/templates/infrastructure.d2.j2 @@ -74,6 +74,13 @@ external: { external.scaleway.s3.{{ svc.name }} {% endif %} +{% if svc.mail %} +{{ svc.key }} -> {{ mail_key }}: { + style.stroke: "#C9B81F" +} +{{ mail_key }}.{{ svc.name }} +{% endif %} + {% endfor %}{# svc #} {% for svc in grid_svcs %} diff --git a/variables.tf b/variables.tf index fb508e5..ac7669e 100644 --- a/variables.tf +++ b/variables.tf @@ -72,6 +72,15 @@ variable "healthchecksio_api_key" { } +variable "mailcow_host_name" { + default = "mail.serguzim.me" +} + +variable "mailcow_api_key" { + sensitive = true +} + + variable "ovh_application_key" { sensitive = true } @@ -169,6 +178,7 @@ variable "services" { auth_redirects = optional(list(string)) s3 = bool database = bool + mail = optional(string) })) } diff --git a/visualize.py b/visualize.py index 13b2d5c..3dc3af7 100755 --- a/visualize.py +++ b/visualize.py @@ -63,6 +63,7 @@ def parse_service(svc, data, hosts): for dns in data.get("dns") or []: domains.append(f"- {dns['domain']}") + data['name'] = svc data['key'] = svc_key data['host_key'] = host_key(data["host"], hosts) data['label'] = "\\n".join([svc] + domains) @@ -98,15 +99,17 @@ if __name__ == '__main__': db_key = service_key_find("postgresql", services, hosts) auth_key = service_key_find("authentik", services, hosts) monitoring_key = service_key_find("gatus", services, hosts) + mail_key = service_key_find("mailcowdockerized", services, hosts) jinja_loader = jinja2.FileSystemLoader(searchpath="./templates") jinja_env = jinja2.Environment(loader=jinja_loader) template = jinja_env.get_template("infrastructure.d2.j2") print(template.render( - grid_svcs=[db_key, auth_key], + grid_svcs=[db_key, auth_key, mail_key], svcs=parse_services(services, hosts), hosts=parse_hosts(hosts), db_key=db_key, auth_key=auth_key, monitoring_key=monitoring_key, + mail_key=mail_key, ))