Session close 2026-09-15: gitverse source of truth, gitea mirror, token locations in AGENT.md

This commit is contained in:
2026-09-15 17:47:29 +00:00
parent 0d86f843c3
commit 65c167acc2
4 changed files with 180 additions and 10 deletions
+118 -1
View File
@@ -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).