Настройка Mail-Receiver с Cloudflare
Если вы используете прокси-сервис Cloudflare для вашего самохостингового форума Discourse, для получения входящих писем требуется дополнительная настройка. Cloudflare не перенаправляет SMTP-трафик (порт 25) для доменов, находящихся под прокси — письма, отправленные на ваш домен, будут тихо отброшены, если DNS не настроены правильно. Хорошая новость заключается в том, что существует несколько способов решения этой проблемы, в зависимости от ваших требований к безопасности и от того, хотите ли вы оставить всё на одном сервере или изолировать почтовую инфраструктуру.
В следующей таблице кратко описаны три варианта:
|
Вариант 1 |
Вариант 2 |
Вариант 3 |
| Описание |
mail-receiver на текущем хосте, отдельный почтовый поддомен |
Вариант 1 + Certbot с валидацией DNS для TLS |
Выделенный VPS для mail-receiver |
| Работает с прокси Cloudflare |
 |
 |
 |
| IP-адрес сервера открыт в DNS |
 |
 |
(только VPS почты — Discourse скрыт) |
| TLS-шифрование SMTP |
 |
 |
 |
| Изоляция почты от веб-сервера |
 |
 |
 |
| Дополнительные затраты |
 |
 |
 |
| Сложность |
Низкая |
Средняя |
Средняя |
Вариант 1 — это самый быстрый способ запустить mail-receiver. Вы добавляете выделенный почтовый поддомен (например, mail.yourdomain.com) как запись DNS-only в Cloudflare, указывающую на ваш существующий сервер, что позволяет обойти прокси для SMTP-трафика, оставляя основной домен полностью под прокси. Недостатком является то, что IP-адрес вашего существующего сервера становится видимым в DNS, а SMTP-соединение не шифруется через TLS.
Вариант 2 строится на Варианте 1, добавляя сертификат Let’s Encrypt через DNS-вызов Certbot, что обеспечивает TLS-шифрование SMTP. Поскольку DNS-вызов не требует порта 80 и простоя веб-сервера, это можно добавить на существующий сервер без перебоев.
Вариант 3 переносит mail-receiver на отдельный недорогой VPS (обычно $4–6 в месяц у провайдеров вроде DigitalOcean). Это полностью изолирует вашу почтовую инфраструктуру от веб-сервера, что означает, что IP-адрес вашего основного сервера никогда не раскрывается. Это также дает вам чистую среду без конфликтов портов и является рекомендуемым подходом для производственных сред, где важны безопасность и разделение ответственности.
Вариант 1 — mail-receiver на текущем хосте с отдельным почтовым поддоменом
Поскольку Cloudflare не перенаправляет SMTP-трафик для доменов, находящихся под прокси, mail-receiver требует своего собственного поддомена, который полностью обходит прокси Cloudflare. Ваш основной домен (например, forums.domain.tld) может оставаться полностью под прокси — только почтовый поддомен должен быть настроен как DNS-only.
Настройка DNS в Cloudflare
Вам нужно создать две новые записи DNS. Обе используют тот же IP-адрес, что и ваш существующий сервер.
1. Создайте запись A для почтового поддомена:
| Тип |
Имя |
Значение |
Статус прокси |
| A |
mail |
YOUR.SERVER.IP |
Только DNS (серое облако) |
Критически важно, чтобы эта запись была установлена в режим Только DNS. Если включено оранжевое прокси-облако, Cloudflare перехватит трафик, и SMTP не будет работать.
2. Создайте запись MX, указывающую на новый поддомен:
| Тип |
Имя |
Значение |
Приоритет |
| MX |
@ |
mail.domain.tld |
10 |
Установка
Следуйте основным инструкциям по установке mail-receiver с одним изменением — установите MAIL_DOMAIN в вашей конфигурации на новый почтовый поддомен, а не на основной домен форума:
MAIL_DOMAIN: mail.domain.tld
Компромиссы
Этот вариант позволяет запустить mail-receiver с минимальными усилиями и без дополнительных затрат. Две вещи, о которых следует знать: IP-адрес вашего сервера будет публично виден в DNS через почтовый поддомен, и SMTP-соединения не будут шифроваться через TLS. Если TLS является обязательным требованием, см. Вариант 2, который строится непосредственно на этой настройке.
Вариант 2 — Вариант 1 с TLS-шифрованием SMTP
Вариант 2 строится непосредственно на Варианте 1, добавляя сертификат Let’s Encrypt для включения TLS-шифрования SMTP. Вся работа с сертификатами выполняется на хост-сервере, а не внутри контейнеров Discourse или mail-receiver.
Поскольку Discourse уже занимает порт 80, стандартный метод HTTP-валидации Certbot недоступен. Вместо этого мы используем метод DNS-вызова, который подтверждает владение доменом путем создания временной TXT-записи в Cloudflare. Это не требует доступа к порту 80, не требует простоя веб-сервера и может быть полностью автоматизировано с помощью API-токена Cloudflare.
Как работает DNS-вызов
Certbot → Создает TXT-запись _acme-challenge.mail.domain.tld в Cloudflare
Let's Encrypt → Ищет эту TXT-запись → Валидирует → Выдает сертификат
Certbot → Автоматически удаляет TXT-запись
После выдачи сертификата файлы сертификата копируются в общую папку mail-receiver, чтобы контейнер мог их использовать. Certbot автоматически обрабатывает продление в фоновом режиме через таймер systemd — единственным дополнительным шагом является хук развертывания, который копирует файлы продленного сертификата и перезапускает контейнер после каждого продления.
Предварительные требования
Перед началом выполните все шаги в Варианте 1. Записи DNS и конфигурация mail-receiver из Варианта 1 остаются неизменными — этот вариант только добавляет слой TLS-сертификата поверх.
Компромиссы
Этот вариант дает вам TLS-шифрование SMTP без дополнительных затрат, оставляя всё на вашем существующем сервере. Основной момент заключается в том, что IP-адрес вашего сервера остается видимым в DNS, как и в Варианте 1. Если требуется полная изоляция инфраструктуры, см. Вариант 3.
Настройка
1 — Установите certbot и плагин Cloudflare для certbot:
bash
apt install certbot python3-certbot-dns-cloudflare -y
2 — Создайте API-токен Cloudflare:
- Перейдите в Cloudflare → Мой профиль → API-токены → Создать токен
- Используйте шаблон «Редактирование DNS зоны»
- Разрешения:
Зона → DNS → Редактирование
- Ресурсы зоны:
Включить → Конкретная зона → lotuselan.net
- Ограничения IP: Настройте только для разрешения с IP-адреса вашего сервера
- Скопируйте токен
3 — Сохраните токен в файл учетных данных:
bash
mkdir -p /etc/letsencrypt/cloudflare
nano /etc/letsencrypt/cloudflare/credentials.ini
Вставьте:
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
Заблокируйте файл:
bash
chmod 600 /etc/letsencrypt/cloudflare/credentials.ini
4 — Запросите сертификат:
Обновите следующую команду, указав ваш административный адрес электронной почты и имя домена.
bash
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
--non-interactive \
--agree-tos \
--email youremailadress@domain.tld \
-d mail.domain.tld
В результатах должно быть сообщение:
Certbot настроил запланированную задачу для автоматического продления этого сертификата в фоновом режиме.
Certbot настроит cron для проверки истечения срока действия сертификатов дважды в день. Он будет продлевать сертификаты, когда до истечения срока останется 30 дней. Вы можете проверить это, выполнив:
# Проверьте, активен ли таймер systemd (на большинстве современных систем Ubuntu)
systemctl status certbot.timer
# Или проверьте, был ли добавлен cron-задание
cat /etc/cron.d/certbot
Теперь у вас есть TLS-сертификаты на вашем сервере для нового доменного имени mail-receiver. Они находятся не в том месте, где их можно использовать.
5 — Настройте скрипт развертывания для перемещения файлов
Поскольку certbot автоматически продлевает сертификаты, вам нужен только скрипт для обработки частей, специфичных для Discourse — копирования продленных сертификатов и пересборки mail-receiver. Вы можете значительно упростить скрипт, используя встроенный хук развертывания certbot, который запускается автоматически после успешного продления.
Создайте файл хука развертывания:
bash
nano /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
chmod +x /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
Вставьте это в файл. Обновите критические переменные в верхней части с вашими данными (имя домена и адрес электронной почты):
bash
#!/bin/bash
DOMAIN="mail.domain.tld"
DISCOURSE_DIR="/var/discourse"
CERT_SRC="/etc/letsencrypt/live/${DOMAIN}"
CERT_DEST_1="${DISCOURSE_DIR}/shared/mail-receiver/letsencrypt/${DOMAIN}"
CERT_DEST_2="${DISCOURSE_DIR}/shared/mail-receiver/letsencrypt/${DOMAIN}_ecc"
ADMIN_EMAIL="admin email address"
LOG_FILE="/var/log/mail-cert-renewal.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "=== Хук развертывания Certbot запущен для ${DOMAIN} ==="
# Копирование сертификатов (используйте -L для разрешения символических ссылок)
for DEST in "$CERT_DEST_1" "$CERT_DEST_2"; do
mkdir -p "$DEST"
cp -L "${CERT_SRC}/fullchain.pem" "${DEST}/fullchain.pem"
cp -L "${CERT_SRC}/privkey.pem" "${DEST}/privkey.pem"
cp -L "${CERT_SRC}/cert.pem" "${DEST}/cert.pem"
cp -L "${CERT_SRC}/chain.pem" "${DEST}/chain.pem"
chmod 644 "${DEST}/fullchain.pem" "${DEST}/cert.pem" "${DEST}/chain.pem"
chmod 600 "${DEST}/privkey.pem"
log "Сертификаты скопированы в ${DEST}"
done
# Пересборка mail-receiver
cd "$DISCOURSE_DIR" || { echo "Невозможно перейти в ${DISCOURSE_DIR}" | mail -s "[ОШИБКА] Хук развертывания сертификата почты не удался" "$ADMIN_EMAIL"; exit 1; }
log "Пересборка mail-receiver..."
if ./launcher rebuild mail-receiver >> "$LOG_FILE" 2>&1; then
log "mail-receiver успешно пересобран"
else
log "ОШИБКА: пересборка не удалась"
echo "Пересборка mail-receiver не удалась после продления сертификата. Проверьте ${LOG_FILE}" | \
mail -s "[ОШИБКА] Хук развертывания сертификата почты не удался" "$ADMIN_EMAIL"
exit 1
fi
log "=== Хук развертывания успешно завершен ==="
Ручное задание cron не требуется вообще — certbot оркестрирует весь процесс. Хук развертывания срабатывает только тогда, когда происходит фактическое продление, поэтому ваш mail-receiver не будет получать ненужные пересборки в дни, когда certbot проверяет, но не продлевает.
Чтобы протестировать хук продления, выполните следующее:
bash
bash /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
Если всё настроено правильно, это позволит
→ скопировать сертификаты в директории Discourse
→ пересобрать mail-receiver
→ залогировать всё
6 — Настройте TLS в mail-receiver.yml
Теперь вы можете обновить файл mail.receiver.yml. Обратите внимание, что пути к директориям и томам отличаются
nano /var/discourse/containers/mail-receiver.yml
```\nСо следующими параметрами. Раскомментируйте и введите ваши конкретные данные.
POSTCONF_smtpd_tls_key_file: /letsencrypt/mail.domain.tld/privkey.pem
POSTCONF_smtpd_tls_cert_file: /letsencrypt/mail.domain.tld/fullchain.pem
POSTCONF_smtpd_tls_security_level: may
- volume:
host: /var/discourse/shared/mail-receiver/letsencrypt
guest: /letsencrypt
Вы можете пересобрать mail-receiver и проверить, что всё работает.
./launcher rebuild mail-receiver
Чтобы проверить логи
./launcher logs mail-receiver
Логи должны быть чистыми. Начните тестировать получение ответов напрямую на ваш сервер.
В качестве дополнительного бонуса, это будет пересобирать mail-receiver примерно каждые 60 дней. Когда вы пересобираете основное приложение Discourse, оно загружает последнее программное обеспечение. Таким образом, ваш mail-receiver регулярно остается актуальным.
---
## Вариант 3 — Выделенный VPS для mail-receiver
Вариант 3 переносит mail-receiver с вашего основного сервера Discourse полностью на выделенный VPS. Это рекомендуемый подход для производственных сред, где важны безопасность и разделение ответственности. При стоимости примерно $4–6 в месяц у провайдеров, таких как Hetzner, Vultr или DigitalOcean, это единственный вариант с дополнительными затратами, но он предлагает самую чистую и изолированную настройку из трех.
Поскольку это автономный сервер, на котором не запущены другие службы, нет конфликтов портов с Discourse и нет ограничений вокруг порта 80. Это означает, что вы можете использовать стандартный режим standalone Certbot для выдачи сертификатов, а не метод DNS-вызова, требуемый в Варианте 2, что значительно упрощает настройку сертификатов.
### Как это работает
mail-receiver развертывается как автономный контейнер Docker на новом VPS. Он слушает порт 25, принимает входящий SMTP из интернета и пересылает обработанную почту на ваш экземпляр Discourse через HTTPS, используя API Discourse — точно так же, как в Варианте 1 и 2, просто работает на отдельном оборудовании.
### Ключевые преимущества перед Вариантом 1 и 2
IP-адрес вашего основного сервера никогда не раскрывается в DNS. Запись A почтового поддомена указывает на IP нового VPS, а не на ваш сервер Discourse, что означает, что даже если почтовый поддомен будет запрошен, он раскроет только выделенный почтовый сервер. Ваш сервер Discourse остается полностью скрытым за Cloudflare.
Кроме того, любые проблемы с почтовым сервером — высокая нагрузка от спама, неправильная конфигурация или необходимая перезагрузка — не оказывают никакого влияния на ваш форум Discourse, и наоборот.
### Компромиссы
Это самая сложная настройка из трех вариантов, так как требует выделения и обслуживания второго сервера. Однако, после настройки она в значительной степени самообслуживаема — контейнер автоматически перезапускается при сбое, Certbot обрабатывает продление сертификатов, а ежеквартальный скрипт обновления держит образ актуальным. Текущие административные накладные расходы минимальны.
Вот полный набор инструкций для Варианта 3, написанный для общей аудитории:
---
## Вариант 3 — Инструкции
## Что вам понадобится
- Рабочий форум Discourse
- Домен, управляемый в Cloudflare
- API-ключ Discourse (инструкции ниже)
- Новый VPS-сервер под управлением Ubuntu 24.04 — примерно $4–6 в месяц от Hetzner, Vultr или DigitalOcean
- SSH-доступ к новому VPS от root
---
## Шаг 1 — Выберите и подготовьте VPS
Зарегистрируйтесь у выбранного вами провайдера VPS. Минимально рекомендуемые характеристики: 1 vCPU и 1 ГБ ОЗУ — mail-receiver легкий и не требует много ресурсов.
Рекомендуемые провайдеры и примерная ежемесячная стоимость:
| Провайдер | План | Стоимость |
|---|---|---|
| Hetzner | CX22 (2 vCPU, 4 ГБ ОЗУ) | ~$4/месяц |
| Vultr | Cloud Compute 1 ГБ | ~$6/месяц |
| DigitalOcean | Basic Droplet 1 ГБ | ~$6/месяц |
При подготовке сервера:
- Выберите **Ubuntu 24.04 LTS** в качестве операционной системы
- Добавьте ваш SSH-публичный ключ во время настройки
- Запишите публичный IPv4-адрес, назначенный серверу — он понадобится для DNS
---
## Шаг 2 — Настройте DNS в Cloudflare
Прежде чем касаться сервера, настройте записи DNS. Это даст DNS время на распространение, пока вы проходите остальные шаги.
Войдите в Cloudflare и добавьте следующие две записи для вашего домена:
**Запись A — указывает ваш почтовый поддомен на новый VPS:**
| Тип | Имя | Значение | Статус прокси |
|---|---|---|---|
| A | `mail` | `YOUR.VPS.IP` | 🔘 Только DNS (серое облако) |
**Запись MX — сообщает интернету, куда доставлять вашу почту:**
| Тип | Имя | Значение | Приоритет |
|---|---|---|---|
| MX | `@` | `mail.yourdomain.tld` | 10 |
> Запись A **должна** быть установлена в режим Только DNS (серое облако). Если прокси Cloudflare включен для этой записи, SMTP-трафик будет заблокирован, и почта не будет доставлена.
Убедитесь, что записи правильно разрешаются, прежде чем продолжить:
```bash
# Запустите с любой машины — должно вернуть IP вашего VPS, а не IP Cloudflare
nslookup mail.yourdomain.tld
# Должно показать mail.yourdomain.tld как почтовый сервер
nslookup -type=MX yourdomain.tld
IP-адреса Cloudflare всегда начинаются с 104., 172.64–68. или 162.158. — если вы видите их, запись A все еще находится под прокси.
Шаг 3 — Начальная настройка сервера
Подключитесь по SSH к вашему новому VPS:
ssh root@YOUR.VPS.IP
Обновите систему:
apt update && apt upgrade -y
Установите имя хоста сервера, соответствующее вашему почтовому поддомену:
hostnamectl set-hostname mail.yourdomain.tld
hostname
Шаг 4 — Настройте брандмауэр
apt install ufw -y
# Разрешите SSH первым — сделайте это перед включением UFW, иначе вы заблокируете себя
ufw allow 22/tcp
# Разрешите входящий SMTP
ufw allow 25/tcp
# Включите брандмауэр
ufw enable
# Подтвердите правила
ufw status
Шаг 5 — Отключите системный Postfix
Ubuntu 24.04 иногда устанавливает Postfix по умолчанию. Контейнер mail-receiver запускает свой собственный экземпляр Postfix внутри Docker, поэтому системный должен быть отключен, чтобы освободить порт 25.
# Проверьте, использует ли что-либо порт 25
ss -tlnp | grep :25
# Если Postfix указан, отключите его
systemctl stop postfix
systemctl disable postfix
# Подтвердите, что порт 25 теперь свободен
ss -tlnp | grep :25
Шаг 6 — Установите Docker
Удалите любые старые пакеты Docker, которые могли прийти из репозитория Ubuntu по умолчанию:
apt remove docker docker.io docker-compose docker-doc podman-docker -y
Установите Docker из официального репозитория Docker:
# Установите предварительные требования
apt install ca-certificates curl gnupg -y
# Добавьте официальный GPG-ключ Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Добавьте репозиторий apt Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
# Обновите apt и установите Docker Engine с плагин Compose
apt update
apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Включите автоматический запуск Docker при загрузке
systemctl enable docker
systemctl start docker
# Убедитесь, что Docker и Compose работают
docker --version
docker compose version
Шаг 7 — Установите Certbot
apt install certbot -y
# Проверьте
certbot --version
Шаг 8 — Откройте порт 80 и получите TLS-сертификат
На этом сервере не запущен веб-сервер, поэтому Certbot может использовать режим standalone — он временно запускает свой собственный веб-сервер на порту 80 ровно настолько, чтобы завершить вызов Let’s Encrypt, а затем выключает его.
# Временно откройте порт 80
ufw allow 80/tcp
# Запросите сертификат
certbot certonly \
--standalone \
--non-interactive \
--agree-tos \
--email admin@yourdomain.tld \
-d mail.yourdomain.tld
# Закройте порт 80 — больше не нужен
ufw delete allow 80/tcp
# Подтвердите, что сертификат выдан
certbot certificates
# Подтвердите настройку автоматического продления Certbot
systemctl status certbot.timer
Вы должны увидеть вывод, подтверждающий путь к сертификату:
Имя сертификата: mail.yourdomain.tld
Дата истечения: YYYY-MM-DD
Путь к сертификату: /etc/letsencrypt/live/mail.yourdomain.tld/fullchain.pem
Путь к закрытому ключу: /etc/letsencrypt/live/mail.yourdomain.tld/privkey.pem
Настройте порт 80 для автоматического продления
Certbot автоматически продлевает сертификаты через фоновый таймер systemd. Поскольку порт 80 обычно закрыт на этом сервере, вам нужно указать Certbot автоматически открывать и закрывать его во время каждого продления. Отредактируйте конфигурацию продления:
nano /etc/letsencrypt/renewal/mail.yourdomain.tld.conf
Добавьте эти две строки в раздел [renewalparams]:
pre_hook = ufw allow 80/tcp
post_hook = ufw delete allow 80/tcp
Протестируйте конфигурацию автоматического продления с помощью пробного запуска:
certbot renew --dry-run
Вы должны увидеть, как pre-hook открывает порт 80, вызов завершается успешно, а post-hook закрывает порт 80. Хук развертывания будет показан как пропущенный — это нормально для пробных запусков, так как фактический сертификат не выдается.
Шаг 9 — Создайте рабочую директорию и скопируйте сертификаты
# Создайте структуру рабочей директории
mkdir -p /opt/mail-receiver/certs/mail.yourdomain.tld
mkdir -p /opt/mail-receiver/postfix-spool
# Скопируйте сертификаты — флаг -L разрешает символические ссылки Let's Encrypt на реальные файлы
cp -L /etc/letsencrypt/live/mail.yourdomain.tld/fullchain.pem \
/opt/mail-receiver/certs/mail.yourdomain.tld/
cp -L /etc/letsencrypt/live/mail.yourdomain.tld/privkey.pem \
/opt/mail-receiver/certs/mail.yourdomain.tld/
cp -L /etc/letsencrypt/live/mail.yourdomain.tld/cert.pem \
/opt/mail-receiver/certs/mail.yourdomain.tld/
cp -L /etc/letsencrypt/live/mail.yourdomain.tld/chain.pem \
/opt/mail-receiver/certs/mail.yourdomain.tld/
# Заблокируйте закрытый ключ
chmod 600 /opt/mail-receiver/certs/mail.yourdomain.tld/privkey.pem
# Убедитесь, что все четыре файла присутствуют
ls -la /opt/mail-receiver/certs/mail.yourdomain.tld/
Шаг 10 — Получите API-ключ Discourse
На вашем форуме Discourse:
- Войдите как администратор
- Перейдите в Admin → API → Новый API-ключ
- Установите следующее:
- Описание: mail-receiver
- Уровень пользователя: Все пользователи
- Область: Глобальная
- Нажмите Сохранить и скопируйте сгенерированный ключ — он понадобится вам на следующем шаге
Шаг 11 — Создайте docker-compose.yml
nano /opt/mail-receiver/docker-compose.yml
Вставьте следующее, заменив все значения, показанные заглавными буквами, на свои:
services:
mail-receiver:
image: discourse/mail-receiver:release
restart: always
ports:
- "25:25"
volumes:
# Почтовый каталог Postfix — сохраняет почту между перезапусками контейнера
- ./postfix-spool:/var/spool/postfix
# TLS-сертификаты, смонтированные в контейнер
- ./certs:/letsencrypt
environment:
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LANGUAGE: en_US.UTF-8
# Домен для приема почты
MAIL_DOMAIN: mail.yourdomain.tld
# Точка входа handle_mail вашего экземпляра Discourse
DISCOURSE_MAIL_ENDPOINT: 'https://forums.yourdomain.tld/admin/email/handle_mail'
DISCOURSE_BASE_URL: ''https://forums.yourdomain.tld'
# API-ключ из Шага 10
DISCOURSE_API_KEY: YOUR_API_KEY_HERE
# Оставьте как system, если вы не переименовали этого пользователя в Discourse
DISCOURSE_API_USERNAME: system
# Пути к TLS-сертификатам — это пути внутри контейнера
POSTCONF_smtpd_tls_key_file: /letsencrypt/mail.yourdomain.tld/privkey.pem
POSTCONF_smtpd_tls_cert_file: /letsencrypt/mail.yourdomain.tld/fullchain.pem
POSTCONF_smtpd_tls_security_level: may
# Объявление имени хоста Postfix
POSTCONF_myhostname: mail.yourdomain.tld
Шаг 12 — Запустите контейнер
cd /opt/mail-receiver
# Загрузите последний образ
docker compose pull
# Запустите контейнер в отсоединенном режиме
docker compose up -d
# Подтвердите, что он работает
docker compose ps
# Следите за журналами запуска — ищите 'daemon started' без ошибок TLS
docker compose logs -f
Здоровый запуск выглядит так:
postfix/master[1]: daemon started -- version 3.x.x, configuration /etc/postfix
Нажмите Ctrl+C, чтобы прекратить отслеживание журналов.
Шаг 13 — Настройте Discourse для приема входящей почты
На вашем форуме Discourse:
- Перейдите в Admin → Settings → Email
- Включите ответ по электронной почте
- Установите адрес электронной почты для ответов на:
reply+%{reply_key}@mail.yourdomain.tld
- Перейдите в Admin → Email → Incoming для мониторинга поступающей почты
Шаг 14 — Протестируйте настройку
Запустите эти тесты с вашего локального компьютера, а не с VPS:
# Проверьте, что порт 25 доступен и Postfix отвечает
telnet mail.yourdomain.tld 25
# Ожидаемый ответ: 220 mail.yourdomain.tld ESMTP Postfix
# Проверьте TLS-рукопожатие
openssl s_client -connect mail.yourdomain.tld:25 -starttls smtp
# Должны отображаться детали вашего сертификата Let's Encrypt без ошибок
Затем отправьте тестовое письмо на любой адрес в вашем почтовом домене (например, test@mail.yourdomain.tld) и следите за журналами контейнера:
docker compose -f /opt/mail-receiver/docker-compose.yml logs -f
Успешная доставка выглядит так:
postfix/smtpd[x]: connect from mail-server.example.com
postfix/smtpd[x]: starttls=1
postfix/pipe[x]: status=sent (delivered via discourse service)
Шаг 15 — Настройте хук развертывания Certbot
Создайте хук развертывания, чтобы после каждого автоматического продления сертификата новые файлы сертификата копировались в путь монтирования контейнера, и контейнер перезапускался для их загрузки:
nano /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
chmod +x /etc/letsencrypt/renewal-hooks/deploy/mail-receiver-deploy.sh
Вставьте следующее, заменив домен и электронную почту на свои:
#!/bin/bash
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
DOMAIN="mail.yourdomain.tld"
CERT_SRC="/etc/letsencrypt/live/${DOMAIN}"
CERT_DEST="/opt/mail-receiver/certs/${DOMAIN}"
LOG="/var/log/mail-cert-renewal.log"
ADMIN_EMAIL="admin@yourdomain.tld"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Хук развертывания продления сертификата запущен" >> "$LOG"
# Копирование продленных сертификатов
cp -L "${CERT_SRC}/fullchain.pem" "${CERT_DEST}/" && \
cp -L "${CERT_SRC}/privkey.pem" "${CERT_DEST}/" && \
cp -L "${CERT_SRC}/cert.pem" "${CERT_DEST}/" && \
cp -L "${CERT_SRC}/chain.pem" "${CERT_DEST}/" || {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ОШИБКА: копирование сертификата не удалось" >> "$LOG"
echo "Копирование сертификата mail-receiver не удалось" | \
mail -s "[ОШИБКА] Продление сертификата" "$ADMIN_EMAIL"
exit 1
}
chmod 600 "${CERT_DEST}/privkey.pem"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Сертификаты скопированы, перезапуск контейнера..." >> "$LOG"
cd /opt/mail-receiver && docker compose restart mail-receiver >> "$LOG" 2>&1 || {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ОШИБКА: перезапуск контейнера не удался" >> "$LOG"
echo "Перезапуск mail-receiver не удался после продления сертификата" | \
mail -s "[ОШИБКА] Перезапуск продления сертификата" "$ADMIN_EMAIL"
exit 1
}
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Хук развертывания успешно завершен" >> "$LOG"
Шаг 16 — Настройте ежеквартальный скрипт обновления
Создайте скрипт обновления непосредственно на сервере:
nano /usr/local/bin/update-mail-receiver.sh
chmod +x /usr/local/bin/update-mail-receiver.sh
Вставьте следующее, заменив административную электронную почту на свою:
#!/bin/bash
# =============================================================================
# update-mail-receiver.sh
# Загружает последний образ discourse/mail-receiver и перезапускает контейнер
# Предназначен для запуска ежеквартально через cron
# =============================================================================
# --- Конфигурация -----------------------------------------------------------
COMPOSE_DIR="/opt/mail-receiver"
COMPOSE_FILE="${COMPOSE_DIR}/docker-compose.yml"
ADMIN_EMAIL="admin@yourdomain.tld" # <-- Измените на вашу административную почту
LOG_FILE="/var/log/mail-receiver-update.log"
IMAGE="discourse/mail-receiver:release"
# --- Вспомогательные функции -------------------------------------------------
SCRIPT_START=$(date '+%Y-%m-%d %H:%M:%S')
ERRORS=()
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log_error() {
log "ОШИБКА: $1"
ERRORS+=("$1")
}
send_notification() {
local subject="$1"
local body="$2"
if command -v mail &>/dev/null; then
echo -e "$body" | mail -s "$subject" "$ADMIN_EMAIL"
log "Уведомление отправлено на ${ADMIN_EMAIL}"
else
log "ВНИМАНИЕ: команда mail недоступна — установите mailutils для включения уведомлений"
fi
}
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "Скрипт должен запускаться от root"
exit 1
fi
}
check_dependencies() {
log "--- Проверка зависимостей ---"
for cmd in docker ufw; do
if ! command -v "$cmd" &>/dev/null; then
log_error "Требуемая команда не найдена: ${cmd}"
return 1
fi
done
if [ ! -f "$COMPOSE_FILE" ]; then
log_error "docker-compose.yml не найден в ${COMPOSE_FILE}"
return 1
fi
log "Все зависимости найдены"
}
# --- Функции шагов ----------------------------------------------------------
step_get_current_image_id() {
log "--- Получение текущего идентификатора образа ---"
CURRENT_IMAGE_ID=$(docker inspect --format='{{.Id}}' "$IMAGE" 2>/dev/null || echo "none")
log "Текущий идентификатор образа: ${CURRENT_IMAGE_ID}"
}
step_pull_latest_image() {
log "--- Загрузка последнего образа: ${IMAGE} ---"
if docker pull "$IMAGE" >> "$LOG_FILE" 2>&1; then
log "Загрузка образа завершена"
else
log_error "Не удалось загрузить последний образ"
return 1
fi
}
step_check_if_updated() {
log "--- Проверка, обновлен ли образ ---"
NEW_IMAGE_ID=$(docker inspect --format='{{.Id}}' "$IMAGE" 2>/dev/null || echo "none")
log "Новый идентификатор образа: ${NEW_IMAGE_ID}"
if [ "$CURRENT_IMAGE_ID" = "$NEW_IMAGE_ID" ]; then
log "Образ уже актуален — перезапуск не требуется"
IMAGE_UPDATED=false
else
log "Обнаружен новый образ — контейнер будет перезапущен"
IMAGE_UPDATED=true
fi
}
step_restart_container() {
if [ "$IMAGE_UPDATED" = false ]; then
log "--- Пропуск перезапуска (образ не изменен) ---"
return 0
fi
log "--- Перезапуск контейнера с новым образом ---"
cd "$COMPOSE_DIR" || { log_error "Невозможно перейти в ${COMPOSE_DIR}"; return 1; }
log "Остановка текущего контейнера..."
if docker compose down >> "$LOG_FILE" 2>&1; then
log "Контейнер остановлен"
else
log_error "Не удалось остановить контейнер"
return 1
fi
log "Запуск контейнера с новым образом..."
if docker compose up -d >> "$LOG_FILE" 2>&1; then
log "Контейнер успешно запущен"
else
log_error "Не удалось запустить контейнер"
return 1
fi
# Краткая пауза для инициализации postfix
sleep 5
# Убедитесь, что контейнер действительно работает
if docker compose ps | grep -q "running\|Up"; then
log "Контейнер подтвержден как работающий"
else
log_error "Контейнер, похоже, не работает после перезапуска"
return 1
fi
}
step_cleanup_old_images() {
log "--- Очистка неиспользуемых образов Docker ---"
if docker image prune -f >> "$LOG_FILE" 2>&1; then
log "Неиспользуемые образы очищены"
else
log "ВНИМАНИЕ: Очистка образов не удалась — не критично, продолжаем"
fi
}
step_verify_port_25() {
log "--- Проверка прослушивания порта 25 ---"
sleep 3
if ss -tlnp | grep -q ':25'; then
log "Порт 25 прослушивается — Postfix запущен"
else
log_error "Порт 25 НЕ прослушивается — Postfix, возможно, не запустился"
log "Выполните: docker compose -f ${COMPOSE_FILE} logs --tail=50"
return 1
fi
}
# --- Главная --------------------------------------------------------------------
main() {
log "============================================================"
log " Обновление mail-receiver начато: ${SCRIPT_START}"
log " Образ: ${IMAGE}"
log "============================================================"
check_root
check_dependencies || { send_notification "[ОШИБКА] Обновление mail-receiver не удалось" \
"Проверка зависимостей не удалась.\n\nПроверьте журнал: ${LOG_FILE}"; exit 1; }
step_get_current_image_id
step_pull_latest_image || { send_notification "[ОШИБКА] Обновление mail-receiver не удалось" \
"Загрузка образа не удалась.\n\nПроверьте журнал: ${LOG_FILE}"; exit 1; }
step_check_if_updated
step_restart_container || { send_notification "[ОШИБКА] Обновление mail-receiver не удалось" \
"Перезапуск контейнера не удался.\n\nПроверьте журнал: ${LOG_FILE}"; exit 1; }
step_cleanup_old_images
step_verify_port_25 || { send_notification "[ОШИБКА] Обновление mail-receiver не удалось" \
"Порт 25 не прослушивается после обновления.\n\nПроверьте журнал: ${LOG_FILE}"; exit 1; }
# Итоговое резюме
log "============================================================"
if [ "$IMAGE_UPDATED" = true ]; then
log " ОБНОВЛЕНИЕ ЗАВЕРШЕНО УСПЕШНО"
log " Старый образ: ${CURRENT_IMAGE_ID:0:20}..."
log " Новый образ: ${NEW_IMAGE_ID:0:20}..."
send_notification "[УСПЕХ] mail-receiver обновлен" \
"mail-receiver успешно обновлен до нового образа ${SCRIPT_START}.\n\nПроверьте журнал: ${LOG_FILE}"
else
log " ПРОВЕРКА ЗАВЕРШЕНА — ОБРАЗ УЖЕ АКТУАЛЕН"
fi
log "============================================================"
}
main "$@"
Протестируйте его вручную, прежде чем полагаться на cron:
/usr/local/bin/update-mail-receiver.sh
# Следите за журналом
tail -f /var/log/mail-receiver-update.log
Затем добавьте задание cron для запуска 1 января, апреля, июля и октября в 2 часа ночи:
crontab -e
0 2 1 1,4,7,10 * /usr/local/bin/update-mail-receiver.sh
Скрипт отправит вам электронное письмо и перезапустит контейнер только в том случае, если действительно доступна более новая версия образа. Если образ уже обновлен, скрипт завершит работу без вывода сообщений, не прерывая доставку почты.
Справочник полезных команд
# Просмотр журналов в реальном времени
docker compose -f /opt/mail-receiver/docker-compose.yml logs -f
# Перезапуск контейнера
docker compose -f /opt/mail-receiver/docker-compose.yml restart
# Остановка и запуск
docker compose -f /opt/mail-receiver/docker-compose.yml stop
docker compose -f /opt/mail-receiver/docker-compose.yml up -d
# Ручное обновление до последней версии образа
docker compose -f /opt/mail-receiver/docker-compose.yml pull
docker compose -f /opt/mail-receiver/docker-compose.yml up -d
# Вход в контейнер для отладки
docker exec -it mail-receiver-mail-receiver-1 bash
# Проверка активности таймера certbot
systemctl status certbot.timer
# Просмотр журнала обновления сертификатов
tail -f /var/log/mail-cert-renewal.log
# Просмотр журнала обновлений
tail -f /var/log/mail-receiver-update.log