mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
OpenSpec: разнести openspec по проектам
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-11
|
||||
@@ -0,0 +1,85 @@
|
||||
# Design: Анализ хранения писем — ФС vs Maildir
|
||||
|
||||
## Обзор
|
||||
|
||||
Документ `STORAGE_ANALYSIS.md` пишется вручную (это аналитика, не код).
|
||||
Анализ опирается на:
|
||||
- реальные данные архива (структуру, число файлов, размеры)
|
||||
- стандарты Maildir/MBOX/notmuch
|
||||
- мотивацию пользователя (локальная LLM в скриптах)
|
||||
|
||||
## Файлы
|
||||
|
||||
| Файл | Действие | Описание |
|
||||
|-------|----------|----------|
|
||||
| `/opt/hermes/email-assistant/STORAGE_ANALYSIS.md` | создать | Анализ + таблица + рекомендация |
|
||||
| `/opt/hermes/email-assistant/README.md` | изменить | Добавить ссылку в раздел «Оценка альтернатив» |
|
||||
|
||||
## Анализ (что будет в документе)
|
||||
|
||||
### Текущий формат (`email.md`)
|
||||
- **Плюсы:** человекочитаемый (YAML-frontmatter + Markdown-тело), идеален для LLM
|
||||
(grep/find/obsidian), атомарность записи (новая директория UID), прозрачность бэкапов
|
||||
- **Минусы:** нестандартный (MUA не читают), без флагов на уровне ФС (Seen/Answered
|
||||
в frontmatter, не атрибут), дублирование с SQLite-индексом (mail_index.db),
|
||||
нет жёсткой гарантии целостности (нет fsync-семантики Maildir)
|
||||
|
||||
### Maildir
|
||||
- **Плюсы:** стандарт (mutt/neomutt/thunderbird, dovecot), атомарность
|
||||
(tmp→new→cur), флаги в имени файла (`:2,RS`), быстрый инкрементальный скан
|
||||
(число файлов в new/), не требует БД
|
||||
- **Минусы:** тело в raw-MIME (нужен парсинг для LLM — но `mail`/`mhonarc`
|
||||
извлекают), имена файлов нечитаемы, нет человекочитаемых метаданных, сложнее
|
||||
grep по теме (тема в заголовке MIME, не в frontmatter)
|
||||
|
||||
### MBOX
|
||||
- **Минусы:** один файл на папку (перезапись всего файла при изменении),
|
||||
блокировки, не для инкрементального чтения LLM — сразу исключается для
|
||||
нашего сценария
|
||||
|
||||
### notmuch
|
||||
- **Плюсы:** индексный слой поверх Maildir, быстрый полнотекстовый поиск,
|
||||
тэги (подходят для «назначенных тэгов» из UI), интеграция с MUA
|
||||
- **Минусы:** нужен демон/индекс, не заменяет хранение (всё равно Maildir
|
||||
или own format), ещё один слой сложности
|
||||
|
||||
### LLM-сценарий (главный)
|
||||
- LLM в скриптах: `cat email.md | ollama run qwen3:8b` — работает напрямую
|
||||
(frontmatter + тело). Для Maildir нужен `mail`/`munpack`/свой парсер MIME.
|
||||
- Тэги для веб-UI: в текущем формате можно добавить поле `tags: []` в
|
||||
frontmatter. Maildir — тэги как флаги не предусмотрены (только Seen/Answered/
|
||||
Flagged), для UI-тэгов нужен отдельный индекс (notmuch или SQLite)
|
||||
|
||||
## Рекомендация (предварительная)
|
||||
|
||||
**Остаться на текущем `email.md` + SQLite FTS5**, но с эволюцией:
|
||||
1. Добавить `tags: []` в frontmatter для UI-тэгов
|
||||
2. Оставить Maildir-совместимость как опцию экспорта (не миграции)
|
||||
3. notmuch — опция для поиска, если FTS5 станет тесным
|
||||
|
||||
**Обоснование:** мотивация пользователя (LLM из скриптов) полностью закрывается
|
||||
текущим форматом; Maildir даёт стандартность, но теряет человекочитаемость,
|
||||
удобство LLM и требует парсинга MIME. Гибрид (email.md + экспорт в Maildir/
|
||||
notmuch) даёт лучшее из двух миров. Окончательный вывод — после замеров.
|
||||
|
||||
## Команды применения
|
||||
|
||||
```bash
|
||||
# Создать анализ (вручную, здесь)
|
||||
# Обновить README: добавить ссылку
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```bash
|
||||
grep -q 'STORAGE_ANALYSIS' /opt/hermes/email-assistant/README.md
|
||||
test -f /opt/hermes/email-assistant/STORAGE_ANALYSIS.md
|
||||
find /opt/hermes/email -name 'email.md' | wc -l # без изменений с 2652
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
```bash
|
||||
rm /opt/hermes/email-assistant/STORAGE_ANALYSIS.md
|
||||
# убрать ссылку из README.md
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
# Proposal: Анализ хранения писем — ФС vs Maildir
|
||||
|
||||
## Why
|
||||
|
||||
Пользователь хранит письма в файловой системе как `email.md` (YAML-frontmatter + тело)
|
||||
в `/opt/hermes/email/<folder>/YYYY/MM/UID/`. Мотивация — **использовать локальную
|
||||
нейросеть (Qwen3:8b через Ollama) как инструмент в обычных скриптах**, без облака
|
||||
и трат. Но перед развитием веб-интерфейса (и вообще проекта) нужно **объективно
|
||||
оценить**, удобен ли текущий формат хранения по сравнению с **Maildir** и
|
||||
аналогичными (MBOX, notmuch) — чтобы не закладывать архитектуру на неправильном
|
||||
фундаменте.
|
||||
|
||||
Пользователь явно сказал: «анализировать насколько мой подход в хранении писем
|
||||
в файловой системе удобен по сравнению с maildir и ему подобными способами.
|
||||
Последняя задача в приоритете, пока мы не ушли далеко».
|
||||
|
||||
## What Changes
|
||||
|
||||
Создаётся документ `STORAGE_ANALYSIS.md` в корне `/opt/hermes/email-assistant/` —
|
||||
объективное сравнение подходов к хранению писем:
|
||||
|
||||
1. **Текущий формат** (`email.md`: YAML-frontmatter + тело в `/YYYY/MM/UID/`)
|
||||
2. **Maildir** (стандарт: `cur/`, `new/`, `tmp/`, имя файла = `host.timestamp.pid_uid.size:2,S`)
|
||||
3. **MBOX** (один mbox-файл на папку)
|
||||
4. **notmuch** (индексный слой поверх Maildir/почты)
|
||||
|
||||
Критерии сравнения (таблица):
|
||||
- **Производительность** инкрементального чтения (LLM-анализ в скриптах)
|
||||
- **Устойчивость** к сбоям (атомарность, потеря данных)
|
||||
- **Интеграция** с инструментами (grep/find/jq/obsidian)
|
||||
- **Пригодность для LLM** (быстрое чтение тела без парсинга MIME)
|
||||
- **Совместимость** со стандартными MUA (mutt/neomutt/thunderbird)
|
||||
- **Масштабируемость** (10k, 100k писем)
|
||||
- **Резервное копирование** (Yandex Disk, git)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `email-storage-format`: Документированное обоснование выбора формата хранения писем
|
||||
(текущий vs Maildir vs MBOX vs notmuch) и рекомендация по дальнейшему развитию.
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- нет -->
|
||||
|
||||
## Impact
|
||||
|
||||
- **Код:** нет изменений кода, только документация
|
||||
- **Документация:** новый файл `STORAGE_ANALYSIS.md`, ссылка из `README.md`
|
||||
- **Риск:** анализ может порекомендовать миграцию на Maildir — тогда это
|
||||
отдельный change (следующий шаг). Пока — только документ, **ничего не мигрируем**.
|
||||
|
||||
## Rollback
|
||||
|
||||
- Удалить `STORAGE_ANALYSIS.md` и ссылку из `README.md`.
|
||||
- Данные не трогаются — откат тривиален.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# Email Storage Format — Requirement Spec (Delta)
|
||||
|
||||
> New capability: `email-storage-format`
|
||||
> Change: `email-storage-analysis`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: REQ-EMA-STORAGE-001: Обоснование выбора формата хранения
|
||||
|
||||
**MUST** — проект должен содержать документ `STORAGE_ANALYSIS.md` в корне
|
||||
`/opt/hermes/email-assistant/`, объективно сравнивающий текущий формат
|
||||
хранения (`email.md` в `/<folder>/YYYY/MM/UID/`) с Maildir, MBOX и notmuch.
|
||||
|
||||
#### Scenario: Документ анализа существует
|
||||
|
||||
**GIVEN** файл `STORAGE_ANALYSIS.md` существует
|
||||
**WHEN** его открывают
|
||||
**THEN** он содержит:
|
||||
- таблицу сравнения по критериям (производительность, устойчивость, интеграция,
|
||||
пригодность для LLM, совместимость с MUA, масштабируемость, бэкапы)
|
||||
- явную рекомендацию (остаться на текущем / мигрировать на Maildir / иное)
|
||||
- обоснование рекомендации с учётом мотивации пользователя (локальная LLM
|
||||
в скриптах, без облака)
|
||||
|
||||
### Requirement: REQ-EMA-STORAGE-002: Ссылка из README
|
||||
|
||||
**MUST** — `README.md` проекта должен содержать ссылку на `STORAGE_ANALYSIS.md`.
|
||||
|
||||
#### Scenario: README содержит ссылку
|
||||
|
||||
**GIVEN** `README.md` проекта
|
||||
**WHEN** открываем его
|
||||
**THEN** в разделе «Оценка альтернатив» (или аналогичном) есть ссылка
|
||||
`[Анализ формата хранения (ФС vs Maildir)](STORAGE_ANALYSIS.md)`.
|
||||
|
||||
### Requirement: REQ-EMA-STORAGE-003: Без изменения данных
|
||||
|
||||
**MUST** — change не должен модифицировать, мигрировать или удалять
|
||||
существующие письма в `/opt/hermes/email/`. Анализ — только документация.
|
||||
|
||||
#### Scenario: Архив не изменён
|
||||
|
||||
**GIVEN** архив `/opt/hermes/email/`
|
||||
**WHEN** change применён
|
||||
**THEN** файлы писем остаются без изменений (проверка: `find /opt/hermes/email -name 'email.md' | wc -l` — то же число, что и до change).
|
||||
@@ -0,0 +1,25 @@
|
||||
# Tasks: Анализ хранения писем — ФС vs Maildir
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [x] T1: Собрать факты по текущему формату (структура, число файлов, размеры,
|
||||
frontmatter-поля)
|
||||
- Команда: `find /opt/hermes/email -name 'email.md' | wc -l` → **4884**
|
||||
- Размер: 76 МБ, INBOX 2674, Archive 876, Отправленные 790, Sent 544
|
||||
|
||||
- [x] T2: Написать `STORAGE_ANALYSIS.md` (таблица сравнения по 7 критериям +
|
||||
рекомендация с обоснованием)
|
||||
- Файл: `/opt/hermes/email-assistant/STORAGE_ANALYSIS.md` (создан 2026-09-11)
|
||||
|
||||
- [x] T3: Добавить ссылку в `README.md` (раздел «Оценка альтернатив»)
|
||||
- Файл: `/opt/hermes/email-assistant/README.md` (добавлена ссылка на STORAGE_ANALYSIS.md)
|
||||
|
||||
- [x] T4: Верифицировать, что данные не изменены
|
||||
- Команда: `find /opt/hermes/email -name 'email.md' | wc -l` → **4884** (проверено, без изменений)
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] V1: `test -f /opt/hermes/email-assistant/STORAGE_ANALYSIS.md`
|
||||
- [ ] V2: `grep -q 'STORAGE_ANALYSIS' /opt/hermes/email-assistant/README.md`
|
||||
- [ ] V3: `find /opt/hermes/email -name 'email.md' | wc -l` → 2652 (без изменений)
|
||||
- [ ] V4: `cd /opt/hermes/openspec-lab && openspec validate email-storage-analysis`
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-11
|
||||
@@ -0,0 +1,144 @@
|
||||
# Design: Локальные сервисы календаря (Radicale) и задач (Vikunja)
|
||||
|
||||
## Архитектура (по требованию пользователя)
|
||||
|
||||
> **ВСЕ сервисы работают в отдельных Docker-контейнерах.**
|
||||
> Полный стек изолирован в docker compose; никаких systemd-сервисов
|
||||
> для Radicale/Vikunja.
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ bigbox (docker) │
|
||||
│ ┌──────────────┐ ┌───────────────────┐ │
|
||||
│ │ radicale │ │ vikunja │ │
|
||||
│ │ :5232 CalDAV │ │ :3456 WebUI+API │ │
|
||||
│ │ │ │ └─ postgres (:5433)│ │
|
||||
│ └──────────────┘ └───────────────────┘ │
|
||||
└────────────────────────────────────────────┘
|
||||
│ │
|
||||
└── Caddy reverse proxy (TLS) → Android DAVx5
|
||||
```
|
||||
|
||||
## Сервисы и файлы
|
||||
|
||||
| Сервис | Образ | Порт | Каталог | Данные |
|
||||
|--------|-------|------|---------|--------|
|
||||
| Radicale | `tobymossman/radicale` | 5232 | `/opt/hermes/radicale/` | volume `radicale-data` (коллекции .xand) |
|
||||
| Vikunja | `vikunja/vikunja:latest` | 3456 | `/opt/hermes/vikunja/` | volume `vikunja-files`, `vikunja-db` |
|
||||
| PostgreSQL | `postgres:16-alpine` | 5433 (внутр.) | — | volume `vikunja-db` |
|
||||
|
||||
## Radicale (docker)
|
||||
|
||||
```yaml
|
||||
# /opt/hermes/radicale/docker-compose.yml
|
||||
services:
|
||||
radicale:
|
||||
image: tobyworrall/radicale:latest
|
||||
container_name: radicale
|
||||
ports:
|
||||
- "5232:5232"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./config:/config
|
||||
environment:
|
||||
- RADICALE_APPS=caldav
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- **Auth:** HTTP Basic. Пользователи в `config` (htpasswd-файл users)
|
||||
- **Коллекции:** `/data/collections/` (календари .xand, задачи .ics)
|
||||
- **Создание коллекций:** через DAVx5/curl PUT, или вручную скриптом
|
||||
(`radicale_collections.sh` создаёт «Личный», «Рабочий», «Задачи»)
|
||||
|
||||
## Vikunja (docker)
|
||||
|
||||
```yaml
|
||||
# /opt/hermes/vikunja/docker-compose.yml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_PASSWORD: [REDACTED]
|
||||
POSTGRES_DB: vikunja
|
||||
volumes:
|
||||
- vikunja-db:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
vikunja:
|
||||
image: vikunja/vikunja:latest
|
||||
depends_on: [db]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_DATABASE_USERNAME: vikunja
|
||||
VIKUNJA_DATABASE_PASSWORD: [REDACTED]
|
||||
VIKUNJA_SERVICE_JWTSECRET: [REDACTED]
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://tasks.nixg.ru
|
||||
VIKUNJA_SERVICE_PUBLICURL: https://tasks.nixg.ru
|
||||
ports:
|
||||
- "3456:80"
|
||||
volumes:
|
||||
- vikunja-files:/app/files
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- **Порт:** 3456 (netbox на 8080 — не конфликтует)
|
||||
- **Postgres:** внутренний контейнер на 5433 (не публикуем наружу)
|
||||
- **Данные:** volumes vikunja-db + vikunja-files
|
||||
- **Логин:** первый зарегистрированный пользователь становится админом
|
||||
- **API:** `/api/v1/` — токен в Настройки → API tokens
|
||||
|
||||
## Caddy reverse proxy (TLS для Android)
|
||||
|
||||
Radicale (DAVx5) и Vikunja доступны снаружи через Caddy (он уже есть в
|
||||
/opt/gitea.nixg.ru). Домены:
|
||||
- `cal.nixg.ru` → Radicale :5232
|
||||
- `tasks.nixg.ru` → Vikunja :3456
|
||||
|
||||
```caddy
|
||||
cal.nixg.ru {
|
||||
reverse_proxy 127.0.0.1:5232
|
||||
}
|
||||
tasks.nixg.ru {
|
||||
reverse_proxy 127.0.0.1:3456
|
||||
}
|
||||
```
|
||||
|
||||
TLS — Let's Encrypt через Caddy (получает автоматически).
|
||||
|
||||
## Команды применения
|
||||
|
||||
```bash
|
||||
# Radicale
|
||||
mkdir -p /opt/hermes/radicale/{config,data}
|
||||
cd /opt/hermes/radicale && docker compose up -d
|
||||
|
||||
# Vikunja
|
||||
mkdir -p /opt/hermes/vikunja
|
||||
cd /opt/hermes/vikunja && docker compose up -d
|
||||
|
||||
# Проверка
|
||||
curl -i -X PROPFIND http://127.0.0.1:5232/ -u estorozhenko:...
|
||||
curl http://127.0.0.1:3456/api/v1/info
|
||||
```
|
||||
|
||||
## Верификация (GIVEN/WHEN/THEN → команды)
|
||||
|
||||
1. Radicale: `curl -s -o /dev/null -w '%{http_code}' -X PROPFIND http://127.0.0.1:5232/ -u estorozhenko:...` → 207
|
||||
2. Vikunja: `curl -s http://127.0.0.1:3456/api/v1/info` → JSON 200
|
||||
3. API-токен: создать задачу → 201
|
||||
4. Android/DAVx5: событие с телефона появляется в Radicale
|
||||
|
||||
## Rollback
|
||||
|
||||
```bash
|
||||
cd /opt/hermes/vikunja && docker compose down -v # удалить всё, включая volume
|
||||
cd /opt/hermes/radicale && docker compose down -v
|
||||
rm -rf /opt/hermes/vikunja /opt/hermes/radicale
|
||||
# убрать строки из Caddy и перезапустить его (внешне, sudo systemctl restart)
|
||||
```
|
||||
|
||||
## Секреты
|
||||
|
||||
Пароли/токены — только в `.env` файлах сервисов ([REDACTED] в доках), НЕ в git.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Proposal: Локальные сервисы календаря (Radicale) и задач (Vikunja)
|
||||
|
||||
## Why
|
||||
|
||||
Для синхронизации календаря и задач с Android-телефоном нужны локальные
|
||||
сервисы (без облака). Пользователь хочет:
|
||||
- **Календарь** — нативная синхронизация с Android
|
||||
- **Трекер задач** — с веб-UI и API, чтобы веб-интерфейс почты мог
|
||||
создавать задачи кнопкой
|
||||
- Всё **локально** (bigbox), без облачных зависимостей
|
||||
|
||||
Сейчас календаря и трекера задач **нет** (проверено: порт 5232 свободен,
|
||||
образы radicale/vikunja не установлены). Порт 8080 занят NetBox.
|
||||
|
||||
## What Changes
|
||||
|
||||
Два новых сервиса в `/opt/hermes/`:
|
||||
|
||||
### 1. Radicale (CalDAV) — календарь + задачи VTODO
|
||||
- **Порт:** 5232 (свободен)
|
||||
- **Путь:** `/opt/hermes/radicale/`
|
||||
- **Способ:** Python pip (лёгкий, systemd) ИЛИ docker
|
||||
- **Назначение:** CalDAV-сервер для:
|
||||
- Календаря «Личный» + «Рабочий» (синхронизация с Android через DAVx5)
|
||||
- Задач (VTODO) — тоже через CalDAV
|
||||
- **Конфиг:** лёгкий, single-user (правки через UI/файл)
|
||||
|
||||
### 2. Vikunja (трекер задач) — веб-UI + REST API + Android app
|
||||
- **Порт:** 3456 (дефолт Vikunja, свободен)
|
||||
- **Путь:** `/opt/hermes/vikunja/`
|
||||
- **Способ:** docker compose (postgres + vikunja)
|
||||
- **Назначение:**
|
||||
- Трекер задач с веб-интерфейсом (kanban/список)
|
||||
- **REST API** — для кнопки «Создать задачу» из веб-интерфейса почты
|
||||
- Android-приложение (нативный клиент Vikunja)
|
||||
- **БД:** PostgreSQL (docker), данные в volume
|
||||
|
||||
### Общие решения
|
||||
- **Доступ:** 127.0.0.1 (локально) + опционально через Caddy reverse proxy
|
||||
на поддомене (caddy есть в /opt/gitea.nixg.ru)
|
||||
- **Порты:** 5232 (radicale), 3456 (vikunja) — не конфликтуют с 8080 (NetBox)
|
||||
- **Авторизация:** Radicale — HTTP Basic (логин/пароль в конфиге);
|
||||
Vikunja — своя (регистрация/логин, API-токены)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `calendar-caldav`: Локальный CalDAV-сервер (Radicale) для календаря и
|
||||
задач, синхронизация с Android через DAVx5
|
||||
- `task-tracker-vikunja`: Локальный трекер задач Vikunja с веб-UI и REST API,
|
||||
интеграция с веб-интерфейсом почты через API-токен
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- нет -->
|
||||
|
||||
## Impact
|
||||
|
||||
- **Новые сервисы:** `/opt/hermes/radicale/`, `/opt/hermes/vikunja/`
|
||||
- **Порты:** 5232 (radicale), 3456 (vikunja) — новые
|
||||
- **Данные:** календари/задачи будут храниться локально
|
||||
- **Документация:** обновить README/STATUS (порты, логины, как синхронизировать)
|
||||
- **Риск:** Vikunja тянет Postgres (память/диск); Radicale — минимален
|
||||
|
||||
## Rollback
|
||||
|
||||
1. **Radicale:** `systemctl stop radicale` + удалить `/opt/hermes/radicale/`
|
||||
2. **Vikunja:** `docker compose -f /opt/hermes/vikunja/docker-compose.yml down -v`
|
||||
(удалить контейнеры и volume с данными) + убрать `/opt/hermes/vikunja/`
|
||||
3. Убрать упоминания из README/STATUS
|
||||
4. Вернуть порты в исходное состояние (оба сейчас свободны, конфликтов нет)
|
||||
@@ -0,0 +1,52 @@
|
||||
# Calendar CalDAV — Requirement Spec (Delta)
|
||||
|
||||
> New capability: `calendar-caldav`
|
||||
> Change: `local-calendar-tasks`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: REQ-CAL-001: Локальный CalDAV-сервер
|
||||
|
||||
**MUST** — на bigbox должен работать CalDAV-сервер (Radicale), доступный по
|
||||
HTTP на 127.0.0.1:5232, предоставляющий календари и задачи по стандарту CalDAV.
|
||||
|
||||
#### Scenario: Radicale отвечает
|
||||
|
||||
**GIVEN** Radicale запущен
|
||||
**WHEN** выполняется `curl -i -X PROPFIND http://127.0.0.1:5232/ -u login:pass`
|
||||
**THEN** ответ содержит HTTP 207 (Multi-Status) и список коллекций.
|
||||
|
||||
### Requirement: REQ-CAL-002: Календарь и задачи через CalDAV
|
||||
|
||||
**MUST** — Radicale должен поддерживать как календари (VEVENT), так и задачи
|
||||
(VTODO), чтобы Android-клиент (DAVx5) синхронизировал и календарь, и задачи.
|
||||
|
||||
#### Scenario: Создание события
|
||||
|
||||
**GIVEN** настроенный календарь пользователя
|
||||
**WHEN** клиент (DAVx5 на Android / curl) создаёт VEVENT через `PUT`
|
||||
**THEN** событие сохраняется в Radicale и доступно для чтения обратно.
|
||||
|
||||
### Requirement: REQ-CAL-003: Синхронизация с Android
|
||||
|
||||
**MUST** — сервер должен поддерживать стандарт CalDAV (RFC 4791) для
|
||||
двусторонней синхронизации с Android через DAVx5 (или аналог).
|
||||
|
||||
#### Scenario: Двусторонняя синхронизация
|
||||
|
||||
**GIVEN** DAVx5 настроен на Android с URL Radicale
|
||||
**WHEN** создаётся событие на телефоне
|
||||
**THEN** оно появляется на bigbox (Radicale) и наоборот (событие из Radicale
|
||||
доступно на телефоне).
|
||||
|
||||
### Requirement: REQ-CAL-004: Безопасность
|
||||
|
||||
**MUST** — Radicale доступен только локально (127.0.0.1) или через TLS-прокси
|
||||
(Caddy) с авторизацией; не должен быть открыт в интернет без TLS.
|
||||
|
||||
#### Scenario: Доступ снаружи
|
||||
|
||||
**GIVEN** Caddy reverse proxy настроен для Radicale
|
||||
**WHEN** Android подключается по https://<домен>/
|
||||
**THEN** соединение TLS + аутентификация работают; без Caddy доступ
|
||||
ограничен локальным интерфейсом.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Task Tracker Vikunja — Requirement Spec (Delta)
|
||||
|
||||
> New capability: `task-tracker-vikunja`
|
||||
> Change: `local-calendar-tasks`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: REQ-VIK-001: Локальный трекер задач
|
||||
|
||||
**MUST** — на bigbox должен работать трекер задач Vikunja, доступный по
|
||||
HTTP на 127.0.0.1:3456 (или через Caddy на поддомене), с веб-интерфейсом
|
||||
и REST API.
|
||||
|
||||
#### Scenario: Vikunja отвечает
|
||||
|
||||
**GIVEN** Vikunja запущен (docker compose)
|
||||
**WHEN** выполняется `curl http://127.0.0.1:3456/api/v1/info`
|
||||
**THEN** ответ содержит JSON с `version` и статус 200.
|
||||
|
||||
### Requirement: REQ-VIK-002: REST API для создания задач
|
||||
|
||||
**MUST** — API Vikunja позволяет создавать задачи программно (для кнопки
|
||||
«Создать задачу» из веб-интерфейса почты), используя API-токен.
|
||||
|
||||
#### Scenario: Создание задачи через API
|
||||
|
||||
**GIVEN** валидный API-токен Vikunja
|
||||
**WHEN** выполняется `POST /api/v1/projects/<id>/tasks` с телом
|
||||
`{"title": "Тестовая задача", "description": "из письма"}`
|
||||
**THEN** задача создаётся (HTTP 201) и видна в веб-UI.
|
||||
|
||||
### Requirement: REQ-VIK-003: Авторизация
|
||||
|
||||
**MUST** — Vikunja должен требовать аутентификацию (логин/пароль или
|
||||
API-токен) для всех операций, кроме анонимного info.
|
||||
|
||||
#### Scenario: Доступ без токена запрещён
|
||||
|
||||
**GIVEN** неавторизованный запрос
|
||||
**WHEN** выполняется `POST /api/v1/projects/<id>/tasks`
|
||||
**THEN** возвращается 401/403, задача не создаётся.
|
||||
|
||||
### Requirement: REQ-VIK-004: Хранение данных
|
||||
|
||||
**MUST** — данные Vikunja хранятся в PostgreSQL (docker volume), переживают
|
||||
перезапуск контейнера.
|
||||
|
||||
#### Scenario: Перезапуск Vikunja
|
||||
|
||||
**GIVEN** Vikunja с созданными задачами
|
||||
**WHEN** контейнер перезапускается (`docker compose restart`)
|
||||
**THEN** задачи и проекты сохраняются (volume не теряется).
|
||||
@@ -0,0 +1,26 @@
|
||||
# Tasks: Локальные сервисы календаря (Radicale) и задач (Vikunja)
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
### Radicale (CalDAV, docker :5232)
|
||||
- [ ] R1: Создать `/opt/hermes/radicale/docker-compose.yml` (образ radicale, порт 5232, volumes, auth)
|
||||
- [ ] R2: Создать коллекции: «Личный», «Рабочий», «Задачи» (VTODO) — через скрипт или DAVx5
|
||||
- [ ] R3: Проверить `curl -X PROPFIND http://127.0.0.1:5232/` → 207
|
||||
|
||||
### Vikunja (трекер, docker :3456)
|
||||
- [ ] V1: Создать `/opt/hermes/vikunja/docker-compose.yml` (postgres + vikunja, env, volumes)
|
||||
- [ ] V2: `docker compose up -d` → Vikunja отвечает на `http://127.0.0.1:3456/api/v1/info`
|
||||
- [ ] V3: Создать админ-пользователя, получить API-токен
|
||||
- [ ] V4: Тест: `POST /api/v1/projects/<id>/tasks` с токеном → 201
|
||||
|
||||
### Общее
|
||||
- [ ] O1: Caddy reverse proxy (cal.nixg.ru → 5232, tasks.nixg.ru → 3456) + TLS
|
||||
- [ ] O2: Проверить доступ с Android (DAVx5 для Radicale, Vikunja app/token для задач)
|
||||
- [ ] O3: Обновить README/STATUS (порты, логины, как синхронизировать)
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] V1: `curl -s -o /dev/null -w '%{http_code}' -X PROPFIND http://127.0.0.1:5232/ -u estorozhenko:...` → 207
|
||||
- [ ] V2: `curl -s http://127.0.0.1:3456/api/v1/info` → 200
|
||||
- [ ] V3: Задача через API → 201
|
||||
- [ ] V4: `cd /opt/hermes/openspec-lab && openspec validate local-calendar-tasks`
|
||||
Reference in New Issue
Block a user