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.
|
||||
$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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue