Документация: обновлён AGENTS.md (API), удалены superpowers-планы, добавлен README.ru.md с шапкой
This commit is contained in:
parent
d1e4068636
commit
f35ee2aaa2
|
|
@ -15,3 +15,4 @@ server-data/
|
|||
.DS_Store
|
||||
Thumbs.db
|
||||
release/
|
||||
pass
|
||||
|
|
|
|||
71
AGENTS.md
71
AGENTS.md
|
|
@ -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 и архитектуре.
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -1,3 +1,17 @@
|
|||
<div align="center">
|
||||
|
||||
# Verstak Sync Server
|
||||
|
||||
### Optional self-hosted synchronization relay for Verstak vaults.
|
||||
|
||||
**English** · [Русский](README.ru.md)
|
||||
|
||||
[](https://github.com/mirivlad/verstak-sync-server/releases)
|
||||

|
||||
[](LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
# Verstak Sync Server
|
||||
|
||||
Standalone sync server for Verstak2 platform.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
<div align="center">
|
||||
|
||||
# Verstak Sync Server
|
||||
|
||||
### Собственный сервер синхронизации для vault-хранилищ Верстака.
|
||||
|
||||
[English](README.md) · **Русский**
|
||||
|
||||
[](https://github.com/mirivlad/verstak-sync-server/releases)
|
||||

|
||||
[](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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
Loading…
Reference in New Issue