diff --git a/.gitignore b/.gitignore
index 4c3e12a..a2771fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ server-data/
.DS_Store
Thumbs.db
release/
+pass
diff --git a/AGENTS.md b/AGENTS.md
index 16b05a0..1791128 100644
--- a/AGENTS.md
+++ b/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 и архитектуре.
diff --git a/README.md b/README.md
index 56b01d6..48b4069 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,17 @@
+
+
+# Verstak Sync Server
+
+### Optional self-hosted synchronization relay for Verstak vaults.
+
+**English** · [Русский](README.ru.md)
+
+[](https://github.com/mirivlad/verstak-sync-server/releases)
+
+[](LICENSE)
+
+
+
# Verstak Sync Server
Standalone sync server for Verstak2 platform.
diff --git a/README.ru.md b/README.ru.md
new file mode 100644
index 0000000..1891ba6
--- /dev/null
+++ b/README.ru.md
@@ -0,0 +1,158 @@
+
+
+# Verstak Sync Server
+
+### Собственный сервер синхронизации для vault-хранилищ Верстака.
+
+[English](README.md) · **Русский**
+
+[](https://github.com/mirivlad/verstak-sync-server/releases)
+
+[](LICENSE)
+
+
+
+> **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).
diff --git a/docs/superpowers/plans/2026-07-10-sync-tenant-isolation.md b/docs/superpowers/plans/2026-07-10-sync-tenant-isolation.md
deleted file mode 100644
index 960811a..0000000
--- a/docs/superpowers/plans/2026-07-10-sync-tenant-isolation.md
+++ /dev/null
@@ -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:` 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.
diff --git a/docs/superpowers/specs/2026-07-10-sync-tenant-isolation-design.md b/docs/superpowers/specs/2026-07-10-sync-tenant-isolation-design.md
deleted file mode 100644
index dc64f86..0000000
--- a/docs/superpowers/specs/2026-07-10-sync-tenant-isolation-design.md
+++ /dev/null
@@ -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:`. 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.