feat(mail): rename mail→mx, webmail→mail.csrx.ru + reliability
Some checks failed
CI/CD / syntax-check (push) Successful in 1m23s
CI/CD / deploy (push) Has been cancelled

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:
jack 2026-03-22 20:07:59 +07:00
parent 66b70827df
commit 1e638055c8
8 changed files with 138 additions and 37 deletions

View file

@ -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.
$TTL 3600
; ── A-записи сервисов ────────────────────────────────────────────────────────
; ── A-записи сервисов (Cloudflare proxied) ───────────────────────────────────
vault IN A 87.249.49.32
git 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
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)
; ── Почта ────────────────────────────────────────────────────────────────────
@ IN MX 10 mail.csrx.ru.
; ── A-записи прямого подключения (DNS-only, Cloudflare proxy OFF) ─────────────
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
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:admin@csrx.ru"
; SPF — разрешаем отправку только с IP из MX-записи (85.193.83.9)
; mx = "IP-адреса из всех MX-записей" = mx.csrx.ru = 85.193.83.9
@ IN TXT "v=spf1 mx -all"
; DKIM — добавить после первого запуска Stalwart (взять ключ из mail.csrx.ru → DKIM)
; Пример как будет выглядеть:
; mail._domainkey IN TXT "v=DKIM1; k=rsa; p=<ключ из Stalwart>"
; 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"
; 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.

View file

@ -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 сейчас недоступна по домену.
- [ ] **Бэкап tools-сервера**
@ -83,7 +96,11 @@
- [x] Outline wiki с email magic link авторизацией
- [x] n8n автоматизация
- [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] Мониторинг (Prometheus + Grafana + Loki + AlertManager)
- [x] CrowdSec IDS + fail2ban

View file

@ -80,10 +80,20 @@
### SnappyMail вместо Roundcube
**Дата:** 2026-03
**Решение:** djmaze/snappymail как веб-клиент почты.
**Решение:** djmaze/snappymail как веб-клиент почты на `mail.csrx.ru`.
**Причина:** Лёгкий, современный UI, простой Docker образ.
**Альтернативы:** 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

View file

@ -41,22 +41,48 @@
| Outline | wiki.csrx.ru | ✅ | Wiki, аутентификация через email magic link |
| n8n | n8n.csrx.ru | ✅ | Автоматизация workflow |
| docker-mailserver | mail.csrx.ru | ✅ | Postfix + Dovecot, порты 25/465/587/993 |
| SnappyMail | webmail.csrx.ru | ✅ | Веб-клиент почты |
| SnappyMail | mail.csrx.ru | ✅ | Веб-клиент почты (ранее webmail.csrx.ru) |
### Почта (@csrx.ru)
**Аккаунты:**
| Аккаунт | Назначение |
|---------|-----------|
| noreply@csrx.ru | Системные письма (Outline magic link) |
| admin@csrx.ru | Администратор |
| jack@csrx.ru | Личный |
**DNS-записи для почты:**
- A `mail.csrx.ru` → 85.193.83.9
- MX `csrx.ru``mail.csrx.ru` (priority 10)
- TXT `csrx.ru``v=spf1 mx ~all`
- TXT `_dmarc.csrx.ru``v=DMARC1; p=quarantine; rua=mailto:admin@csrx.ru`
- TXT `mail._domainkey.csrx.ru` → DKIM-ключ (генерируется автоматически при первом деплое)
**Архитектура почты:**
```
Входящая: internet → port 25 → mx.csrx.ru (85.193.83.9) → Postfix → Dovecot → IMAP
Исходящая: Outlook/SnappyMail → port 587 (STARTTLS) → mx.csrx.ru → Postfix → internet
Outline: outline контейнер → mailserver:587 (Docker mail-internal сеть) → noreply@
```
**Настройки почтового клиента:**
- 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
---

View file

@ -12,7 +12,8 @@ domain_auth: "auth.{{ domain_base }}"
domain_status: "status.{{ domain_base }}"
domain_wiki: "wiki.{{ 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
services_root: /opt/services

View file

@ -114,12 +114,12 @@ http:
service: n8n
middlewares: [rate-limit-strict]
webmail:
rule: "Host(`{{ domain_webmail }}`)"
mail:
rule: "Host(`{{ domain_mail }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: webmail
service: mail
middlewares: [rate-limit-default]
services:
@ -179,7 +179,7 @@ http:
servers:
- url: "http://{{ ip_tools }}:5678"
webmail:
mail:
loadBalancer:
servers:
- url: "http://{{ ip_tools }}:8888"

View file

@ -46,14 +46,14 @@
owner: root
group: root
- name: Obtain TLS certificate for mail.{{ domain_base }}
- name: Obtain TLS certificate for mx.{{ domain_base }}
ansible.builtin.command: >
certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
--email {{ acme_email }}
--agree-tos --no-eff-email
-d mail.{{ domain_base }}
-d mx.{{ domain_base }}
--non-interactive
register: certbot_result
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.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 ────────────────────────────────────────────────────
- name: Allow SMTP inbound (port 25)
community.general.ufw:

View file

@ -138,26 +138,27 @@ services:
mailserver:
image: {{ mailserver_image }}
container_name: mailserver
hostname: mail
hostname: mx
domainname: {{ domain_base }}
restart: unless-stopped
networks:
- mail-internal # Outline → mailserver (internal, port 25, no auth)
- mail-internal # Outline → mailserver (internal, port 587 with auth)
- front # inbound/outbound internet SMTP
ports:
- "{{ ip_tools }}:25:25" # SMTP inbound (MX delivery from internet)
- "{{ ip_tools }}:587:587" # SMTP submission (mail clients)
- "{{ ip_tools }}:993:993" # IMAPS (mail clients)
- "{{ ip_tools }}:465:465" # SMTPS (mail clients, alternative)
- "{{ ip_tools }}:587:587" # SMTP submission (mail clients, STARTTLS)
- "{{ ip_tools }}:993:993" # IMAPS (mail clients, TLS)
- "{{ ip_tools }}:465:465" # SMTPS (mail clients, implicit TLS)
environment:
- ENABLE_RSPAMD=1 # spam filter for inbound mail
- ENABLE_RSPAMD=1 # spam filter + DKIM signing
- ENABLE_CLAMAV=0 # no antivirus (saves RAM)
- ENABLE_FAIL2BAN=0 # host fail2ban already handles this
- 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
- OVERRIDE_HOSTNAME=mail.{{ domain_base }}
- OVERRIDE_HOSTNAME=mx.{{ domain_base }}
- POSTMASTER_ADDRESS=admin@{{ domain_base }}
- POSTFIX_MESSAGE_SIZE_LIMIT=26214400 # 25 MB max message size
volumes:
- {{ tools_root }}/mailserver/mail-data:/var/mail
- {{ tools_root }}/mailserver/mail-state:/var/mail-state