Документация: обновлён AGENTS.md (API), удалены superpowers-планы, добавлен README.ru.md с шапкой

This commit is contained in:
mirivlad 2026-07-18 18:06:44 +08:00
parent d1e4068636
commit f35ee2aaa2
6 changed files with 228 additions and 207 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ server-data/
.DS_Store
Thumbs.db
release/
pass

View File

@ -11,16 +11,49 @@
- Плагины явно указывают, какие данные можно синхронизировать.
- Sync — не источник правды, а дополнение.
## API
## API (текущий)
Desktop sync client:
```
POST /api/v1/pair — создание пары устройство-сервер
POST /api/v1/auth — аутентификация
GET /api/v1/devices — список устройств
POST /api/v1/sync — синхронизация операций
GET /api/v1/blob/:hash — скачать blob
PUT /api/v1/blob/:hash — загрузить blob
GET /api/v1/operations — получить операции с последнего sync point
POST /api/client/pair — pair device with username/password
POST /api/auth/test — validate username/password
GET /api/client/me — current authenticated device details
POST /api/client/revoke-current — revoke current device token
POST /api/client/revoke-device — revoke another device
POST /api/v1/sync/push — push local operations
POST /api/v1/sync/pull — pull operations since server sequence
POST /api/v1/blobs/ — store a blob, return SHA-256
GET /api/v1/blobs/{sha256} — download a stored blob
```
User API:
```
POST /api/v1/auth/register — register a user
GET /api/v1/auth/confirm — confirm email (GET shows form, POST confirms)
POST /api/v1/auth/login — user login
POST /api/v1/auth/forgot — request password reset
POST /api/v1/auth/reset — reset password
GET /api/v1/user/devices — list user devices
```
Operational:
```
GET /api/v1/health — server health and storage status
GET /livez — liveness probe
GET /readyz — readiness probe
```
Embedded web console:
```
GET / — public page
GET /login, /register, /forgot, /reset, /logout
GET /dashboard — user device management
GET /admin/login — admin console
GET /admin/... — admin sections (users, devices, vaults, storage, audit, SMTP, diagnostics)
```
## Структура
@ -31,14 +64,20 @@ verstak-sync-server/
cmd/
server/
internal/
api/
auth/
device/
vault/
blob/
conflict/
migrations/
...
server/
server.go
routes.go
handlers_api.go
handlers_auth.go
handlers_admin.go
schema.go
web/
...
go.mod
README.md
```
## Подробнее
См. [README.md](README.md) для полной документации по развёртыванию,
конфигурации, backup/restore и архитектуре.

View File

@ -1,3 +1,17 @@
<div align="center">
# Verstak Sync Server
### Optional self-hosted synchronization relay for Verstak vaults.
**English** · [Русский](README.ru.md)
[![Release](https://img.shields.io/github/v/release/mirivlad/verstak-sync-server?include_prereleases\&label=release)](https://github.com/mirivlad/verstak-sync-server/releases)
![Status](https://img.shields.io/badge/status-alpha-orange)
[![License](https://img.shields.io/github/license/mirivlad/verstak-sync-server)](LICENSE)
</div>
# Verstak Sync Server
Standalone sync server for Verstak2 platform.

158
README.ru.md Normal file
View File

@ -0,0 +1,158 @@
<div align="center">
# Verstak Sync Server
### Собственный сервер синхронизации для vault-хранилищ Верстака.
[English](README.md) · **Русский**
[![Релиз](https://img.shields.io/github/v/release/mirivlad/verstak-sync-server?include_prereleases\&label=release)](https://github.com/mirivlad/verstak-sync-server/releases)
![Статус](https://img.shields.io/badge/status-alpha-orange)
[![Лицензия](https://img.shields.io/github/license/mirivlad/verstak-sync-server)](LICENSE)
</div>
> **Alpha-версия.** Сервер синхронизации дополняет локальный рабочий процесс.
> Desktop-приложение остаётся основным источником данных.
## Обзор
Сервер синхронизации обеспечивает обмен данными между устройствами с Verstak Desktop:
- Регистрация устройств и аутентификация
- Упорядоченный журнал операций с серверными номерами последовательности
- Передача бинарных и больших файлов через Blob API
- Управление пользователями с подтверждением email
- Встроенная веб-консоль для администратора и пользователей
## Быстрый старт
```bash
# Сборка
./scripts/build.sh
# Запуск
./build/bin/verstak-sync-server --data ./server-data
# Первый запуск с администратором
printf '%s\n' 'выберите-длинный-пароль' > /tmp/verstak-admin-password
chmod 600 /tmp/verstak-admin-password
./build/bin/verstak-sync-server --admin-user admin --admin-pass-file /tmp/verstak-admin-password
```
## Установка
Соберите бинарник и запустите установочный скрипт:
```bash
./scripts/build.sh
sudo ./scripts/install.sh \
--bin ./build/bin/verstak-sync-server \
--listen 127.0.0.1:47732 \
--admin-user admin \
--admin-pass-file /root/verstak-admin-password
```
Скрипт создаёт системного пользователя, инициализирует каталог данных и
устанавливает systemd-сервис `verstak-server`.
Основные операции:
```bash
sudo systemctl status verstak-server
sudo journalctl -u verstak-server -f
curl http://127.0.0.1:47732/api/v1/health
```
## Развёртывание
В production сервер должен работать за HTTPS. Сам сервер слушает plain HTTP;
TLS терминируется в обратном прокси (nginx, Caddy).
Пример nginx:
```nginx
location / {
proxy_pass http://127.0.0.1:47732;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Всегда привязывайте сервер к loopback (`127.0.0.1`), если явно не нужен внешний
доступ.
## Резервное копирование и восстановление
Создание резервной копии (сервис должен быть остановлен):
```bash
sudo systemctl stop verstak-server
sudo tar --xattrs --acls -czf verstak-sync-backup-$(date +%Y%m%d-%H%M%S).tar.gz \
-C /var/lib verstak-sync-server
sudo systemctl start verstak-server
```
Восстановление:
```bash
sudo systemctl stop verstak-server
sudo tar --xattrs --acls -xzf verstak-sync-backup-ГГГГММДД-ЧЧММСС.tar.gz -C /var/lib
sudo chown -R verstak:verstak /var/lib/verstak-sync-server
sudo systemctl start verstak-server
```
## API
Desktop sync client:
| Метод | Назначение |
|-------|------------|
| `POST /api/client/pair` | Сопряжение устройства |
| `POST /api/auth/test` | Проверка учётных данных |
| `GET /api/client/me` | Информация о текущем устройстве |
| `POST /api/v1/sync/push` | Отправка локальных операций |
| `POST /api/v1/sync/pull` | Получение операций с сервера |
| `POST /api/v1/blobs/` | Загрузка бинарного файла |
| `GET /api/v1/blobs/{sha256}` | Скачивание бинарного файла |
User API:
| Метод | Назначение |
|-------|------------|
| `POST /api/v1/auth/register` | Регистрация |
| `POST /api/v1/auth/login` | Вход |
| `GET /api/v1/user/devices` | Список устройств |
Операционные:
| Метод | Назначение |
|-------|------------|
| `GET /api/v1/health` | Состояние сервера |
Административная веб-консоль доступна по `/admin/login`.
## Встроенная веб-консоль
Сервер включает встроенную веб-консоль без внешних зависимостей:
- Локализованные публичные страницы (System / English / Русский)
- Личный кабинет пользователя: устройства, подтверждение email
- Административная панель: пользователи, устройства, vaults, хранилище,
аудит, SMTP, диагностика
- Защита сессиями, CSRF-токенами и security-заголовками
## Архитектура
Сервер не является источником истины для данных. Desktop остаётся local-first.
Сервер передаёт операции и blob-файлы между устройствами, но не сливает
содержимое файлов, не разрешает конфликты и не создаёт замещающих имён.
Файлы не имеют сквозного шифрования в текущем milestone. Секреты, настройки
плагинов, задачи, журнал, активность и браузерный inbox не синхронизируются.
## Лицензия
Copyright © 2026 Verstak contributors. Распространяется на условиях
[GNU AGPLv3 или новее](LICENSE).

View File

@ -1,99 +0,0 @@
# Sync Tenant Isolation Implementation Plan
> **For agentic workers:** execute this plan task by task with focused tests
> before each implementation change.
**Goal:** prevent cross-user and cross-vault operation visibility while binding
the persisted source device to the authenticated token.
**Architecture:** the server derives user, device, and vault scope from the
bearer token. SQLite rows carry that scope; desktop sends the immutable vault
ID only while creating a pairing. Existing unscoped devices and operations are
migrated into a deterministic legacy scope.
**Tech stack:** Go, `database/sql`, SQLite, `net/http`, desktop Go sync client.
## Task 1: Establish server behaviour tests
**Files:**
- Modify: `internal/server/server_test.go`
- [x] Add helpers that create confirmed users and token-authenticated devices
with a specified vault ID.
- [x] Add a failing test where separate users push and pull from the same
vault ID; each pull must contain only its own operation and cursor.
- [x] Add a failing test where one user owns devices in two vaults; pulls must
remain vault-local.
- [x] Add a failing test that sends another device's ID in `push`; assert the
stored and returned operation uses the authenticated device ID.
- [x] Add a failing test for identical idempotency keys in different scopes.
- [x] Run: `go test ./internal/server -run 'TestSync.*Isolation|TestSyncPush'`
and confirm the new assertions fail for the intended missing behaviour.
## Task 2: Add idempotent SQLite scope migration
**Files:**
- Modify: `internal/server/schema.go`
- Modify: `internal/server/server.go`
- Test: `internal/server/server_test.go`
- [x] Define `vault_id` on new devices and `user_id`/`vault_id` on new
operations; define scoped tombstone and idempotency primary keys.
- [x] Add startup migration helpers that inspect columns, add compatible
columns, backfill owner IDs, assign `legacy:<user_id>` to old scopes, and
rebuild the two tables whose primary keys change.
- [x] Add a failing legacy-schema fixture test, then make it pass by opening
the database through `NewServer` and asserting its operation has the
expected owner and legacy scope.
- [x] Run: `go test ./internal/server -run 'Test.*Migration|TestSync.*'`.
## Task 3: Apply authenticated scope to sync handlers
**Files:**
- Modify: `internal/server/middleware.go`
- Modify: `internal/server/handlers_api.go`
- Test: `internal/server/server_test.go`
- [x] Extend authenticated device lookup to provide the effective vault scope;
missing user ownership must not authorize sync operations.
- [x] Require `vault_id` when creating a new client pairing and store it with
the device.
- [x] Make push use authenticated device/user/vault values for inserts,
conflicts, revisions, tombstones, and idempotency lookup/storage.
- [x] Make pull filter operations and its reported cursor by authenticated
user/vault.
- [x] Run the focused tests from Task 1 until green, then
`go test ./internal/server`.
## Task 4: Send the current vault ID while pairing
**Files:**
- Modify: `../verstak-desktop/internal/core/sync/client.go`
- Modify: `../verstak-desktop/internal/core/sync/client_test.go`
- Modify: `../verstak-desktop/internal/api/app.go`
- Modify: `../verstak-desktop/internal/api/app_test.go`
- [x] Add `vault_id` to the pair request and expose it in the pairing client
method without changing push/pull wire compatibility.
- [x] Read the open vault metadata in `syncConfigure`; reject configuration if
the vault ID is absent.
- [x] Add a failing client/API test that captures the pair request and asserts
the persistent vault ID is sent.
- [x] Rebind desktop sync state, cursor, and persisted device identity whenever
a vault is created, opened, or switched.
- [x] Hydrate missing legacy vault device IDs from the authenticated sync
server before a token-based sync can use a global fallback.
- [x] Run: `go test ./internal/core/sync ./internal/api`.
## Task 5: Document, verify, and publish
**Files:**
- Modify: `README.md`
- Modify: `docs/superpowers/specs/2026-07-10-sync-tenant-isolation-design.md`
- [x] Document that pairing is vault-bound and that sync cursors are scoped.
- [x] Run `gofmt` on all changed Go files.
- [x] Run `go test ./...` in both `verstak-sync-server` and
`verstak-desktop`, then `git diff --check` in both repositories.
- [x] Commit and push the sync-server and desktop changes as coordinated
security commits.

View File

@ -1,92 +0,0 @@
# Sync Tenant Isolation Design
**Status:** approved for implementation by the project owner on 2026-07-10.
## Goal
Make sync operations private to the authenticated user and vault, and make the
server, not the request body, the authority for the originating device.
## Scope
- Bind new desktop pairings to the current vault ID.
- Persist `user_id` and `vault_id` with every sync operation.
- Scope push conflict detection, pull cursors, tombstones, and idempotency
responses to that pair of identifiers.
- Ignore the legacy `device_id` field in a push body for authorization and
storage; retain it in the wire format for backward-compatible decoding.
- Upgrade existing SQLite databases without deleting data.
Blob ownership, API-key retirement, reset-token handling, HTML escaping, and
upload limits are separate security slices and are deliberately not included
in this change.
## Data model
`server_devices` gains a nullable `vault_id`. A device created through either
enrollment endpoint must have a non-empty vault ID that does not use the
reserved `legacy:` prefix. The authenticated device therefore identifies one
user and one vault.
`server_ops` gains `user_id` and `vault_id`. New writes always set both from
the authenticated device. Pull and conflict queries filter both fields.
`server_tombstones` is rebuilt with a composite key of
`(user_id, vault_id, entity_type, entity_id)`. `server_idempotency_keys` is
rebuilt with a composite key of `(user_id, vault_id, idempotency_key)`.
Existing devices without `vault_id` use the explicit effective scope
`legacy:<user_id>`. Existing operations inherit their device owner and that
legacy scope during startup migration. This preserves existing single-vault
accounts while preventing data from crossing account boundaries. New pairings
never use the legacy scope.
Desktop sync state, operation queues, cursors, and persisted device IDs are
vault-local. The desktop recreates its sync service whenever the active vault
is created, opened, or switched. When it opens a legacy vault state without a
stored device ID, it obtains the authenticated ID from `/api/client/me` before
syncing and persists it locally.
## API contract
`POST /api/client/pair` accepts a required `vault_id`. The desktop gets it
from `.verstak/vault.json` and sends it while pairing.
`POST /api/v1/sync/push` keeps accepting `device_id` for old clients, but the
server ignores it. The stored operation device ID is always the authenticated
device. A request whose token is not associated with a user and effective
vault returns a client error.
`POST /api/v1/sync/pull` returns only operations from the authenticated
user/vault scope. `server_sequence` is the highest sequence in that scope;
global sequence gaps are not exposed as the caller's cursor.
## Migration and failure handling
Startup migration is idempotent. It checks SQLite table columns before adding
new operation/device fields, backfills `user_id` from each operation's device,
and assigns the explicit legacy scope when an old device has no vault ID.
Tables whose primary key must change are rebuilt transactionally.
The prior global idempotency cache is intentionally discarded during migration:
it is only a replay cache and retaining it could replay one tenant's response
for another tenant.
If an operation cannot be associated with a user, it remains unscoped and is
not readable through sync APIs. The server must not guess an owner from a
request body.
## Verification
Focused server tests must prove:
1. two users cannot pull each other's operations;
2. two vaults of one user cannot pull each other's operations;
3. a caller cannot forge another device through the push body;
4. scoped idempotency does not replay another tenant's response;
5. a legacy SQLite database is upgraded with its existing operation retained
in the matching legacy scope.
6. switching the active desktop vault rebinds the sync queue, cursor, and
device identity to the new vault.
Desktop tests must prove that pairing sends the opened vault's persistent ID.