Безопасность, SMTP, очередь отправки, DKIM, деплой на Jambotron (Jambotron v0.2.3 75.1–75.3)
- SMTP-сервер переписан: разбор по строкам, AUTH PLAIN/LOGIN, нет открытого релея, MAIL FROM только свой после входа, 250 только после сохранения (иначе 451), лимит размера - отправка наружу: очередь outbound_queue (V2) с повторами ~22 ч и уведомлением о недоставке; Jakarta Mail, режимы off / mx / smarthost, байты письма не меняются (DKIM) - DKIM: подпись utils-mail-dkim, проверка Apache jDKIM; ключ PKCS#8, старый PKCS#1 читается - права: письма, папки, вложения — только владельцу (было: любой по номеру); имена вложений без ../ - секреты без значений по умолчанию (DB_PASSWORD, JWT_SECRET), регистрация закрыта, admin123 и /api/simulate/receive — только профиль dev - веб-клиент внутри jar (относительный /api), Dockerfile в 3 этапа, не root, health; docker-compose.jambotron.yml: секреты создаются при первом запуске, наружу только 8080 - gradle-wrapper.jar был в .gitignore — сборка не запускалась - тесты: 26 (SMTP реальным клиентом и сокетом, GreenMail, DKIM подпись+проверка, очередь) α Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,440 +1,85 @@
|
||||
# Mail Server
|
||||
# Reactive Mail Server
|
||||
|
||||
Прототип почтового сервера на **Spring Boot + Angular** с REST API для управления письмами.
|
||||
Почтовый сервер на **Spring Boot WebFlux + R2DBC (PostgreSQL) + Angular**: свой SMTP-сервер на Reactor Netty, веб-почта, очередь отправки, DKIM/SPF/DMARC.
|
||||
|
||||
## Архитектура
|
||||
Назначение — наши личные цели (v0.2.3, раздел 75 чек-листа Jambotron): тестовый проект на Jambotron, ловушка почты для dev/staging. Для писем пользователям Jambotron (регистрация, сброс пароля) остаётся Resend.
|
||||
|
||||
## Как устроено
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Angular Frontend │
|
||||
│ (веб-почта: список, чтение, написание, контакты) │
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│ REST API (JSON)
|
||||
┌──────────────────────▼──────────────────────────────┐
|
||||
│ Spring Boot Backend │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
|
||||
│ │ Users │ │ Messages │ │ Contacts │ │
|
||||
│ └────────────┘ └────────────┘ └──────────────┘ │
|
||||
│ ┌────────────┐ ┌────────────┐ │
|
||||
│ │ Folders │ │Attachments │ │
|
||||
│ └────────────┘ └────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼────────┐ │
|
||||
│ │ JavaMailSender │ │
|
||||
│ │ (SMTP отправка)│ │
|
||||
│ └─────────────────┘ │
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│ JPA
|
||||
┌──────────────────────▼──────────────────────────────┐
|
||||
│ PostgreSQL 17 │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
SMTP-клиент / чужой сервер ──► SmtpServer (Netty, порт 3025) ──┐
|
||||
├─► MailRouter ─┬─► LocalDelivery → свой ящик (PostgreSQL)
|
||||
Веб-почта (Angular) ──► REST /api ──► SubmissionService ──────┘ └─► OutboundQueue → DeliveryWorker → SMTP получателя
|
||||
```
|
||||
|
||||
## Запуск
|
||||
- **Приём (SMTP).** Команды разбираются по строкам (`LineBasedFrameDecoder`): работает и пачка команд в одном пакете, и строка, разорванная на два пакета. Письмо сохраняется или ставится в очередь **до** ответа `250`; если сохранить не удалось — `451`, и отправитель повторит. Работа с базой и DNS идёт не в сетевом потоке.
|
||||
- **Не открытый релей.**
|
||||
- Письмо на свой домен принимается от кого угодно.
|
||||
- Письмо на чужой домен — только после `AUTH`, иначе `550 5.7.1 Relaying denied`.
|
||||
- Вошедший пользователь пишет только от своего адреса, иначе `553`.
|
||||
- **Маршрут** (`MailRouter`) один для SMTP и веб-почты:
|
||||
- наш домен → наш ящик, а если ящика нет — «нет такого пользователя»;
|
||||
- задан `MAIL_CATCH_ALL` → всё в этот ящик;
|
||||
- чужой домен → очередь.
|
||||
- **Отправка наружу** (`OUTBOUND_MODE`):
|
||||
- `off` — ничего не уходит;
|
||||
- `mx` — напрямую на MX получателя, порт 25, STARTTLS по возможности;
|
||||
- `smarthost` — через провайдера (Resend, SES) с логином и проверкой TLS.
|
||||
- **Очередь** — таблица `outbound_queue`. Временная ошибка — повтор через 1 мин, 5 мин, 15 мин, 1 ч, 3 ч, 6 ч, 12 ч. Постоянная ошибка или исчерпанные повторы — уведомление отправителю во входящие.
|
||||
- **DKIM.** Подпись — `utils-mail-dkim` (rsa-sha256, relaxed/relaxed), проверка входящих — Apache James jDKIM по ключу из DNS отправителя. Ключ создаётся один раз в `DKIM_KEYS_DIR`, запись для DNS печатается в лог при старте: `TXT <selector>._domainkey.<domain>`.
|
||||
- **Права.** Письма, папки и вложения отдаются только владельцу (чужие — `404`). Регистрация по умолчанию закрыта, ящики — только в своих доменах.
|
||||
|
||||
### 1. Docker Compose (рекомендуется)
|
||||
## Настройки (переменные окружения)
|
||||
|
||||
Секреты без значений по умолчанию — без них сервер не стартует. Их можно передать переменными или файлами в `MAILSERVER_SECRETS_DIR` (имя файла = имя переменной).
|
||||
|
||||
| Переменная | Что | По умолчанию |
|
||||
|---|---|---|
|
||||
| `DB_PASSWORD` | пароль PostgreSQL | **обязательно** |
|
||||
| `JWT_SECRET` | ключ токенов входа, ≥ 32 байт | **обязательно** |
|
||||
| `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER` | база | `localhost`, `5432`, `mailserver`, `mail` |
|
||||
| `MAIL_DOMAINS` | наши домены, через запятую | `localhost` |
|
||||
| `MAIL_HOSTNAME` | имя сервера в SMTP | `localhost` |
|
||||
| `MAIL_ADMIN_EMAIL`, `MAIL_ADMIN_PASSWORD` | первый ящик на пустой базе; без пароля — случайный, один раз в логе | `admin@<первый домен>` |
|
||||
| `MAIL_REGISTRATION` | открыть регистрацию | `false` |
|
||||
| `MAIL_CATCH_ALL` | ящик-ловушка: вся почта сюда, наружу ничего | — |
|
||||
| `SMTP_PORT` | порт SMTP | `3025` |
|
||||
| `SMTP_AUTH_WITHOUT_TLS` | разрешить `AUTH` без шифрования — **только** в закрытой сети (docker, ВМ) | `false` |
|
||||
| `OUTBOUND_MODE` | `off` / `mx` / `smarthost` | `off` |
|
||||
| `OUTBOUND_HOST`, `OUTBOUND_PORT`, `OUTBOUND_USERNAME`, `OUTBOUND_PASSWORD`, `OUTBOUND_TLS` | smarthost (`starttls` / `ssl` / `none`) | —, `587`, —, —, `starttls` |
|
||||
| `DKIM_DOMAIN`, `DKIM_SELECTOR`, `DKIM_KEYS_DIR` | DKIM | `localhost`, `mail`, `./dkim-keys` |
|
||||
| `SPF_ENABLED`, `DMARC_ENABLED` | проверки входящих | `true` |
|
||||
| `MAIL_CORS_ORIGINS` | другие источники для веб-клиента (только разработка) | — |
|
||||
|
||||
## Разработка
|
||||
|
||||
```bash
|
||||
cd mail-server
|
||||
docker compose up --build
|
||||
docker compose up -d # PostgreSQL на 127.0.0.1:5433
|
||||
./gradlew :backend:bootRun --args='--spring.profiles.active=dev' # API :8080, SMTP :3025, admin@mailserver.local / admin123
|
||||
cd frontend && npm install && npx ng serve # http://localhost:4200, /api → :8080
|
||||
./gradlew :backend:test # тесты (база не нужна)
|
||||
```
|
||||
|
||||
Сервисы:
|
||||
- **REST API**: http://localhost:8080
|
||||
- **SMTP (приём)**: localhost:3025
|
||||
- **IMAP (доступ к письмам)**: localhost:143
|
||||
- **Mailpit UI**: http://localhost:8025 (SMTP на порту 1025)
|
||||
- **PostgreSQL**: localhost:5432
|
||||
Профиль `dev` — только для своего компьютера: известные пароли, открытая регистрация, `POST /api/simulate/receive` (письмо в ящик без SMTP).
|
||||
|
||||
### 2. Локально
|
||||
## Деплой на Jambotron (тест)
|
||||
|
||||
Требования:
|
||||
- Java 21
|
||||
- PostgreSQL 17
|
||||
В мастере деплоя выбрать `docker-compose.jambotron.yml`, домен дать сервису `mail`, порт `8080`.
|
||||
|
||||
```bash
|
||||
# Запустите PostgreSQL
|
||||
docker run -d --name mail-db -p 5432:5432 \
|
||||
-e POSTGRES_DB=mailserver \
|
||||
-e POSTGRES_USER=mail \
|
||||
-e POSTGRES_PASSWORD=secret \
|
||||
postgres:17
|
||||
- Пароль базы и ключ токенов создаёт контейнер `secrets-init` при первом запуске (том `secrets`) — вводить ничего не нужно, в git ничего нет.
|
||||
- Первый ящик — `admin@mail.test`, пароль — в логах сервиса `mail` при первом запуске.
|
||||
- Наружу открыты только веб-почта и API. SMTP (3025) — внутри сети ВМ, наружу ничего не отправляется (`OUTBOUND_MODE=off`).
|
||||
- Домен ящиков — `MAIL_DOMAINS` (по умолчанию `mail.test`).
|
||||
|
||||
# Запустите Mailpit для тестовой отправки
|
||||
docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit
|
||||
Образ собирается из `backend/Dockerfile` в три этапа: Angular → Spring Boot (веб-клиент внутри jar) → JRE не от root.
|
||||
|
||||
# Соберите и запустите бэкенд
|
||||
cd backend
|
||||
./gradlew bootRun
|
||||
```
|
||||
## Не сделано
|
||||
|
||||
## API
|
||||
|
||||
### Тестовый пользователь
|
||||
|
||||
При первом запуске приложения автоматически создаётся тестовый пользователь:
|
||||
|
||||
- **Email:** `admin@mailserver.local`
|
||||
- **Пароль:** `admin123`
|
||||
|
||||
### Регистрация
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"secret123","displayName":"Test User"}'
|
||||
```
|
||||
|
||||
### Логин
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"secret123"}'
|
||||
```
|
||||
|
||||
Ответ:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGci...",
|
||||
"email": "test@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
Все защищённые эндпоинты требуют заголовок `Authorization: Bearer <token>`.
|
||||
|
||||
### Получить папки
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/api/folders \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Получить письма из папки
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8080/api/messages?folderId=1&page=0&size=20" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Отправить письмо
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/send \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-d '{"to":"recipient@example.com","subject":"Hello","body":"Test message"}'
|
||||
```
|
||||
|
||||
### Симулировать входящее письмо (для тестов)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/simulate/receive \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"senderEmail":"sender@example.com",
|
||||
"senderName":"Sender",
|
||||
"recipientEmail":"test@example.com",
|
||||
"subject":"Incoming test",
|
||||
"body":"This is a test message"
|
||||
}'
|
||||
```
|
||||
|
||||
### Отметить как прочитанное
|
||||
|
||||
```bash
|
||||
curl -X PUT http://localhost:8080/api/messages/1/read \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Удалить письмо
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8080/api/messages/1 \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Загрузить вложение к письму
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/messages/1/attachments \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-F "file=@/path/to/file.pdf"
|
||||
```
|
||||
|
||||
### Получить список вложений письма
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/api/messages/1/attachments \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Скачать вложение
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/api/attachments/1/download \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-o file.pdf
|
||||
```
|
||||
|
||||
### Удалить вложение
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8080/api/attachments/1 \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Поиск по письмам
|
||||
|
||||
Поиск поддерживает два режима: простой поиск по ключевому слову и расширенный поиск с отдельными фильтрами.
|
||||
|
||||
**Простой поиск** — ищет по ключевому слову в теме, теле, отправителе и получателе:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8080/api/messages/search?folderId=1&keyword=hello" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
**Расширенный поиск** — используйте отдельные параметры для точной фильтрации:
|
||||
|
||||
```bash
|
||||
# Поиск по теме
|
||||
curl "http://localhost:8080/api/messages/search?folderId=1&subject=report" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
# Поиск по тексту письма
|
||||
curl "http://localhost:8080/api/messages/search?folderId=1&body=invoice" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
# Поиск по отправителю
|
||||
curl "http://localhost:8080/api/messages/search?folderId=1&sender=company.com" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
# Комбинированный поиск (все фильтры применяются одновременно)
|
||||
curl "http://localhost:8080/api/messages/search?folderId=1&subject=report&sender=company.com" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
Все параметры поиска (`keyword`, `subject`, `body`, `sender`, `recipient`) необязательны. Если ни один параметр не указан, возвращаются все письма папки.
|
||||
|
||||
### Отправить письмо через SMTP-сервер
|
||||
|
||||
Встроенный SMTP-сервер слушает порт **3025**. Любое письмо, отправленное на этот порт, будет сохранено в базе данных, если получатель зарегистрирован в системе.
|
||||
|
||||
```bash
|
||||
# Отправить письмо через telnet
|
||||
telnet localhost 3025
|
||||
```
|
||||
|
||||
Или с помощью curl:
|
||||
|
||||
```bash
|
||||
curl --url 'smtp://localhost:3025' \
|
||||
--mail-from 'sender@example.com' \
|
||||
--mail-rcpt 'test@example.com' \
|
||||
-T <(echo -e 'From: sender@example.com\nTo: test@example.com\nSubject: SMTP Test\n\nHello from SMTP!')
|
||||
```
|
||||
|
||||
Письмо появится во входящих (INBOX) пользователя `test@example.com`.
|
||||
|
||||
### Управление чёрным списком
|
||||
|
||||
Добавьте отправителя в чёрный список — письма от него будут автоматически помечаться как спам.
|
||||
|
||||
**Получить список заблокированных отправителей:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/api/spam/blacklist \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
**Добавить отправителя в чёрный список:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/spam/blacklist \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-d '{"senderEmail":"spammer@example.com","reason":"Спам"}'
|
||||
```
|
||||
|
||||
**Удалить отправителя из чёрного списка:**
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8080/api/spam/blacklist?senderEmail=spammer@example.com" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
Письма от заблокированных отправителей автоматически получают флаг `spam: true` и могут быть отфильтрованы на стороне клиента.
|
||||
|
||||
### Подключение через IMAP-сервер
|
||||
|
||||
Встроенный IMAP-сервер слушает порт **143**. Вы можете подключиться к нему из любого почтового клиента (Thunderbird, Outlook, Apple Mail) или через telnet для тестирования.
|
||||
|
||||
```bash
|
||||
# Подключение через telnet
|
||||
telnet localhost 143
|
||||
```
|
||||
|
||||
Пример сессии IMAP:
|
||||
|
||||
```
|
||||
a LOGIN test@example.com secret123
|
||||
a OK LOGIN completed
|
||||
|
||||
a LIST "" "*"
|
||||
* LIST (\HasNoChildren) "." "INBOX"
|
||||
* LIST (\HasNoChildren) "." "Sent"
|
||||
a OK LIST completed
|
||||
|
||||
a SELECT "INBOX"
|
||||
* 5 EXISTS
|
||||
* 0 RECENT
|
||||
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
|
||||
a OK [READ-WRITE] SELECT completed
|
||||
|
||||
a FETCH 1 (BODY[])
|
||||
* 1 FETCH (UID 1 FLAGS (\Seen) BODY[] {123
|
||||
Hello, this is a test message.
|
||||
))
|
||||
a OK FETCH completed
|
||||
|
||||
a SEARCH ALL
|
||||
* SEARCH 1 2 3 4 5
|
||||
a OK SEARCH completed
|
||||
|
||||
a LOGOUT
|
||||
* BYE IMAP4rev1 server terminating
|
||||
a OK LOGOUT completed
|
||||
```
|
||||
|
||||
Поддерживаемые IMAP-команды:
|
||||
- **LOGIN** — аутентификация по email и паролю
|
||||
- **SELECT / EXAMINE** — выбор папки для работы
|
||||
- **FETCH** — получение заголовков и тела писем
|
||||
- **SEARCH** — поиск по письмам
|
||||
- **STORE** — изменение флагов (прочитано, помечено, удалено)
|
||||
- **EXPUNGE** — удаление помеченных писем
|
||||
- **LIST / LSUB** — список папок
|
||||
- **STATUS** — информация о папке (количество писем, непрочитанных)
|
||||
- **CAPABILITY** — список поддерживаемых возможностей
|
||||
- **LOGOUT** — выход из сессии
|
||||
|
||||
Порт IMAP настраивается через `mailserver.imap.port` в `application.properties` (по умолчанию 143).
|
||||
|
||||
### DKIM / SPF / DMARC — защита от подделки писем
|
||||
|
||||
Почтовый сервер поддерживает три стандарта защиты электронной почты:
|
||||
|
||||
**DKIM (DomainKeys Identified Mail)** — криптографическая подпись писем.
|
||||
- При отправке письма автоматически подписываются RSA-ключом (2048 бит)
|
||||
- При получении входящих писем проверяется DKIM-подпись отправителя
|
||||
- Ключи хранятся в директории `./dkim-keys/` (генерируются автоматически при первом запуске)
|
||||
- Публичный ключ публикуется в DNS TXT-записи: `_default._domainkey.<domain>`
|
||||
|
||||
**SPF (Sender Policy Framework)** — проверка разрешённых серверов отправителя.
|
||||
- При получении письма проверяется SPF DNS-запись домена отправителя
|
||||
- Поддерживаемые механизмы: `all`, `ip4`, `ip6`, `mx`, `a`, `include`
|
||||
- Локальные адреса (127.0.0.1, 192.168.x.x) пропускаются без проверки
|
||||
|
||||
**DMARC (Domain-based Message Authentication, Reporting & Conformance)** — политика обработки.
|
||||
- Объединяет результаты SPF и DKIM с проверкой alignment (совпадение доменов)
|
||||
- Поддерживаемые политики: `none` (мониторинг), `quarantine` (спам), `reject` (отклонить)
|
||||
- Если SPF и DKIM не пройдены, применяется политика DMARC из DNS
|
||||
|
||||
**Результат проверки:**
|
||||
- Письма, не прошедшие SPF/DKIM/DMARC, автоматически помечаются флагом `spam: true`
|
||||
- Логируются результаты всех трёх проверок для каждого входящего письма
|
||||
|
||||
**Настройки в `application.properties`:**
|
||||
```
|
||||
mailserver.dkim.enabled=true
|
||||
mailserver.dkim.domain=localhost
|
||||
mailserver.dkim.keys-dir=./dkim-keys
|
||||
mailserver.spf.enabled=true
|
||||
mailserver.spf.local-bypass=true
|
||||
mailserver.dmarc.enabled=true
|
||||
mailserver.dmarc.local-bypass=true
|
||||
```
|
||||
|
||||
**Публикация DKIM-ключа в DNS:**
|
||||
При первом запуске сервер выводит в лог публичный ключ DKIM. Добавьте его как TXT-запись:
|
||||
```
|
||||
_name: _default._domainkey.yourdomain.com
|
||||
type: TXT
|
||||
value: v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQ...
|
||||
```
|
||||
|
||||
## Модели данных
|
||||
|
||||
| Сущность | Описание |
|
||||
|---|---|
|
||||
| **User** | Почтовый ящик, пароль, квота |
|
||||
| **Folder** | INBOX, Sent, Drafts, Trash, Spam |
|
||||
| **Message** | Заголовки, тело, флаги (read, flagged, spam) |
|
||||
| **BlacklistEntry** | Заблокированный отправитель, привязка к пользователю |
|
||||
| **Attachment** | Имя, MIME-тип, размер, путь к файлу |
|
||||
| **Contact** | Имя, email, телефон, компания |
|
||||
|
||||
## Деплой на VPS
|
||||
|
||||
### Caddy Docker Proxy
|
||||
|
||||
Проект использует **Caddy Docker Proxy** для автоматического HTTPS и reverse proxy. Caddy сам генерирует конфиг из `labels` в `docker-compose.prod.yml` — отдельный Caddyfile не нужен.
|
||||
|
||||
**Как это работает:**
|
||||
- Контейнер `mail-server` имеет `labels` с настройками Caddy
|
||||
- `lucaslorentz/caddy-docker-proxy` следит за Docker через socket и автоматически создаёт виртуальные хосты
|
||||
- HTTPS-сертификат от Let's Encrypt получается автоматически
|
||||
|
||||
**Ключевые labels:**
|
||||
|
||||
| Label | Описание |
|
||||
|-------|----------|
|
||||
| `caddy: mail.jambotron.com` | Домен, на котором доступен сервис |
|
||||
| `caddy.route.api.reverse_proxy` | Проксирует `/api/*` на Spring Boot (порт 8080) |
|
||||
| `caddy.route.static.root` | Корневая директория для Angular фронтенда |
|
||||
| `caddy.route.static.file_server` | Раздаёт статические файлы Angular |
|
||||
|
||||
**Запуск на VPS:**
|
||||
|
||||
```bash
|
||||
# Клонируйте репозиторий
|
||||
git clone https://gitea.jambotron.com/jambotron_gang/reactive-mail-server.git
|
||||
cd reactive-mail-server
|
||||
|
||||
# Соберите фронтенд
|
||||
cd frontend && npm install && npx ng build --configuration=production
|
||||
cd ..
|
||||
|
||||
# Запустите
|
||||
docker compose -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
**Перед запуском:**
|
||||
1. Замените `mail.jambotron.com` на ваш домен в `docker-compose.prod.yml`
|
||||
2. Настройте PTR (Reverse DNS) у хостера — IP → `mail.jambotron.com`
|
||||
3. Добавьте DNS-записи: A (`mail` → IP), MX (`@` → `mail.jambotron.com`), TXT (`v=spf1 ip4:<IP> -all`)
|
||||
4. Откройте порты: 80, 443, 25 (SMTP relay), 3025 (SMTP приём)
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Хэширование паролей (BCrypt)
|
||||
- [x] Аутентификация (JWT)
|
||||
- [x] Приём SMTP (встроенный сервер на WebFlux TcpServer)
|
||||
- [x] IMAP-сервер (встроенный, порт 143)
|
||||
- [x] Вложения (загрузка/скачивание)
|
||||
- [x] Поиск по письмам (по теме, телу, отправителю, получателю)
|
||||
- [x] Angular фронтенд (v22 + Tailwind CSS + Synthwave дизайн)
|
||||
- [x] Антиспам (чёрный список отправителей)
|
||||
- [x] DKIM/SPF/DMARC (подпись, верификация, DNS-проверка)
|
||||
- [x] SMTP релей (доставка на внешнюю почту)
|
||||
- [x] Контакты (адресная книга)
|
||||
- [x] Reply / Forward
|
||||
- [x] Пагинация писем
|
||||
- [x] HTML/Markdown режим
|
||||
- [x] Caddy Docker Proxy (автоматический HTTPS)
|
||||
- [ ] TLS шифрование SMTP/IMAP
|
||||
- [ ] TLS: STARTTLS для SMTP и IMAP. Пока `AUTH` — только в закрытой сети.
|
||||
- [ ] IMAP выключен: упрощённый, блокирует сетевой поток (`.block()`). Переписать и проверить с Thunderbird.
|
||||
- [ ] Вложения входящих писем не сохраняются (берётся только текст или HTML).
|
||||
- [ ] Письмо нескольким получателям: при ошибке на втором первый может получить его дважды (после повтора отправителя).
|
||||
- [ ] Своя настоящая почта: отдельная VPS, порт 25, PTR, отдельный домен (чек-лист Jambotron 75.6).
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
||||
Reference in New Issue
Block a user