feat(maddy): add mail stack
This commit is contained in:
@@ -20,6 +20,7 @@ domain:
|
||||
# Check the hosts.j2 when cname records are fixed
|
||||
|
||||
services:
|
||||
# common
|
||||
crowdsec:
|
||||
domain:
|
||||
internal: "crowdsec"
|
||||
@@ -27,6 +28,18 @@ services:
|
||||
https: "8080"
|
||||
dependency:
|
||||
cert: true
|
||||
kopia:
|
||||
domain:
|
||||
internal: "kopia"
|
||||
ports:
|
||||
https: "51515"
|
||||
dependency:
|
||||
cert: true
|
||||
caddy:
|
||||
ports:
|
||||
http: "2080"
|
||||
https: "2443"
|
||||
# fw
|
||||
bind:
|
||||
domain:
|
||||
internal: "bind"
|
||||
@@ -41,13 +54,7 @@ services:
|
||||
dot: "853"
|
||||
dependency:
|
||||
cert: true
|
||||
kopia:
|
||||
domain:
|
||||
internal: "kopia"
|
||||
ports:
|
||||
https: "51515"
|
||||
dependency:
|
||||
cert: true
|
||||
# infra
|
||||
postgresql:
|
||||
domain:
|
||||
internal: "postgresql"
|
||||
@@ -67,13 +74,15 @@ services:
|
||||
cert: true
|
||||
web: "infra-internal"
|
||||
db: "postgresql"
|
||||
mail:
|
||||
maddy:
|
||||
domain:
|
||||
internal: "mail"
|
||||
ports: ""
|
||||
subuid: ""
|
||||
internal: "maddy"
|
||||
ports:
|
||||
smtp: "2587"
|
||||
imaps: "2993"
|
||||
dependency:
|
||||
cert: true
|
||||
db: "postgresql"
|
||||
ca:
|
||||
domain:
|
||||
internal: "ca"
|
||||
@@ -110,10 +119,7 @@ services:
|
||||
dependency:
|
||||
web: "infra-internal"
|
||||
db: "postgresql"
|
||||
caddy:
|
||||
ports:
|
||||
http: "2080"
|
||||
https: "2443"
|
||||
# auth
|
||||
authelia:
|
||||
domain:
|
||||
public: "authelia"
|
||||
@@ -124,6 +130,7 @@ services:
|
||||
db: "postgresql"
|
||||
redis:
|
||||
subuid: "100998"
|
||||
# app
|
||||
homepage:
|
||||
domain:
|
||||
public: "@"
|
||||
@@ -259,16 +266,19 @@ version:
|
||||
caddy: "2.11.4"
|
||||
# infra
|
||||
step: "0.30.2"
|
||||
## Postgresql
|
||||
postgresql: "18.4"
|
||||
### For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml
|
||||
### pgvector: "v0.8.1"
|
||||
vectorchord: "1.1.1"
|
||||
## lldap
|
||||
ldap: "v0.6.3"
|
||||
## maddy
|
||||
maddy: "0.9.5"
|
||||
x509-exporter: "4.1.0"
|
||||
prometheus: "v3.12.0"
|
||||
loki: "3.7.1"
|
||||
grafana: "13.0.1"
|
||||
## Postgresql
|
||||
postgresql: "18.4"
|
||||
# For immich - https://github.com/immich-app/base-images/blob/main/postgres/versions.yaml
|
||||
# pgvector: "v0.8.1"
|
||||
vectorchord: "1.1.1"
|
||||
# Auth
|
||||
authelia: "4.39.20"
|
||||
# App
|
||||
|
||||
@@ -178,6 +178,14 @@
|
||||
tags: ["site", "ldap"]
|
||||
tags: ["site", "ldap"]
|
||||
|
||||
- name: Set maddy
|
||||
ansible.builtin.include_role:
|
||||
name: "infra"
|
||||
tasks_from: "services/set_maddy"
|
||||
apply:
|
||||
tags: ["site", "maddy"]
|
||||
tags: ["site", "maddy"]
|
||||
|
||||
- name: Set x509 exporter
|
||||
ansible.builtin.include_role:
|
||||
name: "infra"
|
||||
|
||||
@@ -83,3 +83,14 @@
|
||||
changed_when: false
|
||||
listen: "notification_restart_x509-exporter"
|
||||
ignore_errors: true # noqa: ignore-errors
|
||||
|
||||
- name: Restart maddy.service
|
||||
ansible.builtin.systemd:
|
||||
name: "maddy.service"
|
||||
state: "restarted"
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
scope: "user"
|
||||
changed_when: false
|
||||
listen: "notification_restart_maddy"
|
||||
ignore_errors: true # noqa: ignore-errors
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
state: "present"
|
||||
force: true
|
||||
loop:
|
||||
- name: "LDAP_BIND_PASSWORD"
|
||||
- name: "GF_LDAP_BIND_PASSWORD"
|
||||
value: "{{ hostvars['console']['ldap']['password']['services']['grafana'] }}"
|
||||
- name: "GF_DB_PASSWORD"
|
||||
value: "{{ hostvars['console']['postgresql']['password']['grafana'] }}"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
- name: Create maddy directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ node['home_path'] }}/{{ item }}"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ node['group'] }}"
|
||||
state: "directory"
|
||||
mode: "0770"
|
||||
loop:
|
||||
- "containers/maddy"
|
||||
- "containers/maddy/ssl"
|
||||
- "data/containers/maddy"
|
||||
- "data/containers/maddy/data"
|
||||
|
||||
- name: Deploy maddy certificate and key
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
{{ item.value }}
|
||||
dest: "{{ node['home_path'] }}/containers/maddy/ssl/{{ item.name }}"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ node['group'] }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- name: "{{ root_cert_filename }}"
|
||||
value: "{{ hostvars['console']['ca']['root']['crt'] }}"
|
||||
mode: "0440"
|
||||
- name: "maddy.crt"
|
||||
value: |
|
||||
{{ hostvars['console']['maddy']['crt'] | trim }}
|
||||
{{ hostvars['console']['ca']['intermediate']['crt'] }}
|
||||
mode: "0440"
|
||||
- name: "maddy.key"
|
||||
value: "{{ hostvars['console']['maddy']['key'] }}"
|
||||
mode: "0400"
|
||||
become: true
|
||||
notify: "notification_restart_maddy"
|
||||
no_log: true
|
||||
|
||||
- name: Register secret value to podman secret
|
||||
containers.podman.podman_secret:
|
||||
name: "{{ item.name }}"
|
||||
data: "{{ item.value }}"
|
||||
state: "present"
|
||||
force: true
|
||||
loop:
|
||||
- name: "MADDY_LDAP_BIND_PASSWORD"
|
||||
value: "{{ hostvars['console']['ldap']['password']['services']['maddy'] }}"
|
||||
- name: "MADDY_POSTGRES_URL"
|
||||
value: "postgresql://maddy:{{ hostvars['console']['postgresql']['password']['maddy'] | urlencode | replace('/', '%2F') }}\
|
||||
@{{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}/maddy_db?sslmode=verify-full&\
|
||||
sslrootcert=/etc/ssl/maddy/{{ root_cert_filename }}"
|
||||
notify: "notification_restart_maddy"
|
||||
no_log: true
|
||||
|
||||
- name: Deploy configuration files
|
||||
ansible.builtin.template:
|
||||
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/infra/maddy/data/maddy.conf.j2"
|
||||
dest: "{{ node['home_path'] }}/containers/maddy/maddy.conf"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ node['group'] }}"
|
||||
mode: "0400"
|
||||
notify: "notification_restart_maddy"
|
||||
no_log: true
|
||||
|
||||
- name: Pull image
|
||||
containers.podman.podman_image:
|
||||
name: "docker.io/foxcpp/maddy"
|
||||
tag: "{{ version['containers']['maddy'] }}"
|
||||
state: "present"
|
||||
pull: true
|
||||
|
||||
- name: Deploy container file
|
||||
ansible.builtin.template:
|
||||
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/infra/maddy/maddy.container.j2"
|
||||
dest: "{{ node['home_path'] }}/.config/containers/systemd/maddy.container"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ node['group'] }}"
|
||||
mode: "0644"
|
||||
notify: "notification_restart_maddy"
|
||||
|
||||
- name: Enable maddy
|
||||
ansible.builtin.systemd:
|
||||
name: "maddy.service"
|
||||
state: "started"
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
scope: "user"
|
||||
|
||||
- name: Check container archive images
|
||||
ansible.builtin.stat:
|
||||
path: "{{ node['home_path'] }}/archives/containers/docker.io_foxcpp_maddy_{{ version['containers']['maddy'] }}.tar"
|
||||
register: container_archive_images
|
||||
|
||||
- name: Save container archive images
|
||||
containers.podman.podman_save:
|
||||
image:
|
||||
- "docker.io/foxcpp/maddy:{{ version['containers']['maddy'] }}"
|
||||
dest: "{{ node['home_path'] }}/archives/containers/docker.io_foxcpp_maddy_{{ version['containers']['maddy'] }}.tar"
|
||||
format: "oci-archive"
|
||||
force: false
|
||||
when: not container_archive_images.stat.exists
|
||||
|
||||
- name: Fetch container archive images
|
||||
ansible.builtin.fetch:
|
||||
src: "{{ node['home_path'] }}/archives/containers/docker.io_foxcpp_maddy_{{ version['containers']['maddy'] }}.tar"
|
||||
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
|
||||
flat: true
|
||||
@@ -15,6 +15,12 @@
|
||||
| list
|
||||
}}
|
||||
|
||||
- name: Delete existing init files
|
||||
ansible.builtin.file:
|
||||
path: "{{ node['home_path'] }}/containers/postgresql/init"
|
||||
state: "absent"
|
||||
become: true
|
||||
|
||||
- name: Create postgresql directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ node['home_path'] }}/{{ item }}"
|
||||
@@ -109,10 +115,10 @@
|
||||
- name: Validate initiating SQL file count
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- postgresql_init_sql_files.matched == 0 or postgresql_init_sql_files.matched == ((connected_services | length) + 1)
|
||||
- postgresql_init_sql_files.matched == 0 or postgresql_init_sql_files.matched <= ((connected_services | length) + 1)
|
||||
fail_msg: >-
|
||||
Init SQL count mismatch.
|
||||
expected=0 or {{ (connected_services | length) + 1 }},
|
||||
expected=0 or above {{ (connected_services | length) + 1 }},
|
||||
actual={{ postgresql_init_sql_files.matched }}.
|
||||
Check pg_cluster.sql and pg_<service>.sql files in
|
||||
{{ node['home_path'] }}/data/containers/postgresql/backups/sql/
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
group: "root"
|
||||
mode: "0711"
|
||||
loop: "{{ is_libvirt_dir.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: not item.stat.exists
|
||||
become: true
|
||||
|
||||
|
||||
@@ -49,8 +49,7 @@ define PORTS_DHCP = { 67, 68, 546, 547 }
|
||||
define PORTS_DNS = 53
|
||||
define PORTS_NTP = 123
|
||||
define PORTS_VPN = 11290
|
||||
define PORTS_CROWDSEC = 8080
|
||||
define PORTS_NAS = { 5000, 5001 }
|
||||
define PORTS_CROWDSEC = {{ services['crowdsec']['ports']['https'] }}
|
||||
|
||||
table inet nat {
|
||||
chain prerouting {
|
||||
|
||||
@@ -9,15 +9,19 @@ define HOSTS4_CONSOLE = { {{ hostvars['fw']['network4']['console'].values() | jo
|
||||
define HOSTS6_CONSOLE = { {{ hostvars['fw']['network6']['console'].values() | join(', ') }} }
|
||||
define PORTS_SSH = 22
|
||||
define PORTS_DB = 5432
|
||||
define PORTS_CA = 9000
|
||||
define PORTS_CA = {{ services['ca']['ports']['https'] }}
|
||||
define PORTS_LDAPS = 636
|
||||
define PORTS_LDAPS_FORWARD = 6360
|
||||
define PORTS_LDAPS_FORWARD = {{ services['ldap']['ports']['ldaps'] }}
|
||||
define PORTS_HTTP = 80
|
||||
define PORTS_HTTP_FORWARD = 2080
|
||||
define PORTS_HTTPS = 443
|
||||
define PORTS_HTTPS_FORWARD = 2443
|
||||
define PORTS_PROMETHEUS = 9090
|
||||
define PORTS_LOKI = 3100
|
||||
define PORTS_PROMETHEUS = {{ services['prometheus']['ports']['https'] }}
|
||||
define PORTS_LOKI = {{ services['loki']['ports']['https'] }}
|
||||
define PORTS_SMTP = 587
|
||||
define PORTS_SMTP_FORWARD = {{ services['maddy']['ports']['smtp'] }}
|
||||
define PORTS_IMAPS = 993
|
||||
define PORTS_IMAPS_FORWARD = {{ services['maddy']['ports']['imaps'] }}
|
||||
|
||||
table inet nat {
|
||||
chain prerouting {
|
||||
@@ -25,6 +29,8 @@ table inet nat {
|
||||
tcp dport $PORTS_HTTP dnat to :$PORTS_HTTP_FORWARD comment "DNAT http ports to $PORTS_HTTP_FORWARD"
|
||||
tcp dport $PORTS_HTTPS dnat to :$PORTS_HTTPS_FORWARD comment "DNAT https ports to $PORTS_HTTPS_FORWARD"
|
||||
tcp dport $PORTS_LDAPS dnat to :$PORTS_LDAPS_FORWARD comment "DNAT ldaps ports to $PORTS_LDAPS_FORWARD"
|
||||
tcp dport $PORTS_SMTP dnat to :$PORTS_SMTP_FORWARD comment "DNAT smtp ports to $PORTS_SMTP_FORWARD"
|
||||
tcp dport $PORTS_IMAPS dnat to :$PORTS_IMAPS_FORWARD comment "DNAT imaps ports to $PORTS_IMAPS_FORWARD"
|
||||
}
|
||||
chain postrouting {
|
||||
|
||||
@@ -34,6 +40,8 @@ table inet nat {
|
||||
oifname "lo" tcp dport $PORTS_HTTP dnat to :$PORTS_HTTP_FORWARD comment "DNAT http ports to $PORTS_HTTP_FORWARD out of LOCALHOST"
|
||||
oifname "lo" tcp dport $PORTS_HTTPS dnat to :$PORTS_HTTPS_FORWARD comment "DNAT https ports to $PORTS_HTTPS_FORWARD out of LOCALHOST"
|
||||
oifname "lo" tcp dport $PORTS_LDAPS dnat to :$PORTS_LDAPS_FORWARD comment "DNAT ldaps ports to $PORTS_LDAPS_FORWARD out of LOCALHOST"
|
||||
oifname "lo" tcp dport $PORTS_SMTP dnat to :$PORTS_SMTP_FORWARD comment "DNAT smtp ports to $PORTS_SMTP_FORWARD"
|
||||
oifname "lo" tcp dport $PORTS_IMAPS dnat to :$PORTS_IMAPS_FORWARD comment "DNAT imaps ports to $PORTS_IMAPS_FORWARD"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +68,10 @@ table inet filter {
|
||||
ip6 saddr $NET6_SERVER tcp dport $PORTS_PROMETHEUS accept comment "allow ipv6 prometheus connection: SERVER > INFRA"
|
||||
ip saddr $NET4_SERVER tcp dport $PORTS_LOKI accept comment "allow ipv4 loki connection: SERVER > INFRA"
|
||||
ip6 saddr $NET6_SERVER tcp dport $PORTS_LOKI accept comment "allow ipv6 loki connection: SERVER > INFRA"
|
||||
ip saddr $NET4_SERVER tcp dport $PORTS_SMTP_FORWARD ct original proto-dst $PORTS_SMTP accept comment "allow ipv4 smtp connection: SERVER > INFRA"
|
||||
ip6 saddr $NET6_SERVER tcp dport $PORTS_SMTP_FORWARD ct original proto-dst $PORTS_SMTP accept comment "allow ipv6 smtp connection: SERVER > INFRA"
|
||||
ip saddr $NET4_SERVER tcp dport $PORTS_IMAPS_FORWARD ct original proto-dst $PORTS_IMAPS accept comment "allow ipv4 imaps connection: SERVER > INFRA"
|
||||
ip6 saddr $NET6_SERVER tcp dport $PORTS_IMAPS_FORWARD ct original proto-dst $PORTS_IMAPS accept comment "allow ipv6 imaps connection: SERVER > INFRA"
|
||||
}
|
||||
chain forward {
|
||||
type filter hook forward priority 0; policy drop;
|
||||
|
||||
@@ -126,6 +126,7 @@ postgresql:
|
||||
postgres: ENC[AES256_GCM,data:XlpsKT0/3VgNesMdifvHlYZ2dh7mlMEnxi0MJpQZiQ+1tZ0/3zMxQ7+vwec=,iv:akcPRmDZQfFLxelG7V87UnNB8ez28PPLCmY1MYfzStU=,tag:14jvIKP7Htfh+/oMwLYC2Q==,type:str]
|
||||
alloy: ENC[AES256_GCM,data:ifgJH2e7MTAJ/+ppdWHEmLC3DAsKHWNi8j4w1ZVjUh4tN1cDLDJR3EvJTl0=,iv:rDYUkVe6idIf5Si5E9V+mvBaIdYQTJJCB2rOV0Suvr0=,tag:xDCL25NwuTp6H1DOGTIm4w==,type:str]
|
||||
ldap: ENC[AES256_GCM,data:mJrxIhXynHxJhncw3upHpOkXIw+Ka9bmDBJwkDjYl+D9Pg4RDvL6WzBjthw=,iv:y8MUYo6VhgTzbWh/+n7/hf1Jw+L2KcdxKvulPJ67xn8=,tag:4ZFpj1UdOwXmaZjYvC/s3A==,type:str]
|
||||
maddy: ENC[AES256_GCM,data:D8cFa65up8DVGBiIAXvh/2BdYoeZ9X4wPZmCU6aQ6p37+4edBhsl6VWYhH4=,iv:AORId0QsAkeIQH2IMcI+HKFKW7NLrZ0GXi0yCn2NK5Y=,tag:RtI/Vn8WydkjIiNZB9om/w==,type:str]
|
||||
grafana: ENC[AES256_GCM,data:P9okJ7bcsqmeGstkSwbDq/RgnG+lFrgAOvcj8A5lOTpmHaSlXGiKG+ybXa0=,iv:Di1ghnxIbAb/u7uo/mJCC3QYVjdweTHaQDZmXTx8OG4=,tag:DT3a1zgU9sTr0BXpyoZ/SQ==,type:str]
|
||||
authelia: ENC[AES256_GCM,data:OqyloAADO6KKEaBjGLsJc9GTe77wn6IvA1VCD2dfCWxx+zgzUYh87fK1XX8=,iv:QIOHNTdNnzcY/f3Co8dPdNHykhBnYRhm43nt35hbALM=,tag:DLQq58GrZd+Ul7MSn6s9uQ==,type:str]
|
||||
vaultwarden: ENC[AES256_GCM,data:BPj5eFo54DTZ82n3yTIqEbm7kb/jWT0n2kZY//oV5q48eRch3C2RBuxn/Ko=,iv:DGC4ipHMyVs25gc4sNMt8LN1RsHjiR/b303vgiFoxMY=,tag:k1eb4DoRPLKvvMstSI1faQ==,type:str]
|
||||
@@ -156,6 +157,7 @@ ldap:
|
||||
admin: ENC[AES256_GCM,data:xMBoPycO/5wDae2AMZlgomoXjB98BpCOQkxek3W7t7Wk+az1GMqJhdB0osY=,iv:E6nNmNNKlJoNC7KwE0PaDIB8QyGM0MuNutQty3bi1Pc=,tag:yc8nlegBUOX2hfaMBoNaCw==,type:str]
|
||||
authelia: ENC[AES256_GCM,data:1hHNPWgiRte/TJr4gBuwFt3zMHPivgRp7Z2kurYG+bT96HWmwk1+4iKSQVM=,iv:cpV2P14daWabaDuQ9KgrGs8bN+4pc7sXbAUE2mjWQL0=,tag:lsLgHkkhgSPfwxC6vu5JEg==,type:str]
|
||||
grafana: ENC[AES256_GCM,data:42rnv/NMwmvHdyWW6PZj1Lto5mRDTHp6h55OuRYd9xMOtmenX5zBJzGMvnE=,iv:jUKuPisxa/S9erKjXHl3y0zJR+Tfe36O85/BdGmN+gE=,tag:h1InzKaa+bWjlmIq9OZRpw==,type:str]
|
||||
maddy: ENC[AES256_GCM,data:+E8ggJax1s7Nwdc8EpAWHnVuM0ceJqpW++z4UGeTpPN0DYDPqNSBi/7rW6s=,iv:n+iMKOBjuyeVNRLZQTr+3/Fuu3KEYolLjCXLZ9lTBvk=,tag:B5NZO7zKxh+7nN29vvE1Jw==,type:str]
|
||||
users:
|
||||
il: ENC[AES256_GCM,data:GcOsUe7yJFLdhPp2H7i2,iv:T4NGNvYe5beYbxr+ezfT86aS5lCBa/TG3bUWuUrCZ+A=,tag:rrdHtLyZI7pdTGq5qmptYw==,type:str]
|
||||
morsalin: ENC[AES256_GCM,data:86+imwqNJ5Xg,iv:Lgt1I12gtDsjvE1yMjiyed0sZEeExQZoJPD+xKzHBqk=,tag:YiQgczFZnzh7p1t+KIzM6Q==,type:str]
|
||||
@@ -163,9 +165,9 @@ ldap:
|
||||
#
|
||||
#
|
||||
#ENC[AES256_GCM,data:6RVyF1gPUfKEvkV3RQ==,iv:0MTKlm5kE0kZi3E013Kk/Wo642BizBRmcHXR+Zwf/W8=,tag:SaQJ7lvFUuALxXWf41C24A==,type:comment]
|
||||
mail:
|
||||
key: ENC[AES256_GCM,data:2iT93HHmX3eCTtKb8/C5mI2uEwdck7R6TCXoLuWprfs6KMza8mwd+qDeYGauUHUojEplmopycWup1cVWyQRN2B3udxd7nR9YXcdL0ZNvjmEcjRKDicHEhIw3DVb7N5GuGkFDhc4dEx4gen5Lwu3c+b2hpL58fvZNmrldKV1Qs2jnyi4LcFxEaldPNAiJJA5CYC3M/FFrEDw+Zh1VP6rliz0wYvEoqsIanKgDs5sVkSw7IT9iBidg8EagPrlSEcYJOSHlNVJU2a28P45az0G1drw4BWiXASmCWk6CNr2WT26vz24=,iv:8h45V8wA83AHrej7ajzV3z3KUS2eu0fegjPJk7l8YoQ=,tag:kD+QVBEur6xSI0iLEthWeg==,type:str]
|
||||
crt: ENC[AES256_GCM,data:v1nr+IQowiph7eRyENALTAXHt7NPoEdbyBcb3lVxUp9Ga6nGs7IPSyzMTfc3Am4cZ/6kRpXJG30cjINWpfhFkruS6SCPqMUcsxkVw6oLArbN62wV4umws6xh0pQNajTGY8ZNhya4RudWSLAtmIlbI0aneJKxrypPQKXeX+nML48TLcV8PicAhr4gyEk967IaHLCPK3GAuDwrD/b5NsaU3sVb5bHwcSsUKUBzdAYdHTDwsuOVwbrYT0egQ+Bcn44bY/EWw8SbIa90A+iqU6R4G9V2+/eHCSdqaNX27Yp+h3YgRJH+NqItiMlgeJ99bjvYjdSaOucmUE9gcUt7BFHpEuCI6nBGduFu/PEsFd1vNB/jKjLidN/wjcHHXXfmLy1vw8ut8ns9cl3Ufyk2VcwTfexKTuZbcAIlDdc2HGB03Z8bju8Ci96+j6cZEgv04eUZSl/5erRuyrIrA/erC09J3gqz3ojMTPsGxmQs8Rbkv6ZWM2mrsBizZq3K3XMCAP/eC1072pnOjgE91PzDUmQwBSm2xxyvjDDjJxp0M5EVdTQ/GiJXtV/Fwq8NvhI1P3nkQoDOEz+i3I1ZNrsRPLoaC+HKd+DdnO24slTUXXYO2/j9/Y3z+gjj25WR3Q1iFzKXcVH5pPVToDavMKLhlchcXI0FvqjKPYyqAY1uNEg1oVlqcyD/CcOUur1ugNQGriXPf+//nb3WZgxfcZiCwyee4oCAa3OxSMIaENn3QQ6QVGNocNanvulcA9LwIMU8qXp0+AixtFvbTlM/LHBWy10YDH2LKg7kZ9ePd6JnSFYIdB8gKhnbolrtLVIpC9NDcyzYosFrZ1NsvGgEiy+5+tLl+XERaZunulZ3VBG+4w583/PfeIZ4l7kRde1HWkym5oI0IluQG9MagyIv2a2fSScZEIClDPkOJqFaL6WszVViAgB3pAL3dEr8+48=,iv:bByFvLj8L+yZ2ErlkDBHGRhnNVQsFOe7q3WwyGVWQOU=,tag:VK8iwa8m0thEBNyTvEZOZw==,type:str]
|
||||
maddy:
|
||||
key: ENC[AES256_GCM,data:NN1w/AQAOac7898/tk2W8qDe0TA1W4rwe2USinUW3CxvQO3Gy2oGgcHT7xKDOxnqz/Ec3rBBFZ3v1rAH7236L6GNQ700pFRuddn0qdMukLHJGbY3mWGRWVQYwV1JX3WVdM2HMrrmLKXe1ArsiTfRVjSvVj0kVNjnMwCSFl/zNe2PgwIMSLi+L5KeJ+G5OOywmR3ctQPJQQefeu+eXXkDFPRwENQlD8i5H1K9vSwRVy09DKzFtc74Xi3srVozWE6U+bCTE/rRdxWN+N0wJNl8tUO9Ndbw98rRaD6WdARRXta6nfU=,iv:MpSJnnrragy8hCOwytSuTfJ3i8+8DnSEYUemDK6rBCM=,tag:pjWVUZSUhVsx2nCMzNpXSQ==,type:str]
|
||||
crt: ENC[AES256_GCM,data:O912cPVNMp7H/pEtrzaqv3QckgoAdf3E36E0G4yvVVUwfhfnxTwlLkQ3uk2FGA6VpCuwOvSBYiSnngL0/55hfulVTWmW32oT5pkW9c8UhyA+769o4YrDiX7yG/4YbFyO4JK44f/J/z1zZc7E6Oni5z9GOTQuo4SjRqM3iT+pDqnTRnB26HVSSAZBgGJpL/6lFM/WKheDGrzh/SVJmgr+/uHEWJTuo/L+CsxZ1Qn3WDj4/s7HtCu8I5qvEUPwII86wo6FNXmhTapjIyNDsbgvr/QgNGEvSvVQtSaMyoKWEOKPRIOhdvcZ36qSegqVLO6pvBtvrZabMsNDnTyraAUo7ePs4t+5NM9xAfwJ8vrkqkqYMS22prdHBy5mYebczFQdxGhr+kDi0itAVWZULIXnc/7fptrwoB7jJjO3i9bHSfrXx3DdpU8ivIi/snwKDsA24g1tqK5/y4dgCNakzp1GhZVrPT3WKnqCUPapn7vjEjucClg3m+5poNmdUZs1SgGRhqpeBz1inUzhsBPrk+qIPXXY5eSZ37DH8hQG0NnxsgFiPpLAn/5yfHUI3eVoS6i2kNbK4KkpD2sJ/amRX7Pxder5+tIIu8GzmN8EgfgucV8/DlrX+Z0u6HvA9lOxDxMR+/Bast5e9jc7dCKY25x8oun3OAiG0kAAsJYVcB0/wvskYJoBoKOICRjF8kf0w27kxMgCuFh4LLaqeBbtBXh7lKXjD3gEcTZgPWnxeb63RCWZqoTBGeyUcBkGByAIclhbOHClDG/X397pM0Rn+QbaR04LSGgRPF5NCKqbwmvh8z24b0n6mvyrBpBqhCxdlBz1f5RqlIaCGuy/oR0E2vEfeMiz5LAFZydALZM1v7GkoiRPPasYgMhoHfeSPfCgCyFugtGfU0SqtyksJQxYQUzKAuchALHAi/PCQ7YtXq/1K6C7aK4/Z8+Fy+0=,iv:+MJeh6rwG6PtAp5o5v2wtlhd1wrXkPCdHmEed70F0eQ=,tag:xH9CxCF8mC7PCV0xeE9UCA==,type:str]
|
||||
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
|
||||
#
|
||||
#
|
||||
@@ -343,7 +345,7 @@ sops:
|
||||
k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age120wuwcmsm845ztsvsz46pswj5je53uc2n35vadklrfqudu6cxuusxetk7y
|
||||
lastmodified: "2026-06-25T00:07:39Z"
|
||||
mac: ENC[AES256_GCM,data:XNOyATqwo6BygVz6nzBZx6Jn8YONifcvwRKFxWsVxy+aqRRCbUA4eY3g9s6EstpZwHLpf1KeH6iS1ogzwlZfw1Xp8xaWjXgZTEV9R6jEKRzZ4pwPnmC6UVTfrJ2XBB/mjTel0jSR6gbKSgXMJw4rsEyvth+tPxMAaHvrIEcFGqE=,iv:2W2ohbVz4zFDFHG6T5loayab6nOsZaJ/OwqTfNNhEYs=,tag:TNoVHAW5g4/pAOdqolmD7Q==,type:str]
|
||||
lastmodified: "2026-06-25T05:44:30Z"
|
||||
mac: ENC[AES256_GCM,data:eVRWoJ48BjmflUJoXy3hJpf48H4+7zum47xBHZi674UFH4wSWa/01ctq94fXLNeGpovbaPnh2997c33lm26JVXNfSUPzkjALzf5e7pzMLoxnMbpHrAqyctfcx8PxLXWW1DBNOym0Bi0CM8F4BDsuroQPDDtVGzZk2mouzl0iJqo=,iv:ZRqcYFRxae2cnhwAEbHlq66ydV7PbvSdDlu+Nl2dYlw=,tag:bIAhfpzaJhO+STMC34Qxnw==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.1
|
||||
|
||||
@@ -15,7 +15,7 @@ root_ca_cert = "/etc/ssl/grafana/{{ root_cert_filename }}"
|
||||
# client_key = "/path/to/client.key"
|
||||
|
||||
bind_dn = "uid=grafana,ou=people,{{ domain['dc'] }}"
|
||||
bind_password = "$__file{/run/secrets/LDAP_BIND_PASSWORD}"
|
||||
bind_password = "$__file{/run/secrets/GF_LDAP_BIND_PASSWORD}"
|
||||
|
||||
search_filter = "(&(|(uid=%s)(mail=%s))(|(memberOf=cn=admins,ou=groups,{{ domain['dc'] }})(memberOf=cn=users,ou=groups,{{ domain['dc'] }})))"
|
||||
search_base_dns = ["{{ domain['dc'] }}"]
|
||||
|
||||
@@ -31,7 +31,7 @@ Environment="GF_PATHS_CONFIG=/etc/grafana/grafana.ini"
|
||||
Environment="GF_FEATURE_TOGGLES_EXPAND_ENV_VARS=true"
|
||||
|
||||
Secret=GF_DB_PASSWORD,target=/run/secrets/GF_DB_PASSWORD
|
||||
Secret=LDAP_BIND_PASSWORD,target=/run/secrets/LDAP_BIND_PASSWORD
|
||||
Secret=GF_LDAP_BIND_PASSWORD,target=/run/secrets/GF_LDAP_BIND_PASSWORD
|
||||
Secret=GF_ADMIN_PASSWORD,target=/run/secrets/GF_ADMIN_PASSWORD
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Base
|
||||
|
||||
$(hostname) = {{ services['maddy']['domain']['internal'] }}.{{ domain['internal'] }}
|
||||
$(primary_domain) = {{ domain['internal'] }}
|
||||
$(local_domains) = $(primary_domain)
|
||||
state_dir /data
|
||||
runtime_dir /tmp/maddy
|
||||
hostname $(hostname)
|
||||
tls file /etc/ssl/maddy/maddy.crt /etc/ssl/maddy/maddy.key {
|
||||
protocols tls1.2 tls1.3
|
||||
}
|
||||
|
||||
# LDAP
|
||||
auth.ldap smtp_ldap {
|
||||
urls ldaps://{{ services['ldap']['domain']['internal'] }}.{{ domain['internal'] }}
|
||||
bind plain "uid=maddy,ou=people,{{ domain['dc'] }}" "{env:MADDY_LDAP_BIND_PASSWORD}"
|
||||
base_dn "ou=people,{{ domain['dc'] }}"
|
||||
filter "(&(uid={username})(objectClass=person)(|(memberOf=cn=admins,ou=groups,{{ domain['dc'] }})(memberOf=cn=users,ou=groups,{{ domain['dc'] }})(memberOf=cn=services,ou=groups,{{ domain['dc'] }})))"
|
||||
starttls off
|
||||
tls_client {
|
||||
root_ca /etc/ssl/maddy/{{ root_cert_filename }}
|
||||
}
|
||||
}
|
||||
auth.ldap imap_ldap {
|
||||
urls ldaps://{{ services['ldap']['domain']['internal'] }}.{{ domain['internal'] }}
|
||||
bind plain "uid=maddy,ou=people,{{ domain['dc'] }}" "{env:MADDY_LDAP_BIND_PASSWORD}"
|
||||
base_dn "ou=people,{{ domain['dc'] }}"
|
||||
filter "(&(uid={username})(objectClass=person)(|(memberOf=cn=admins,ou=groups,{{ domain['dc'] }})(memberOf=cn=users,ou=groups,{{ domain['dc'] }})))"
|
||||
starttls off
|
||||
tls_client {
|
||||
root_ca /etc/ssl/maddy/{{ root_cert_filename }}
|
||||
}
|
||||
}
|
||||
|
||||
storage.imapsql local_mailboxes {
|
||||
driver postgres
|
||||
dsn "{env:MADDY_POSTGRES_URL}"
|
||||
msg_store fs messages/
|
||||
delivery_map email_localpart
|
||||
delivery_normalize precis_casefold_email
|
||||
}
|
||||
|
||||
table.chain local_rewrites {
|
||||
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
|
||||
|
||||
optional_step static {
|
||||
entry postmaster postmaster@$(primary_domain)
|
||||
}
|
||||
}
|
||||
|
||||
msgpipeline local_routing {
|
||||
destination postmaster $(local_domains) {
|
||||
modify {
|
||||
replace_rcpt &local_rewrites
|
||||
}
|
||||
|
||||
deliver_to &local_mailboxes
|
||||
}
|
||||
|
||||
default_destination {
|
||||
reject 550 5.1.1 "User doesn't exist"
|
||||
}
|
||||
}
|
||||
|
||||
submission tcp://0.0.0.0:587 {
|
||||
hostname $(hostname)
|
||||
|
||||
limits {
|
||||
all rate 20 1s
|
||||
all concurrency 20
|
||||
}
|
||||
|
||||
auth &smtp_ldap
|
||||
insecure_auth no
|
||||
sasl_login yes
|
||||
max_message_size 32M
|
||||
|
||||
source $(local_domains) {
|
||||
check {
|
||||
authorize_sender {
|
||||
prepare_email &local_rewrites
|
||||
|
||||
auth_normalize precis_casefold
|
||||
from_normalize precis_casefold_email
|
||||
user_to_email regexp "(.*)" "$1@$(primary_domain)"
|
||||
|
||||
check_header yes
|
||||
}
|
||||
}
|
||||
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
|
||||
default_destination {
|
||||
reject 550 5.7.1 "External delivery is disabled"
|
||||
}
|
||||
}
|
||||
|
||||
default_source {
|
||||
reject 550 5.7.1 "Non-local sender domain"
|
||||
}
|
||||
}
|
||||
|
||||
imap tls://0.0.0.0:993 {
|
||||
auth &imap_ldap
|
||||
storage &local_mailboxes
|
||||
|
||||
insecure_auth no
|
||||
sasl_login yes
|
||||
|
||||
storage_map identity
|
||||
storage_map_normalize precis_casefold
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
[Quadlet]
|
||||
DefaultDependencies=false
|
||||
|
||||
[Unit]
|
||||
Description=Maddy
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=docker.io/foxcpp/maddy:{{ version['containers']['maddy'] }}
|
||||
|
||||
ContainerName=maddy
|
||||
HostName=maddy
|
||||
|
||||
PublishPort={{ services['maddy']['ports']['smtp'] }}:587/tcp
|
||||
PublishPort={{ services['maddy']['ports']['imaps'] }}:993/tcp
|
||||
|
||||
AddHost={{ services['postgresql']['domain']['internal'] }}.{{ domain['internal'] }}:host-gateway
|
||||
AddHost={{ services['ldap']['domain']['internal'] }}.{{ domain['internal'] }}:host-gateway
|
||||
|
||||
Volume=%h/containers/maddy/maddy.conf:/data/maddy.conf:ro
|
||||
Volume=%h/containers/maddy/ssl:/etc/ssl/maddy:ro
|
||||
Volume=%h/data/containers/maddy/data:/data:rw
|
||||
|
||||
Environment="TZ={{ timezone }}"
|
||||
Secret=MADDY_LDAP_BIND_PASSWORD,type=env
|
||||
Secret=MADDY_POSTGRES_URL,type=env
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=10s
|
||||
TimeoutStopSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
@ IN SOA bind.ilnmors.internal. bind.ilnmors.internal. (
|
||||
2026021201 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
@ IN SOA bind.ilnmors.internal. bind.ilnmors.internal. (
|
||||
2026021201 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
@ IN SOA bind.ilnmors.internal. bind.ilnmors.internal. (
|
||||
2026021201 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
@ IN SOA bind.ilnmors.internal. bind.ilnmors.internal. (
|
||||
2026021201 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
@ IN SOA bind.ilnmors.internal. bind.ilnmors.internal. (
|
||||
2026021201 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 86400
|
||||
|
||||
@ IN SOA bind.ilnmors.internal. mail.ilnmors.internal. (
|
||||
@ IN SOA bind.ilnmors.internal. bind.ilnmors.internal. (
|
||||
2026021201 ; serial
|
||||
3600 ; refresh (1 hour)
|
||||
1800 ; retry (30 minutes)
|
||||
@@ -35,6 +35,7 @@ kopia IN CNAME bk.ilnmors.internal.
|
||||
ca IN CNAME infra.ilnmors.internal.
|
||||
postgresql IN CNAME infra.ilnmors.internal.
|
||||
ldap IN CNAME infra.ilnmors.internal.
|
||||
maddy IN CNAME infra.ilnmors.internal.
|
||||
prometheus IN CNAME infra.ilnmors.internal.
|
||||
loki IN CNAME infra.ilnmors.internal.
|
||||
grafana IN CNAME infra.ilnmors.internal.
|
||||
|
||||
Reference in New Issue
Block a user