149 lines
7.3 KiB
Markdown
149 lines
7.3 KiB
Markdown
# Mail
|
||
|
||
Веб-почта + админка почтового сервера для внутренней платформы. Встраивается
|
||
через iframe в основной сайт. Аналог Roundcube/Nextcloud-Mail с управлением
|
||
сервером.
|
||
|
||
## Возможности
|
||
|
||
- **Почта**: системные и пользовательские IMAP-папки, треды по теме,
|
||
фильтры (Все/Непрочитанные/С флагом/Важные/С вложениями), пометки звезды/важности,
|
||
поиск через Meilisearch, drag-drop чипов между To/Cc/Bcc, paste-файлов из буфера.
|
||
- **Compose**: чипы получателей с автодополнением (контакты + DMS-mailboxes),
|
||
Markdown с тулбаром и preview, подписи, вложения (drag-drop + buffer),
|
||
отложенная отправка через Redis (переживает закрытие вкладки),
|
||
свернуть-в-вкладку, автосейв черновиков в IMAP Drafts.
|
||
- **Realtime**: IMAP IDLE → SSE → новые письма без перезагрузки + автоматическая
|
||
индексация в Meilisearch.
|
||
- **Админка** (`/admin/`): дашборд, ящики, алиасы, домены с DNS-визардом
|
||
(3 шага: записи → проверка через `dnspython` → DKIM + первый ящик),
|
||
редактируемые настройки приложения, аудит-лог, активные сессии,
|
||
карточка ELK с одной кнопкой «Применить».
|
||
- **Аутентификация**: логин по IMAP-паролю, server-side сессии в Postgres
|
||
(Fernet-шифрованный пароль), rate-limit 5/60с, fail2ban-релакс на DMS.
|
||
- **Авто-очистка**: корзина чистится в фоне раз в 6 ч (Dramatiq), retention настраивается.
|
||
- **ELK-shipping**: rsyslog-конфиг отправляет логи Flask/Dramatiq/Postfix/Dovecot
|
||
на удалённый Logstash. Альтернатива через Filebeat в `deploy/elk/`.
|
||
- **Бэкапы**: ежедневный cron (postgres dump + redis snapshot + maildir tar.gz).
|
||
- **Мониторинг**: `/api/health` с глубокими проверками (DB/Redis/IMAP/Meili/queues/postqueue).
|
||
|
||
## Стек
|
||
|
||
- **Python 3.13 + Flask 3** (Jinja SSR), gunicorn + gevent
|
||
- **SQLAlchemy 2 + Alembic** — `signatures`, `groups`, `shared_mailboxes`,
|
||
`autoreply`, `rules`, `user_sessions`, `audit_log`
|
||
- **Dramatiq** (Redis broker) — отложенная отправка, очистка корзины,
|
||
realtime-индексация
|
||
- **docker-mailserver v14** — Postfix + Dovecot + Rspamd
|
||
- **Postgres 16**, **Redis 7**, **Meilisearch 1.x**
|
||
|
||
## Быстрая установка (production)
|
||
|
||
Свежий Debian/Ubuntu, всё готово через единый installer:
|
||
|
||
```bash
|
||
git clone <repo-url> /home/deeily/mail
|
||
cd /home/deeily/mail
|
||
sudo bash deploy/install.sh
|
||
```
|
||
|
||
Что делает установщик:
|
||
|
||
1. Ставит docker / python3-venv / build-essential (если нет)
|
||
2. Создаёт venv, ставит `requirements.txt`
|
||
3. Копирует `.env.example → .env` + генерирует `FLASK_SECRET_KEY`
|
||
4. Поднимает docker-compose (postgres, redis, meili, mailserver)
|
||
5. Применяет fail2ban-override для DMS (Docker bridge в whitelist)
|
||
6. Применяет `alembic upgrade head`
|
||
7. Ставит systemd-юниты `mail-flask` + `mail-dramatiq` и стартует их
|
||
8. Прописывает cron для ежедневного бэкапа в 03:30
|
||
9. Ставит sudoers-entry для UI-кнопки «Применить ELK»
|
||
|
||
После установки заполнить в `/home/deeily/mail/.env`:
|
||
- `ADMINS=admin@your-domain` — кто видит /admin/*
|
||
- `IMAP_PASSWORD=…` — пароль системного IMAP-юзера
|
||
- `MAIL_SERVER_HOSTNAME=smtp.your-domain`
|
||
|
||
И перезапустить: `sudo systemctl restart mail-flask`.
|
||
|
||
## Разработка (локально)
|
||
|
||
```bash
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install -r requirements.txt
|
||
cp .env.example .env
|
||
# выставить USE_MOCK_MAIL=1 для офлайн-режима
|
||
|
||
cd docker && docker compose up -d postgres redis meili
|
||
cd ..
|
||
.venv/bin/alembic upgrade head
|
||
.venv/bin/python wsgi.py
|
||
```
|
||
|
||
Открыть http://localhost:5000/, логин — `admin@mail.local` / `admin123`.
|
||
|
||
## Структура
|
||
|
||
```
|
||
app/
|
||
__init__.py # Flask factory + before_request middleware
|
||
config.py # все env-параметры
|
||
db.py # SQLAlchemy engine + scoped_session
|
||
models.py # ORM
|
||
tasks.py # Dramatiq actors (send_deferred, cleanup_trash, index_new_messages)
|
||
blueprints/
|
||
mail/ # /f/<folder>?uid=… — клиент
|
||
admin/ # /admin/ + /admin/audit + /admin/domains/new (визард)
|
||
auth/ # /auth/login + /auth/logout
|
||
api/ # /api/health, /api/search, /api/contacts, /api/mail/*
|
||
settings/ # /settings/sessions, /settings/signatures, /settings/autoreply
|
||
groups/, rules/, shared/
|
||
services/
|
||
imap_client.py # IMAPClient + per-user RLock + кэши
|
||
imap_idle.py # IDLE-thread per (user,folder), SSE-фанаут, хук в Meili
|
||
smtp_sender.py # smtplib порт 587
|
||
mail_service.py # фасад для blueprints (mock ↔ real)
|
||
store.py # CRUD над Postgres-моделями
|
||
sessions.py # server-side sessions + Fernet + audit_log
|
||
sieve_builder.py # rules → managesieve.sieve, deploy через docker exec
|
||
search.py # Meilisearch wrapper
|
||
dms_config.py # обёртка над `setup` CLI DMS + verify_dns (dnspython)
|
||
templates/ # Jinja
|
||
static/ # styles.css + app.js
|
||
docker/
|
||
docker-compose.yml # postgres, redis, meili, mailserver
|
||
dms-config/ # bind-mount в DMS — postfix-main.cf, fail2ban-jail.cf, …
|
||
deploy/
|
||
install.sh # единый установщик
|
||
backup.sh # ежедневный бэкап
|
||
systemd/ # mail-flask.service + mail-dramatiq.service + install.sh
|
||
rsyslog/ # 49-mail-stack.conf + install.sh (ELK shipping)
|
||
elk/ # альтернатива через Filebeat (если rsyslog не подходит)
|
||
migrations/ # Alembic
|
||
gunicorn.conf.py # gevent, 1 worker, 2000 connections
|
||
wsgi.py # gevent monkey-patch + create_app()
|
||
```
|
||
|
||
## Эксплуатация
|
||
|
||
```bash
|
||
# Статус
|
||
systemctl status mail-flask mail-dramatiq
|
||
journalctl -u mail-flask -f
|
||
curl localhost:5000/api/health | jq
|
||
|
||
# Перезагрузка после правки .env
|
||
sudo systemctl restart mail-flask mail-dramatiq
|
||
|
||
# Бэкап вручную
|
||
/home/deeily/mail/deploy/backup.sh
|
||
|
||
# Подключить ELK
|
||
# через UI: /admin/ → карточка «ELK · отправка логов» → ELK_HOST/PORT/PROTO + «Применить»
|
||
# или вручную: sudo bash deploy/rsyslog/install.sh logstash.internal 5140 tcp
|
||
```
|
||
|
||
## Лицензия
|
||
|
||
Internal — не для публичного использования.
|