OpenSpec: разнести openspec по проектам

This commit is contained in:
2026-09-11 17:30:17 +00:00
parent 757f3413e9
commit f44bc27ce1
20 changed files with 1846 additions and 0 deletions
@@ -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`