feat(mail): rename mail→mx, webmail→mail.csrx.ru + reliability
Rename: - docker-mailserver: hostname mail → mx, OVERRIDE_HOSTNAME → mx.csrx.ru - Traefik route: webmail/domain_webmail → mail/domain_mail - domain_webmail removed, domain_mail + domain_mx added to main.yml - certbot cert: mail.csrx.ru → mx.csrx.ru Email reliability improvements: - certbot renewal cron (03:15 + 15:15 daily) - deploy-hook: auto-reload Postfix+Dovecot after cert renewal - POSTFIX_MESSAGE_SIZE_LIMIT=26214400 (25 MB) - SPF hardened: ~all → -all - DMARC hardened: p=none → p=quarantine, added ruf + fo=1 + adkim/aspf strict - autodiscover/autoconfig CNAME records for mail client setup - dns-zone.zone fully updated with architecture comments Docs: - STATUS.md: full mail architecture section, client settings, DNS table - BACKLOG.md: rDNS task + DNS migration steps - DECISIONS.md: mx/mail split rationale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
66b70827df
commit
1e638055c8
8 changed files with 138 additions and 37 deletions
|
|
@ -1,23 +1,44 @@
|
||||||
|
; DNS zone for csrx.ru — reference file, apply manually in Cloudflare
|
||||||
|
; Last updated: 2026-03-22
|
||||||
|
;
|
||||||
|
; Architecture:
|
||||||
|
; mail.csrx.ru → 87.249.49.32 (Cloudflare proxied) → Traefik → SnappyMail webmail
|
||||||
|
; mx.csrx.ru → 85.193.83.9 (DNS-only, NOT proxied) → docker-mailserver SMTP/IMAP
|
||||||
|
;
|
||||||
$ORIGIN csrx.ru.
|
$ORIGIN csrx.ru.
|
||||||
$TTL 3600
|
$TTL 3600
|
||||||
|
|
||||||
; ── A-записи сервисов ────────────────────────────────────────────────────────
|
; ── A-записи сервисов (Cloudflare proxied) ───────────────────────────────────
|
||||||
vault IN A 87.249.49.32
|
vault IN A 87.249.49.32
|
||||||
git IN A 87.249.49.32
|
git IN A 87.249.49.32
|
||||||
plane IN A 87.249.49.32
|
plane IN A 87.249.49.32
|
||||||
sync IN A 87.249.49.32
|
|
||||||
traefik IN A 87.249.49.32
|
traefik IN A 87.249.49.32
|
||||||
mail IN A 87.249.49.32
|
dash IN A 87.249.49.32
|
||||||
|
auth IN A 87.249.49.32
|
||||||
|
status IN A 87.249.49.32
|
||||||
|
wiki IN A 87.249.49.32
|
||||||
|
n8n IN A 87.249.49.32
|
||||||
|
mail IN A 87.249.49.32 ; SnappyMail webmail (via Traefik, proxied)
|
||||||
|
|
||||||
; ── Почта ────────────────────────────────────────────────────────────────────
|
; ── A-записи прямого подключения (DNS-only, Cloudflare proxy OFF) ─────────────
|
||||||
@ IN MX 10 mail.csrx.ru.
|
mx IN A 85.193.83.9 ; docker-mailserver MX/SMTP/IMAP — НЕ проксировать!
|
||||||
|
|
||||||
; SPF — разрешаем отправку только с нашего mail-сервера
|
; ── Почта ─────────────────────────────────────────────────────────────────────
|
||||||
@ IN TXT "v=spf1 mx ~all"
|
; MX — входящая почта идёт на mx.csrx.ru
|
||||||
|
@ IN MX 10 mx.csrx.ru.
|
||||||
|
|
||||||
; DMARC — мониторинг без блокировки (p=none), отчёты на admin@csrx.ru
|
; SPF — разрешаем отправку только с IP из MX-записи (85.193.83.9)
|
||||||
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:admin@csrx.ru"
|
; mx = "IP-адреса из всех MX-записей" = mx.csrx.ru = 85.193.83.9
|
||||||
|
@ IN TXT "v=spf1 mx -all"
|
||||||
|
|
||||||
; DKIM — добавить после первого запуска Stalwart (взять ключ из mail.csrx.ru → DKIM)
|
; DMARC — режим quarantine (подозрительные письма в спам), отчёты на admin@
|
||||||
; Пример как будет выглядеть:
|
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:admin@csrx.ru; ruf=mailto:admin@csrx.ru; fo=1; adkim=s; aspf=s"
|
||||||
; mail._domainkey IN TXT "v=DKIM1; k=rsa; p=<ключ из Stalwart>"
|
|
||||||
|
; DKIM — selector "mail", ключ генерируется при первом деплое docker-mailserver
|
||||||
|
; Взять из: cat /opt/tools/mailserver/config/opendkim/keys/csrx.ru/mail.txt
|
||||||
|
; mail._domainkey IN TXT "v=DKIM1; k=rsa; p=<PUBLIC_KEY>"
|
||||||
|
|
||||||
|
; ── Autodiscover / Autoconfig (для почтовых клиентов) ─────────────────────────
|
||||||
|
; Thunderbird, Outlook автоматически находят настройки сервера
|
||||||
|
autoconfig IN CNAME mx.csrx.ru.
|
||||||
|
autodiscover IN CNAME mx.csrx.ru.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,20 @@
|
||||||
|
|
||||||
## 🔴 Критично (сделать как можно скорее)
|
## 🔴 Критично (сделать как можно скорее)
|
||||||
|
|
||||||
- [ ] **Добавить DNS-запись `dash.csrx.ru`** в Cloudflare
|
- [ ] **rDNS (PTR-запись) для 85.193.83.9** в панели Timeweb
|
||||||
|
Установить: `85.193.83.9 → mx.csrx.ru`
|
||||||
|
Без PTR Gmail/Yandex будут отклонять или помечать наши письма как спам.
|
||||||
|
Путь: Timeweb → Cloud VPS → tools-сервер → Сеть → Обратная DNS-запись
|
||||||
|
|
||||||
|
- [ ] **Обновить DNS в Cloudflare** после деплоя:
|
||||||
|
- Удалить старую A `mail` → 85.193.83.9 (DNS-only)
|
||||||
|
- Добавить A `mx` → 85.193.83.9 (DNS-only, orange cloud OFF)
|
||||||
|
- Изменить MX запись: `csrx.ru MX 10 mx.csrx.ru.`
|
||||||
|
- Обновить SPF: `v=spf1 mx -all`
|
||||||
|
- Обновить DMARC: `v=DMARC1; p=quarantine; rua=mailto:admin@csrx.ru; ...`
|
||||||
|
- Добавить CNAME `autoconfig` → `mx.csrx.ru`
|
||||||
|
- Добавить CNAME `autodiscover` → `mx.csrx.ru`
|
||||||
|
- A `mail` → 87.249.49.32 (proxied, уже есть — оставить)
|
||||||
A `dash` → `87.249.49.32` (proxied). Grafana сейчас недоступна по домену.
|
A `dash` → `87.249.49.32` (proxied). Grafana сейчас недоступна по домену.
|
||||||
|
|
||||||
- [ ] **Бэкап tools-сервера**
|
- [ ] **Бэкап tools-сервера**
|
||||||
|
|
@ -83,7 +96,11 @@
|
||||||
- [x] Outline wiki с email magic link авторизацией
|
- [x] Outline wiki с email magic link авторизацией
|
||||||
- [x] n8n автоматизация
|
- [x] n8n автоматизация
|
||||||
- [x] docker-mailserver (Postfix + Dovecot), аккаунты: noreply, admin, jack
|
- [x] docker-mailserver (Postfix + Dovecot), аккаунты: noreply, admin, jack
|
||||||
- [x] SnappyMail вебмейл на webmail.csrx.ru
|
- [x] SnappyMail вебмейл, переименован на mail.csrx.ru (было webmail.csrx.ru)
|
||||||
|
- [x] docker-mailserver переименован на mx.csrx.ru (было mail.csrx.ru)
|
||||||
|
- [x] Certbot авторотация сертификата (cron 2x/день + deploy-hook для перезагрузки Postfix/Dovecot)
|
||||||
|
- [x] DMARC ужесточён до p=quarantine (было p=none)
|
||||||
|
- [x] SPF ужесточён до -all (было ~all)
|
||||||
- [x] DKIM/SPF/DMARC DNS-записи для почты
|
- [x] DKIM/SPF/DMARC DNS-записи для почты
|
||||||
- [x] Мониторинг (Prometheus + Grafana + Loki + AlertManager)
|
- [x] Мониторинг (Prometheus + Grafana + Loki + AlertManager)
|
||||||
- [x] CrowdSec IDS + fail2ban
|
- [x] CrowdSec IDS + fail2ban
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,20 @@
|
||||||
|
|
||||||
### SnappyMail вместо Roundcube
|
### SnappyMail вместо Roundcube
|
||||||
**Дата:** 2026-03
|
**Дата:** 2026-03
|
||||||
**Решение:** djmaze/snappymail как веб-клиент почты.
|
**Решение:** djmaze/snappymail как веб-клиент почты на `mail.csrx.ru`.
|
||||||
**Причина:** Лёгкий, современный UI, простой Docker образ.
|
**Причина:** Лёгкий, современный UI, простой Docker образ.
|
||||||
**Альтернативы:** Roundcube (тяжелее, требует MySQL), Rainloop (заброшен).
|
**Альтернативы:** Roundcube (тяжелее, требует MySQL), Rainloop (заброшен).
|
||||||
|
|
||||||
|
### mx.csrx.ru вместо mail.csrx.ru для MX-сервера
|
||||||
|
**Дата:** 2026-03
|
||||||
|
**Решение:** docker-mailserver hostname = `mx.csrx.ru`, веб-клиент = `mail.csrx.ru`.
|
||||||
|
**Причина:** Стандарт индустрии — MX-хост называется `mx`, пользовательский интерфейс — `mail`.
|
||||||
|
Раньше оба указывали на разные вещи под одним именем, что запутывало.
|
||||||
|
**DNS:** `mx.csrx.ru` → 85.193.83.9 (DNS-only, прямые порты SMTP/IMAP).
|
||||||
|
`mail.csrx.ru` → 87.249.49.32 (Cloudflare proxied, HTTPS вебмейл через Traefik).
|
||||||
|
**SPF:** `v=spf1 mx -all` — `-all` жёстче чем `~all`, явно запрещает чужие отправители.
|
||||||
|
**DMARC:** `p=quarantine` — подозрительные письма в спам (было `p=none` — только мониторинг).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
|
||||||
|
|
@ -41,22 +41,48 @@
|
||||||
| Outline | wiki.csrx.ru | ✅ | Wiki, аутентификация через email magic link |
|
| Outline | wiki.csrx.ru | ✅ | Wiki, аутентификация через email magic link |
|
||||||
| n8n | n8n.csrx.ru | ✅ | Автоматизация workflow |
|
| n8n | n8n.csrx.ru | ✅ | Автоматизация workflow |
|
||||||
| docker-mailserver | mail.csrx.ru | ✅ | Postfix + Dovecot, порты 25/465/587/993 |
|
| docker-mailserver | mail.csrx.ru | ✅ | Postfix + Dovecot, порты 25/465/587/993 |
|
||||||
| SnappyMail | webmail.csrx.ru | ✅ | Веб-клиент почты |
|
| SnappyMail | mail.csrx.ru | ✅ | Веб-клиент почты (ранее webmail.csrx.ru) |
|
||||||
|
|
||||||
### Почта (@csrx.ru)
|
### Почта (@csrx.ru)
|
||||||
|
|
||||||
|
**Аккаунты:**
|
||||||
|
|
||||||
| Аккаунт | Назначение |
|
| Аккаунт | Назначение |
|
||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
| noreply@csrx.ru | Системные письма (Outline magic link) |
|
| noreply@csrx.ru | Системные письма (Outline magic link) |
|
||||||
| admin@csrx.ru | Администратор |
|
| admin@csrx.ru | Администратор |
|
||||||
| jack@csrx.ru | Личный |
|
| jack@csrx.ru | Личный |
|
||||||
|
|
||||||
**DNS-записи для почты:**
|
**Архитектура почты:**
|
||||||
- A `mail.csrx.ru` → 85.193.83.9
|
```
|
||||||
- MX `csrx.ru` → `mail.csrx.ru` (priority 10)
|
Входящая: internet → port 25 → mx.csrx.ru (85.193.83.9) → Postfix → Dovecot → IMAP
|
||||||
- TXT `csrx.ru` → `v=spf1 mx ~all`
|
Исходящая: Outlook/SnappyMail → port 587 (STARTTLS) → mx.csrx.ru → Postfix → internet
|
||||||
- TXT `_dmarc.csrx.ru` → `v=DMARC1; p=quarantine; rua=mailto:admin@csrx.ru`
|
Outline: outline контейнер → mailserver:587 (Docker mail-internal сеть) → noreply@
|
||||||
- TXT `mail._domainkey.csrx.ru` → DKIM-ключ (генерируется автоматически при первом деплое)
|
```
|
||||||
|
|
||||||
|
**Настройки почтового клиента:**
|
||||||
|
- IMAP: `mx.csrx.ru` порт 993 (SSL/TLS)
|
||||||
|
- SMTP: `mx.csrx.ru` порт 587 (STARTTLS) или 465 (SSL/TLS)
|
||||||
|
- Веб-клиент: https://mail.csrx.ru (SnappyMail)
|
||||||
|
|
||||||
|
**DNS-записи для почты** (все должны быть в Cloudflare):
|
||||||
|
```
|
||||||
|
A mx → 85.193.83.9 DNS-only (НЕ proxied!)
|
||||||
|
A mail → 87.249.49.32 Proxied (webmail через Traefik)
|
||||||
|
MX @ 10 → mx.csrx.ru.
|
||||||
|
TXT @ → "v=spf1 mx -all"
|
||||||
|
TXT _dmarc → "v=DMARC1; p=quarantine; rua=mailto:admin@csrx.ru; ..."
|
||||||
|
TXT mail._domainkey → "v=DKIM1; k=rsa; p=<KEY>" (генерируется при деплое)
|
||||||
|
CNAME autoconfig → mx.csrx.ru. для Thunderbird autodiscover
|
||||||
|
CNAME autodiscover → mx.csrx.ru. для Outlook autodiscover
|
||||||
|
```
|
||||||
|
|
||||||
|
**rDNS (PTR-запись)** — настроить в панели Timeweb:
|
||||||
|
`85.193.83.9 → mx.csrx.ru` (критично для доставки в Gmail/Yandex!)
|
||||||
|
|
||||||
|
**Автообновление TLS-сертификата:**
|
||||||
|
- certbot renew cron: каждый день в 03:15 и 15:15
|
||||||
|
- deploy-hook: после обновления автоматически перезагружает Postfix+Dovecot
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ domain_auth: "auth.{{ domain_base }}"
|
||||||
domain_status: "status.{{ domain_base }}"
|
domain_status: "status.{{ domain_base }}"
|
||||||
domain_wiki: "wiki.{{ domain_base }}"
|
domain_wiki: "wiki.{{ domain_base }}"
|
||||||
domain_n8n: "n8n.{{ domain_base }}"
|
domain_n8n: "n8n.{{ domain_base }}"
|
||||||
domain_webmail: "webmail.{{ domain_base }}"
|
domain_mail: "mail.{{ domain_base }}" # SnappyMail webmail (HTTPS via Traefik)
|
||||||
|
domain_mx: "mx.{{ domain_base }}" # docker-mailserver FQDN (SMTP/IMAP direct)
|
||||||
|
|
||||||
# Service paths
|
# Service paths
|
||||||
services_root: /opt/services
|
services_root: /opt/services
|
||||||
|
|
|
||||||
|
|
@ -114,12 +114,12 @@ http:
|
||||||
service: n8n
|
service: n8n
|
||||||
middlewares: [rate-limit-strict]
|
middlewares: [rate-limit-strict]
|
||||||
|
|
||||||
webmail:
|
mail:
|
||||||
rule: "Host(`{{ domain_webmail }}`)"
|
rule: "Host(`{{ domain_mail }}`)"
|
||||||
entrypoints: [websecure]
|
entrypoints: [websecure]
|
||||||
tls:
|
tls:
|
||||||
certresolver: letsencrypt
|
certresolver: letsencrypt
|
||||||
service: webmail
|
service: mail
|
||||||
middlewares: [rate-limit-default]
|
middlewares: [rate-limit-default]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
@ -179,7 +179,7 @@ http:
|
||||||
servers:
|
servers:
|
||||||
- url: "http://{{ ip_tools }}:5678"
|
- url: "http://{{ ip_tools }}:5678"
|
||||||
|
|
||||||
webmail:
|
mail:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: "http://{{ ip_tools }}:8888"
|
- url: "http://{{ ip_tools }}:8888"
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,14 @@
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
|
|
||||||
- name: Obtain TLS certificate for mail.{{ domain_base }}
|
- name: Obtain TLS certificate for mx.{{ domain_base }}
|
||||||
ansible.builtin.command: >
|
ansible.builtin.command: >
|
||||||
certbot certonly
|
certbot certonly
|
||||||
--dns-cloudflare
|
--dns-cloudflare
|
||||||
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
|
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
|
||||||
--email {{ acme_email }}
|
--email {{ acme_email }}
|
||||||
--agree-tos --no-eff-email
|
--agree-tos --no-eff-email
|
||||||
-d mail.{{ domain_base }}
|
-d mx.{{ domain_base }}
|
||||||
--non-interactive
|
--non-interactive
|
||||||
register: certbot_result
|
register: certbot_result
|
||||||
changed_when: "'Certificate not yet due for renewal' not in certbot_result.stdout"
|
changed_when: "'Certificate not yet due for renewal' not in certbot_result.stdout"
|
||||||
|
|
@ -62,6 +62,31 @@
|
||||||
- "'Certificate not yet due for renewal' not in certbot_result.stdout"
|
- "'Certificate not yet due for renewal' not in certbot_result.stdout"
|
||||||
- "'Certificate not yet due for renewal' not in certbot_result.stderr"
|
- "'Certificate not yet due for renewal' not in certbot_result.stderr"
|
||||||
|
|
||||||
|
- name: Deploy certbot renewal deploy-hook (reload mailserver after cert renewal)
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/letsencrypt/renewal-hooks/deploy/reload-mailserver.sh
|
||||||
|
mode: "0750"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
content: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Triggered by certbot after successful cert renewal
|
||||||
|
# Reloads Postfix + Dovecot TLS without full restart
|
||||||
|
set -euo pipefail
|
||||||
|
if docker ps --format '{{ '{{' }}.Names{{ '}}' }}' | grep -q '^mailserver$'; then
|
||||||
|
docker exec mailserver supervisorctl restart postfix dovecot
|
||||||
|
echo "[$(date)] mailserver TLS reloaded after cert renewal"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Schedule certbot auto-renewal (twice daily, certbot renews only when needed)
|
||||||
|
ansible.builtin.cron:
|
||||||
|
name: "certbot renew"
|
||||||
|
minute: "15"
|
||||||
|
hour: "3,15"
|
||||||
|
job: "certbot renew --quiet --deploy-hooks 2>&1 | logger -t certbot"
|
||||||
|
user: root
|
||||||
|
state: present
|
||||||
|
|
||||||
# ── Open mail ports in UFW ────────────────────────────────────────────────────
|
# ── Open mail ports in UFW ────────────────────────────────────────────────────
|
||||||
- name: Allow SMTP inbound (port 25)
|
- name: Allow SMTP inbound (port 25)
|
||||||
community.general.ufw:
|
community.general.ufw:
|
||||||
|
|
|
||||||
|
|
@ -138,26 +138,27 @@ services:
|
||||||
mailserver:
|
mailserver:
|
||||||
image: {{ mailserver_image }}
|
image: {{ mailserver_image }}
|
||||||
container_name: mailserver
|
container_name: mailserver
|
||||||
hostname: mail
|
hostname: mx
|
||||||
domainname: {{ domain_base }}
|
domainname: {{ domain_base }}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mail-internal # Outline → mailserver (internal, port 25, no auth)
|
- mail-internal # Outline → mailserver (internal, port 587 with auth)
|
||||||
- front # inbound/outbound internet SMTP
|
- front # inbound/outbound internet SMTP
|
||||||
ports:
|
ports:
|
||||||
- "{{ ip_tools }}:25:25" # SMTP inbound (MX delivery from internet)
|
- "{{ ip_tools }}:25:25" # SMTP inbound (MX delivery from internet)
|
||||||
- "{{ ip_tools }}:587:587" # SMTP submission (mail clients)
|
- "{{ ip_tools }}:587:587" # SMTP submission (mail clients, STARTTLS)
|
||||||
- "{{ ip_tools }}:993:993" # IMAPS (mail clients)
|
- "{{ ip_tools }}:993:993" # IMAPS (mail clients, TLS)
|
||||||
- "{{ ip_tools }}:465:465" # SMTPS (mail clients, alternative)
|
- "{{ ip_tools }}:465:465" # SMTPS (mail clients, implicit TLS)
|
||||||
environment:
|
environment:
|
||||||
- ENABLE_RSPAMD=1 # spam filter for inbound mail
|
- ENABLE_RSPAMD=1 # spam filter + DKIM signing
|
||||||
- ENABLE_CLAMAV=0 # no antivirus (saves RAM)
|
- ENABLE_CLAMAV=0 # no antivirus (saves RAM)
|
||||||
- ENABLE_FAIL2BAN=0 # host fail2ban already handles this
|
- ENABLE_FAIL2BAN=0 # host fail2ban already handles this
|
||||||
- POSTFIX_INET_PROTOCOLS=ipv4
|
- POSTFIX_INET_PROTOCOLS=ipv4
|
||||||
- SSL_TYPE=letsencrypt # TLS via certbot cert at /etc/letsencrypt
|
- SSL_TYPE=letsencrypt # TLS certs from /etc/letsencrypt/live/mx.csrx.ru/
|
||||||
- LOG_LEVEL=warn
|
- LOG_LEVEL=warn
|
||||||
- OVERRIDE_HOSTNAME=mail.{{ domain_base }}
|
- OVERRIDE_HOSTNAME=mx.{{ domain_base }}
|
||||||
- POSTMASTER_ADDRESS=admin@{{ domain_base }}
|
- POSTMASTER_ADDRESS=admin@{{ domain_base }}
|
||||||
|
- POSTFIX_MESSAGE_SIZE_LIMIT=26214400 # 25 MB max message size
|
||||||
volumes:
|
volumes:
|
||||||
- {{ tools_root }}/mailserver/mail-data:/var/mail
|
- {{ tools_root }}/mailserver/mail-data:/var/mail
|
||||||
- {{ tools_root }}/mailserver/mail-state:/var/mail-state
|
- {{ tools_root }}/mailserver/mail-state:/var/mail-state
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue