Files
hermes 8ea022f5c0 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) для следующей сессии
2026-09-13 17:21:51 +00:00

101 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks: CardDAV-сервер для синхронизации контактов
## 1. Адресная книга в Radicale
- [x] 1.1 Создать адресную книгу «Контакты» на ФС:
`mkdir -p /opt/hermes/email-assistant/radicale/data/collections/collection-root/estorozhenko/Контакты`
и проверить, что Radicale видит её как addressbook:
`curl -u estorozhenko:$RADICALE_PASS -X PROPFIND -H 'Depth: 0' http://127.0.0.1:5232/estorozhenko/Контакты/`
→ HTTP 207 и в XML есть `<C:addressbook>` (если RS не определяется —
добавить `.Radicale.props` с addressbook и повторить).
**Сделано:** MKCOL через протокол дал 201; `.Radicale.props` = `{"tag": "VADDRESSBOOK"}`.
- [x] 1.2 Положить тестовую vCard (test.vcf) в коллекцию и проверить, что
она отдаётся: `curl ... /estorozhenko/Контакты/test.vcf` → 200 + vCard;
затем удалить тестовую карточку.
## 2. Синк в contacts_extractor.py
- [x] 2.1 Рефакторинг: вынести генерацию vCard 4.0 из существующей
generate_vcard() в отдельную функцию `contact_to_vcard(contact) -> str`,
чтобы переиспользовать для CardDAV-карточек. Проверка: скрипт
запускается без ошибок, contacts.vcf генерируется как раньше.
**Сделано:** в `scripts/contacts_caldav_sync.py` (модульная выноска, contacts_extractor не тронут).
- [x] 2.2 Добавить функцию `sync_contacts_to_caldav(contacts_dir, base_url,
user, password, prune=False)`: читает contacts.json, строит карту
uid→(ETag, href) через PROPFIND Depth:1, PUT создаёт/обновляет vCard,
при `prune=True` DELETE удаляет лишние. Использовать urllib + Basic
Auth. Проверка: юнит-запуск с тестовым Radicale (см. 3.x).
- [x] 2.3 CLI-флаги: `--sync-caldav`, `--prune-caldav`, `--caldav-url`,
`--caldav-user`, `--caldav-pass` (или env CALDAV_PASS). По умолчанию
синк выключен. Проверка: `python3 scripts/contacts_extractor.py --help`
показывает все флаги; без `--sync-caldav` поведение прежнее.
**Сделано:** CLI в `contacts_caldav_sync.py` (--sync-caldav выключен по умолчанию, --dry-run и пр.)
- [x] 2.4 Двусторонняя сверка: `sync_carddav()` после push выполняет pull —
PROPFIND Depth:1, сравнение ETag, создание/обновление контактов из
новых/изменённых карточек (REQ-008/009), `deleted: true` при удалении
карточки (REQ-010). Проверка: см. 3.5-3.8 (reverse-pull тесты).
- [x] 2.5 ETag-конфликты: при PUT с If-Match и ответе 412 — принять версию
с сервера, локальную записать в `caldav-sync.log` (`conflict_local`),
продолжить (REQ-011). Проверка: ручной тест — изменить vCard на
сервере И контакт в базе, запустить синк, увидеть conflict в логе.
## 3. Сквозной тест sync
- [x] 3.1 Запустить синк с реальной базой:
`python3 scripts/contacts_extractor.py --sync-caldav`
(или отдельный скрипт) → в Radicale появились карточки (счётчик
PROPFIND/cards): `curl ... PROPFIND Depth:1 /estorozhenko/Контакты/`
показывает N карточек ≈ количеству контактов в contacts.json с email.
**Сделано:** 81 карточка создана (81 контакт), PROPFIND 207.
- [x] 3.2 Повторный запуск — количество карточек не растёт (идемпотентность).
**Сделано:** стабильно 162 unchanged (81 push + 81 pull), 0 PUT.
- [x] 3.3 Изменить контакт в contacts.json (добавить телефон) → повторный
синк обновляет карточку (TEL появился, ETag изменился).
- [x] 3.4 `--prune-caldav`: удалить контакт из contacts.json → карточка
удалена с сервера.
- [x] 3.5 **Reverse pull:** вручную (curl PUT) изменить vCard на сервере →
повторный синк обновляет контакт в contacts.json.
**Сделано:** добавлен/удалён TEL на сервере → база обновилась/обнулила phone.
- [x] 3.6 **Новая карточка на сервере:** curl PUT новой vCard → синк создаёт
контакт в базе. **Сделано:** pulled_created (id=phone-newcard-001, from_device=True).
- [x] 3.7 **Удаление на сервере:** curl DELETE vCard → контакт получает
`deleted: true` в базе. **Сделано:** pulled_deleted, артефакт убран из базы.
- [x] 3.8 **Конфликт:** изменить и vCard (curl), и контакт в базе → синк
применяет версию с сервера, локальная в caldav-sync.log.
## 4. Интеграция в cron и документация
- [ ] 4.1 Обновить `config/contacts-cron.sh`: добавить `--sync-caldav` и
подтянуть пароль из radicale/.env (env CALDAV_PASS). Проверка:
запуск cron-скрипта вручную синкает контакты без ошибок.
- [x] 4.2 Обновить README/STATUS: раздел «CardDAV (контакты)» — как
подключить на Android (DAVx5, URL http://cal.nixg.ru:5232
или cal.nixg.ru, логин estorozhenko), порты, флаги синка.
**Сделано (2026-09-13):** STATUS.md — новый раздел «DAVx⁵ (Android: CalDAV/CardDAV-мост)» + Caddy-конфиги (Authorization, handle_path, X-Script-Name), Задача 3 помечена как следующая сессия.
- [ ] 4.3 `openspec validate contacts-caldav-server` → valid.
- [ ] 4.4 Git commit и push (gitea.nixg.ru/hermes/email-assistant).
## 5. Синхронизация с телефоном (СЛЕДУЮЩАЯ СЕССИЯ — первая задача)
- [ ] 5.1 Настроить Caddy на vps02: `cal.nixg.ru` → Radicale bigbox :5232
(header_up Authorization, reverse_proxy 10.8.0.2:5232);
`tasks.nixg.ru` → Vikunja :3456. Reload Caddyfile.
- [ ] 5.2 Проверить CalDAV/CardDAV снаружи: PROPFIND https://cal.nixg.ru/... → 207
(учесть кириллицу «Контакты» в URL, Auth).
- [ ] 5.3 DAVx⁵ на телефоне: аккаунт https://cal.nixg.ru, логин estorozhenko,
пароль RADICALE_PASS → выбрать календари (Личный/Рабочий/Задачи)
и адресную книгу «Контакты».
- [ ] 5.4 jtx board на телефоне → Vikunja https://tasks.nixg.ru (токен API),
проверить задачи.
- [ ] 5.5 Проверка двусторонней синхронизации: контакт создан/изменён на
телефоне → в базе (contacts.json) после `contacts_caldav_sync.py`;
задача из jtx board → в Vikunja; событие из DAVx⁵ → в Radicale.
- [ ] 5.6 (опц.) Вписать sync в contacts-cron.sh (4.1) и проверить V5.
## Verification
- [ ] V1: `curl -X PROPFIND -u estorozhenko:PASS http://127.0.0.1:5232/estorozhenko/Контакты/` → 207 + addressbook
- [ ] V2: после синка количество vCard в адресной книге == числу контактов с email в contacts.json
- [ ] V3: повторный синк → 0 новых карточек
- [ ] V4: правка контакта на сервере (curl PUT vCard) → контакт в contacts.json обновлён после синка
- [ ] V5: `python3 scripts/contacts_extractor.py --sync-caldav` из cron-обёртки завершается кодом 0 и пишет caldav-sync.log