mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
Session close 2026-09-15: gitverse source of truth, gitea mirror, token locations in AGENT.md
This commit is contained in:
@@ -21,6 +21,30 @@
|
||||
IdentityFile ~/.ssh/gitverse, IdentitiesOnly yes) — валиден для аутентификации,
|
||||
но push-to-create отключён; для создания репо — API.
|
||||
|
||||
## Токены (местоположение)
|
||||
|
||||
**Единый источник — `/opt/hermes/.hermes/secrets/git-tokens.env`** (chmod 600, единственный
|
||||
истинный файл токенов; НЕ коммитится ни в один репозиторий):
|
||||
|
||||
| Переменная | Назначение |
|
||||
|---|---|
|
||||
| `GITVERSE_LOGIN` | логин gitverse (`kpa39l`, НЕ estorozhenko) |
|
||||
| `GITVERSE_API` | база gitverse API (`https://api.gitverse.ru`) |
|
||||
| `GITVERSE_PAT` | PAT gitverse (создан 19 июл 2026, имя `hermes-agent`) |
|
||||
| `GITEA_API` | база gitea API (`http://127.0.0.1:3000/api/v1`) |
|
||||
| `GITEA_TOKEN` | токен gitea (читает/пишет репо, зеркала) |
|
||||
|
||||
Загрузка: `set -a && source /opt/hermes/.hermes/secrets/git-tokens.env && set +a`
|
||||
|
||||
Прочие токены проекта (НЕ в git-tokens.env):
|
||||
- **Telegram Bot (уведомления о важных письмах):** `/opt/vesti/.env` → `VESTI_BOT_TOKEN`
|
||||
(защищён Hermes-секретом — не читать/не обходить; использовать через `source` в рантайме).
|
||||
- **TELEGRAM_CHAT_ID** (ЛС kpa39l, `281328953`): `/opt/hermes/email-assistant/.env` (gitignored).
|
||||
- **SSH-ключ gitverse:** `~/.ssh/gitverse` (алиас `Host gitverse.ru` в `~/.ssh/config`).
|
||||
|
||||
⚠️ Токены НЕ хранить в коде/README/коммитах — только в secrets-файле и remote URL git.
|
||||
Если в репозитории случайно закоммичен токен — немедленно отозвать и перевыпустить.
|
||||
|
||||
## Как пушить
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Email Assistant — локальный архив и ассистент почты
|
||||
|
||||
**Дата:** 2026-09-13
|
||||
**Фаза:** 1.5–1.7 + Портфель веб-UI (планирование) + Классификация/обработчики (в работе)
|
||||
**Дата:** 2026-09-15
|
||||
**Фаза:** 1.5–1.7 + Портфель веб-UI (планирование) + Классификация/обработчики (в работе) + IMAP Realtime Sync (планирование)
|
||||
|
||||
**Стек:** Himalaya CLI → Python → SQLite → Ollama (Qwen3:8b) → Radicale (CalDAV) → Telegram
|
||||
|
||||
@@ -140,8 +140,22 @@ Hermes cron:
|
||||
- [x] **Фикс секретов (2026-09-14)**: скрипт теперь сам читает `radicale/.env` (RADICALE_PASS) и `/opt/vesti/.env` (VESTI_BOT_TOKEN) — раньше без ручного export был 401; добавлен stdlib-парсер .env (python-dotenv в системе нет)
|
||||
- [x] **Живой прогон (2026-09-14)**: письмо 2026/422 → VTODO «Задачи» (204) + VEVENT «Рабочий» (204); повтор — идемпотентно (0 дублей)
|
||||
- [x] **Cron (2026-09-14)**: `mail-classify-handlers` (6e1e78ceedfd, every 5m) — классификатор (--limit 10) → обработчики; end-to-end проверено: 5 новых «meeting» → 5 VEVENT (201)
|
||||
- [ ] Живое urgent-письмо → доставка в Telegram (механика готова, токен подхватывается; пока не было urgent-писем)
|
||||
- [x] **Telegram-секреты**: токен `VESTI_BOT_TOKEN` читается из /opt/vesti/.env; канал-дефолт `@dedinit_vesti` (TELEGRAM_CHAT_ID можно переопределить в .env проекта)
|
||||
- [x] **Живое urgent-письмо → доставка в Telegram (2026-09-15)**: письмо с id 3216 («RE: Платежи Аврора», Archive/2026/08/3216) помечено `classification: urgent` → `email_handlers.py` с `TELEGRAM_CHAT_ID=281328953` отправил в ЛС (private чат kpa39l) через бота @dedinit_controller_bot — `msg_id=60`, пользователь подтвердил получение; `handled_urgent: true` записан (идемпотентно, повторной отправки нет)
|
||||
- [x] **Telegram-секреты**: токен `VESTI_BOT_TOKEN` читается из /opt/vesti/.env; канал-дефолт `@dedinit_vesti` — **внимание**: для ЛС нужно явно `TELEGRAM_CHAT_ID=281328953` (private chat kpa39l); зафиксировать дефолт в ЛС — TODO при настройке stream-сервиса
|
||||
|
||||
---
|
||||
|
||||
## Сделано в сессии 2026-09-15 (IMAP realtime sync — планирование + Telegram-уведомления в ЛС)
|
||||
|
||||
- [x] **Выделена авторизация IMAP в отдельную функцию** — `scripts/imap_client.py` (новый): `imap_connect()` (socket+STARTTLS+LOGIN, re-try 5→60с, креды из config/himalaya-config.toml); `mail_archive.py` переведён на неё (sys.path-фикс для cron); проверено живьём: `OK: connected+LOGIN as e.storozhenko @ mail.corpoffice.tech`
|
||||
- [x] **Логирование авторизации + метрики** (в imap_client.py): JSON-лог `/opt/hermes/email/logs/imap_client.log` (conn_ok/conn_error/auth_ok/auth_failed/session_started/session_ended); `imap_metrics()` (auth_success_rate, conn_ok/err, sessions_active); контекст-менеджер `imap_session`; `--metrics` CLI
|
||||
- [x] **Telegram-уведомления о важных письмах — в ЛС** (kpa39l): создан `/opt/hermes/email-assistant/.env` с `TELEGRAM_CHAT_ID=281328953`; живой тест: письмо 3216 («RE: Платежи Аврора») помечено urgent → доставка в private chat 281328953 (bot @dedinit_controller_bot, msg_id=60), пользователь подтвердил
|
||||
- [x] **Найден и исправлен баг доставки**: крон-скрипт `mail-classify-handlers.sh` использовал системный `python3` без httpx → все Telegram-уведомления молча падали (накопилось 10 urgent-писем). Фикс: `PY=/opt/vesti/.venv/bin/python` + `--limit 2` (дозированная отправка backlog, анти-спам)
|
||||
- [x] **Формат даты в уведомлениях**: добавлены `format_date_for_tg()` (ISO → `02.09.2026 10:49`) и `_unquote_yaml()` (снятие YAML-кавычек в parse_email_md); строка `📅 ДД.ММ.ГГГГ ЧЧ:ММ` в handle_urgent
|
||||
- [x] **IMAP realtime sync (проект)**: proposal.md + design.md + tasks.md + spec-дельты (imap-realtime-sync, email-attachments, email-classification) — openspec `change 'imap-realtime-sync' is valid`; диагноз rate-limit (не TLS-fingerprinting); 2 микросервиса (imap_stream.py IDLE + change_analyzer.py ChangeLog SQLite), systemd, soft-delete
|
||||
- [x] **Backlog urgent-писем**: 10 писем в INBOX с `classification: urgent` без `handled_urgent` (июль 2026 — сентябрь 2026, включая срочное «переоформление договоров… отключение интернета до 20-го») — отправляются по 2 за крон-прогон в ЛС
|
||||
- [x] **Репозиторий переведён на gitverse (источник истины)**: создан приватный `kpa39l/email-assistant` на gitverse.ru (id 336792) через API; remote `gitverse` добавлен; master запушен; **gitea: pull mirror `estorozhenko/email-assistant` ← gitverse (12h, id 54)** — gitea.nixg.ru остаётся зеркалом, не источником
|
||||
- [x] **AGENT.md создан** — политика репозитория: gitverse = истина, gitea = зеркало, команды пуша, **местоположение токенов** (git-tokens.env, VESTI_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
||||
|
||||
### Задача 1: Веб-интерфейс ассистента ⬜
|
||||
- [ ] FastAPI + SQLite FTS5: список писем (дата/адресант/тэги/папка)
|
||||
@@ -300,10 +314,10 @@ dav.example.com {
|
||||
|
||||
## Конфигурация
|
||||
|
||||
- **Репозиторий:** `https://gitea.nixg.ru/hermes/email-assistant`
|
||||
- **Push mirror на gitverse.ru:** ❌ не настроен
|
||||
- [ ] Настроить push mirror из gitea.nixg.ru в gitverse.ru
|
||||
- Требуется: создать репозиторий на gitverse.ru, получить токен, настроить mirror в настройках gitea (Settings → Git Hooks/Mirrors → Add Push Mirror)
|
||||
- **Репозиторий (источник истины):** `https://gitverse.ru/kpa39l/email-assistant` (private, remote `gitverse`, https с токеном из `/opt/hermes/.hermes/secrets/git-tokens.env`)
|
||||
- **Gitea (зеркало):** `https://gitea.nixg.ru/estorozhenko/email-assistant` (pull mirror ← gitverse, интервал 12h, id 54). Старый `hermes/email-assistant` — обычный репо, не зеркало.
|
||||
- **AGENT.md** — политика репозитория/токенов (см. файл)
|
||||
- ~~Push mirror gitea → gitverse~~ ✅ **сделано 2026-09-15** (pull mirror наоборот: gitverse → gitea)
|
||||
|
||||
### Ресурсы проекта (расположение и доступ)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 2026-07-19 | clean_body — вырезание цитируемой переписки (Outlook/forward) | ✅ закрыта | STATUS.md контекст |
|
||||
| 2026-07-19 | Cron mail-index-incremental | 🔵 открыта | |
|
||||
| 2026-07-19 | Cron digest-weekly | 🔵 открыта | |
|
||||
| 2026-07-19 | Push mirror gitea → gitverse.ru | 🔵 открыта | |
|
||||
| 2026-07-19 | Push mirror gitea → gitverse.ru | ✅ закрыта (2026-09-15, иначе: pull mirror gitverse → gitea) | AGENT.md |
|
||||
|
||||
## 2026-09-11
|
||||
| Дата | Задача | Статус | Закрыта в |
|
||||
@@ -50,6 +50,21 @@
|
||||
| 2026-09-13 | Задача 2: Caddy reverse proxy — cal.nixg.ru работает (207), tasks.nixg.ru закомментирован | 🟡 частично (cal.nixg.ru готов) | STATUS.md |
|
||||
| 2026-09-13 | Задача 3: Android — контакты синхронизированы (DAVx5), события/задачи ещё не проверены в приложении | 🔵 открыта | |
|
||||
|
||||
## 2026-09-15
|
||||
| Дата | Задача | Статус | Закрыта в |
|
||||
|---|---|---|---|
|
||||
| 2026-09-15 | IMAP realtime sync: proposal/design/tasks/spec-дельты (openspec valid) | ✅ закрыта | openspec/changes/imap-realtime-sync/ |
|
||||
| 2026-09-15 | Выделена авторизация IMAP в `scripts/imap_client.py` (imap_connect, STARTTLS+LOGIN, re-try) + mail_archive.py переведён на неё | ✅ закрыта | STATUS.md §2026-09-15 |
|
||||
| 2026-09-15 | Логирование авторизации + метрики (JSON-лог /opt/hermes/email/logs/imap_client.log, imap_metrics, imap_session, --metrics) | ✅ закрыта | STATUS.md §2026-09-15 |
|
||||
| 2026-09-15 | Telegram-уведомления о важных письмах в ЛС (письмо 3216 → ЛС kpa39l, user подтвердил) | ✅ закрыта | `.env` проект (TELEGRAM_CHAT_ID=281328953) |
|
||||
| 2026-09-15 | Баг: крон mail-classify-handlers использовал python3 без httpx → уведомления не уходили (10 urgent-писем в backlog). Фикс: /opt/vesti/.venv/bin/python + --limit 2 | ✅ закрыта | scripts/mail-classify-handlers.sh |
|
||||
| 2026-09-15 | Формат даты в уведомлениях: `02.09.2026 10:49` (format_date_for_tg) + снятие YAML-кавычек (_unquote_yaml) | ✅ закрыта | scripts/email_handlers.py |
|
||||
| 2026-09-15 | imap_stream.py (IDLE-цикл) — скелет + выбор библиотеки (aioimaplib не установлен; свой клиент поверх imap_connect) | 🔵 открыта | openspec/changes/imap-realtime-sync/tasks.md |
|
||||
| 2026-09-15 | Backlog 10 urgent-писем (июль-сентябрь) — отправляются по 2/прогон крона в ЛС; часть просрочена (решить, пропускать ли) | 🔵 открыта | STATUS.md §2026-09-15 |
|
||||
| 2026-09-15 | Репозиторий: gitverse = источник истины (создан kpa39l/email-assistant, remote gitverse, master запушен) | ✅ закрыта | AGENT.md, STATUS.md |
|
||||
| 2026-09-15 | Gitea pull mirror: estorozhenko/email-assistant ← gitverse (12h, id 54) | ✅ закрыта | AGENT.md, STATUS.md |
|
||||
| 2026-09-15 | AGENT.md: добавить местоположение токенов (git-tokens.env, VESTI_BOT_TOKEN, TELEGRAM_CHAT_ID) | ✅ закрыта | AGENT.md |
|
||||
|
||||
## 2026-09-14
|
||||
| Дата | Задача | Статус | Закрыта в |
|
||||
|---|---|---|---|
|
||||
|
||||
+118
-1
@@ -303,4 +303,121 @@ VTIMEZONE Europe/Moscow + RRULE:FREQ=WEEKLY;BYDAY=TU, DTSTART 11:00.
|
||||
|
||||
**Cron:** mail-archive-every-5min (5f2305b2bbf8) приостанавливался на время
|
||||
отладки → **ВОЗОБНОВЛЁН** (next_run 08:05, state scheduled).
|
||||
вычитка openspec-файлов чейнджа.
|
||||
вычитка openspec-файлов чейнджа.
|
||||
|
||||
## 2026-09-15 — IMAP realtime sync (планирование) + Telegram-уведомления в ЛС
|
||||
|
||||
### 1. Диагностика авторизации: rate-limit, а не TLS-fingerprinting
|
||||
|
||||
**Симптом:** python/openssl/imaplib не логинятся на mail.corpoffice.tech:143
|
||||
(`NO AUTHENTICATE failed`), himalaya (rustls) логинится.
|
||||
|
||||
**Что перепробовано:**
|
||||
1. Raw-socket LOGIN для e.storozhenko / e.storozhenko@vinogorod.ru /
|
||||
VINOGOROD\e.storozhenko — NO.
|
||||
2. `ssl.wrap_socket` — `AttributeError` (убрано в py3.12) → `SSLContext.wrap_socket`.
|
||||
3. himalaya — логинится (эталон).
|
||||
4. Python с тем же base64 AUTHENTICATE PLAIN, что himalaya — NO.
|
||||
5. Сравнение TLS-отпечатков openssl vs rustls (ClientHello, ciphers) — различаются
|
||||
→ гипотеза **JA3 fingerprinting**.
|
||||
6. Порт 993 (IMAPS) через imaplib — тоже NO.
|
||||
7. **Опровержение:** чтение прод-кода `mail_archive.py` (~528) — он логинится
|
||||
простым `LOGIN login password` (не AUTH PLAIN). После паузы 45с — успех.
|
||||
Вывод: **rate-limit Exchange** (сервер молчит timeout после ~10 быстрых
|
||||
подключений), НЕ fingerprinting, НЕ бан IP.
|
||||
|
||||
**Урок:** не спешить с «экзотическими» диагнозами (JA3); сначала прочитать
|
||||
существующий прод-код — там уже рабочее решение.
|
||||
|
||||
### 2. `scripts/imap_client.py` — отдельная функция авторизации + наблюдаемость
|
||||
|
||||
- `imap_connect()`: socket → STARTTLS → LOGIN (простая форма, как в проде),
|
||||
re-try с экспоненциальной паузой 5→60с, креды из config/himalaya-config.toml.
|
||||
- JSON-лог в `/opt/hermes/email/logs/imap_client.log`: conn_ok/conn_error/
|
||||
auth_ok/auth_failed/session_started/session_ended (пароль никогда).
|
||||
- `imap_metrics()`: auth_success_rate, conn_ok/err, sessions_active.
|
||||
- `imap_session` (contextmanager) — гарантирует session_ended.
|
||||
- CLI: `python3 imap_client.py` (self-test), `--metrics`.
|
||||
- `mail_archive.py`: блок соединения заменён на `imap_connect()`;
|
||||
`sys.path.insert(0, str(Path(__file__).parent))` перед импортом imap_client
|
||||
(важно для cron: иначе импорт падает из-за cwd).
|
||||
- Проверено: `HOME=/home/estorozhenko python3 imap_client.py` →
|
||||
`OK: connected+LOGIN as e.storozhenko @ mail.corpoffice.tech`.
|
||||
ВАЖНО: запускать с `HOME=/home/estorozhenko` (конфиг himalaya там).
|
||||
|
||||
### 3. Telegram-уведомления о важных письмах — в ЛС (kpa39l)
|
||||
|
||||
- Создан `/opt/hermes/email-assistant/.env`: `TELEGRAM_CHAT_ID=281328953`
|
||||
(private chat kpa39l, бот @dedinit_controller_bot id 7765665742).
|
||||
Файл в .gitignore (строка 9) — не попадёт в git.
|
||||
- Порядок загрузки .env в email_handlers.py: проект `.env` → radicale/.env →
|
||||
/opt/vesti/.env; `override=False` → проект не перезаписывается.
|
||||
- Живой тест: письмо 3216 («RE: Платежи Аврора») помечено
|
||||
`classification: urgent` → отправлено в ЛС (msg_id=60), пользователь
|
||||
подтвердил получение. `handled_urgent: true` записан.
|
||||
|
||||
### 4. БАГ: крон не отправлял уведомления (python3 без httpx)
|
||||
|
||||
**Симптом:** 10 urgent-писем накопились без уведомлений.
|
||||
|
||||
**Причина:** `scripts/mail-classify-handlers.sh` вызывал `python3` (системный),
|
||||
у которого нет httpx → `tg_call` бросал RuntimeError, скрипт глушил ошибку
|
||||
`|| echo "[handlers] ошибка"`.
|
||||
|
||||
**Фикс:** `PY=/opt/vesti/.venv/bin/python` (fallback python3) + `--limit 2`
|
||||
(дозированно, анти-спам backlog). Проверено: `bash -n` OK.
|
||||
|
||||
### 5. Формат даты в уведомлениях
|
||||
|
||||
- `format_date_for_tg()`: `2026-09-02 10:49+03:00` → `02.09.2026 10:49`
|
||||
(ДД.ММ.ГГГГ ЧЧ:ММ; RFC3339 и Z тоже; незнакомое — as-is).
|
||||
- `_unquote_yaml()` в parse_email_md: снятие YAML-кавычек (`"..."` → значение).
|
||||
- Строка `📅 <дата>` в handle_urgent под заголовком.
|
||||
|
||||
### 6. Репозиторий: gitverse = источник истины + gitea mirror
|
||||
|
||||
**Решение пользователя (2026-09-15):** gitverse.ru — источник истины для всех
|
||||
репозиториев; gitea.nixg.ru — зеркало.
|
||||
|
||||
**Токен:** единый источник — `/opt/hermes/.hermes/secrets/git-tokens.env`
|
||||
(chmod 600): `GITVERSE_LOGIN=kpa39l` (НЕ estorozhenko!), `GITVERSE_PAT`,
|
||||
`GITVERSE_API=https://api.gitverse.ru`, `GITEA_API=http://127.0.0.1:3000/api/v1`,
|
||||
`GITEA_TOKEN`. Загрузка: `set -a && source ... && set +a`.
|
||||
|
||||
**Создание репо на gitverse (private, auto_init:false!):**
|
||||
```bash
|
||||
curl -X POST "$GITVERSE_API/user/repos" \
|
||||
-H "Authorization: Bearer $GITVERSE_PAT" \
|
||||
-H "Accept: application/vnd.gitverse.object+json;version=1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"email-assistant","private":true,"auto_init":false}' # → 201
|
||||
```
|
||||
|
||||
**Push в gitverse:** remote `gitverse` = `https://kpa39l:$GITVERSE_PAT@gitverse.ru/kpa39l/email-assistant.git`; `git push gitverse master`.
|
||||
|
||||
**Pull mirror в gitea (12h):** migrate API:
|
||||
```bash
|
||||
curl -X POST "$GITEA_API/repos/migrate" -H "Authorization: token $GITEA_TOKEN" \
|
||||
-d '{"clone_addr":"https://oauth2:$GITVERSE_PAT@gitverse.ru/$GITVERSE_LOGIN/email-assistant.git",
|
||||
"repo_name":"email-assistant","repo_owner":"estorozhenko","service":"git",
|
||||
"mirror":true,"mirror_interval":"12h"}' # → 201
|
||||
```
|
||||
Проверка: `git ls-remote https://oauth2:$PAT@gitverse.ru/kpa39l/email-assistant.git HEAD` ==
|
||||
`docker exec gitea git --git-dir=/data/git/repositories/estorozhenko/email-assistant.git rev-parse HEAD`;
|
||||
mirror-sync `POST $GITEA_API/repos/estorozhenko/email-assistant/mirror-sync` → 200.
|
||||
|
||||
**Питфолы:**
|
||||
- База gitverse API — `api.gitverse.ru`, НЕ `gitverse.ru/api/v1` (там HTML SPA).
|
||||
- Gitea migrate только с `https://oauth2:<PAT>@...` (ssh:// → 422).
|
||||
- `auto_init:false` обязательно (иначе первый push упадёт).
|
||||
- `GITVERSE_API` уже содержит `https://` — не добавлять его повторно.
|
||||
- После пуша токен в remote URL — оставить (git config, не в коммитах); НЕ вычищать,
|
||||
иначе следующий push потребует креды.
|
||||
|
||||
### Открытые хвосты (2026-09-15)
|
||||
|
||||
- imap_stream.py (Фаза 1): скелет IDLE-цикла; aioimaplib не установлен —
|
||||
выбор: ставить его или свой клиент поверх imap_connect() (raw socket).
|
||||
- Backlog 10 urgent-писем (июль-сентябрь): отправляются по 2/прогон крона в ЛС;
|
||||
часть просрочена — решить, пропускать ли (пометить handled).
|
||||
- Live-тесты Exchange: пауза ≥45с между подключениями (rate-limit).
|
||||
Reference in New Issue
Block a user