CardDAV: двусторонний синк контактов с Radicale (push+pull) + docs DAVx5/Caddy

- scripts/contacts_caldav_sync.py: push 81 контакт → vCard (Radicale :5232),
  pull правок/создания/удаления с телефона → contacts.json (ETag сверка,
  soft-delete, конфликты 412 → приоритет телефону, лог caldav-sync.log)
- fix(contacts_extractor): перестройка by_email-индексов после sort()
- STATUS.md: раздел DAVx5 + Caddy (Authorization/handle_path/X-Script-Name),
  Задача 3 (синхронизация с телефоном) = следующая сессия
- tasks.md: отмечены 1.x-3.x/V (реализовано и протестировано), добавлен
  раздел 5 (Caddy + DAVx5 + jtx board) для следующей сессии
This commit is contained in:
2026-09-13 17:21:51 +00:00
parent 17252ebfa9
commit 8ea022f5c0
5 changed files with 698 additions and 25 deletions
+53 -6
View File
@@ -119,13 +119,15 @@ Hermes cron:
- [x] Change `local-calendar-tasks` создан и валиден (опenspec-lab) — proposal/specs/design/tasks
- [x] **Radicale развёрнут и РАБОТАЕТ (2026-09-13)**: контейнер на :5232, PROPFIND 207 с паролем / 401 без. Коллекции Личный/Рабочий/Задачи на ФС.
- [x] **Vikunja v2.6.0 развёрнут (2026-09-13)**: postgres + app, миграции прошли, HTTP слушает :3456. **Блокер:** снаружи connection reset (docker-proxy/NAT на bigbox) — контейнер отвечает, маунт файлов исправлен (./files → /app/vikunja/files).
- [ ] Vikunja: создать админа + API-токен, проверить tasks.nixg.ru
- [ ] Caddy reverse proxy (cal.nixg.ru → 5232, tasks.nixg.ru → 3456)
- [ ] Android-синхронизация (DAVx5)
- [x] **CardDAV-синк контактов (2026-09-13, change `contacts-caldav-server`)**: `scripts/contacts_caldav_sync.py` — двусторонний sync. PUSH: 81 контакт → vCard в Radicale. PULL: правки/создание/удаление карточек с телефона → contacts.json. Идемпотентно (162 unchanged, 0 PUT на повторе). Конфликты (412) — приоритет телефону, локальная версия в `caldav-sync.log`. Подробнее: `scripts/contacts_caldav_sync.py --help`, лог `/opt/hermes/email/contacts/caldav-sync.log`.
- [ ] **Vikunja: создать админа + API-токен, проверить tasks.nixg.ru**
- [ ] **Caddy reverse proxy (cal.nixg.ru → 5232, tasks.nixg.ru → 3456)** — СДЕЛАТЬ СЛЕДУЮЩЕЙ СЕССИЕЙ (см. ниже «DAVx⁵»)
- [ ] **Android-синхронизация (DAVx5)** — СДЕЛАТЬ СЛЕДУЮЩЕЙ СЕССИЕЙ (см. ниже)
### Задача 3: Нативная синхронизация с Android ⬜ (после Задачи 2)
- [ ] DAVx5 на телефоне → Radicale; Vikunja app/token
- [ ] Проверка двусторонней синхронизации (событие с телефона → bigbox)
### Задача 3: Нативная синхронизация с Android 🔵 (следующая сессия)
- [ ] **Первая задача след. сессии: настроить синхронизацию с телефоном** (DAVx5 → Radicale; Vikunja → jtx board)
- [ ] Настроить Caddy reverse proxy для Radicale (cal.nixg.ru) — см. ниже
- [ ] Проверка двусторонней синхронизации (событие/контакт с телефона → bigbox → база)
### Задача 1: Веб-интерфейс ассистента ⬜
- [ ] FastAPI + SQLite FTS5: список писем (дата/адресант/тэги/папка)
@@ -235,6 +237,51 @@ Hermes cron:
---
## DAVx⁵ (Android: CalDAV/CardDAV-мост)
**Назначение:** DAVx⁵ — приложение-синхронизатор для Android, **не имеет собственного UI** для просмотра событий/контактов, а встраивается в стандартные системные приложения Android (Календарь, Контакты). Это стандарт де-факто для синхронизации с Radicale на Android.
- **Где взять:** F-Droid (бесплатно) или Google Play (платно, поддержка разработчиков).
- **Как работает:** добавляете аккаунт (URL сервера Radicale, логин, пароль) → DAVx⁵ сам находит доступные календари и адресные книги → выбираете, что синхронизировать с системой.
- **Для задач** пользователь поставил **jtx board** (не DAVx⁵).
### Настройка Caddy для Radicale
Проксирование Radicale через Caddy требует внимания к путям и заголовкам, иначе CalDAV/CardDAV-клиенты не найдут ресурсы.
**Рабочий пример для домена `dav.example.com` (Radicale в корне):**
```caddyfile
dav.example.com {
# Важно: сохраняем заголовок Authorization для Radicale
header_up Authorization {header.Authorization}
# Если Radicale в подпапке — используйте handle_path (см. ниже)
reverse_proxy localhost:5232
}
```
**Ключевые моменты:**
1. **Сохранение Authorization:** Caddy по умолчанию может удалять заголовки. `header_up Authorization {header.Authorization}` гарантирует, что Radicale получит логин/пароль.
2. **X-Script-Name (если в подпапке):** для размещения по `/radicale` нужен `handle_path` для удаления префикса пути + заголовок `X-Script-Name`, чтобы Radicale знал о своём расположении.
3. **Обязательный `handle_path` для подпапки:** простой `reverse_proxy` внутри `handle` может не сработать — Radicale ожидает запросы без префикса (получает его через `X-Script-Name`).
**Пример для подпапки `/radicale`:**
```caddyfile
dav.example.com {
handle_path /radicale/* {
header_up Authorization {header.Authorization}
header_up X-Script-Name /radicale
reverse_proxy localhost:5232
}
}
```
**В DAVx⁵ указывается базовый URL** (например, `https://dav.example.com` или `https://dav.example.com/radicale`); пути к календарям/контактам приложение определяет автоматически.
**Наш случай (следующая сессия):** поддомен `cal.nixg.ru` (Caddy на vps02, `reverse_proxy 10.8.0.2:5232` к bigbox) — Radicale слушает 127.0.0.1:5232, user `estorozhenko`, пароль `RADICALE_PASS` в `radicale/.env`. Адресная книга: `Контакты` (кириллица в URL — DAVx5 умеет). Vikunja → jtx board через `tasks.nixg.ru`.
---
## Конфигурация
- **Репозиторий:** `https://gitea.nixg.ru/hermes/email-assistant`