diff --git a/.gitignore b/.gitignore
index 6c2b95da..962572ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ build/
.kotlin
### IntelliJ IDEA ###
+.idea/
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
@@ -102,8 +103,8 @@ ESP32/**/*.d
ESP32/**/*.a
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
-server-backup/archive/**
-!server-backup/archive/.gitkeep
+deploy/backup/archive/**
+!deploy/backup/archive/.gitkeep
# Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим
.claude/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index c2083d37..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 77cff8d8..00000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-shine-server-server
\ No newline at end of file
diff --git a/.idea/artifacts/server_jar.xml b/.idea/artifacts/server_jar.xml
deleted file mode 100644
index a890428b..00000000
--- a/.idea/artifacts/server_jar.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- $PROJECT_DIR$/out/artifacts/server_jar
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 6a8fc097..00000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index a19bb717..00000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 0faa7979..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index 350f9226..21aa62f4 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,7 +36,7 @@
- Модуль логически связан с SHiNE, но не должен автоматически подключаться к сборке или деплою основного сервера без отдельного решения.
- В Solana-модуле действуют локальные инструкции `shine-solana/shine/AGENTS.md`; при изменениях внутри модуля сначала читать их.
- В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги.
-- Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля с `deployServer`/`deployUI` основного проекта.
+- Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля со скриптами основного server/UI deploy из `deploy/scripts/`.
- Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в:
- `docs/Инициализация_Solana_регистрации/README.md`
- Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте.
@@ -86,18 +86,17 @@
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
## Deploy
-- Все документы и заметки по деплою хранить в папке `Deploy/`.
-- Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`).
-- Второй production-хост SHiNE: `player@193.8.215.70` (`server2.shineup.me`).
-- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
+- Все документы, инструкции, backup-правила и скрипты деплоя хранить в папке `deploy/`.
+- Подробные правила для агента по деплою находятся в `deploy/AGENTS.md`; перед любым deploy читать этот файл.
+- Production-серверы SHiNE: `shineup.me` и `server2.shineup.me`.
+- Тестовые/devnet серверы SHiNE: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
+- В deploy-документах и скриптах использовать домены, а не IP.
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
-- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя.
-- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `server2.shineup.me`.
-- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`.
-- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`.
-- Production server deploy: `./gradlew deployServerProduction`.
-- Production UI deploy: `./gradlew deployUIProduction`.
+- Любые изменения и любой деплой на production (`shineup.me` и `server2.shineup.me`) выполнять только после отдельного явного подтверждения пользователя.
+- Перед production deploy обязательно проверить/обновить бэкап в `deploy/backup/archive/`.
+- Если пользователь пишет просто `задеплой` без уточнения production/test, уточнить целевой контур; не выбирать production автоматически.
+- Deploy выполнять shell-скриптами из `deploy/scripts/`; Gradle deploy-задачи не использовать.
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
@@ -123,19 +122,6 @@
- `unknown_error`
- В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
-## Недопроверенные фичи (обязательно)
-- Папка для учёта недопроверенных фич: `docs/Pending_Features/`.
-- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `docs/Pending_Features/`.
-- Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_.md`.
-- Имена новых файлов и краткие описания фич по возможности писать на русском языке.
-- Внутри файла обязательно указывать:
- - краткое описание фичи;
- - что именно проверять;
- - ожидаемый результат;
- - статус (например: `pending`, `in_progress`, `done`).
-- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
-- В `docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
-
## Будущие фичи / TODO
- Папка для задач, сознательно отложенных на будущее: `TODO/`.
- Точка входа по планам: `TODO/README.md`.
diff --git a/Deploy/PRODUCTION_SERVERS.md b/Deploy/PRODUCTION_SERVERS.md
deleted file mode 100644
index a3177b51..00000000
--- a/Deploy/PRODUCTION_SERVERS.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# Production-серверы SHiNE
-
-## Короткий ответ
-
-По текущим данным репозитория у SHiNE описаны **два production-контура**:
-
-- `player@shineup.me`
-- домен `shineup.me`
-- IP `178.208.64.62`
-
-и
-
-- `player@193.8.215.70`
-- домен `server2.shineup.me`
-- IP `193.8.215.70`
-
-## 1. Основной production-хост
-
-- SSH: `player@shineup.me`
-- домен: `shineup.me`
-- IP: `178.208.64.62`
-- пользователь: `player`
-- базовый путь: `/home/player`
-
-Основные каталоги:
-
-- проект SHiNE: `/home/player/SHiNE`
-- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
-- UI: `/home/player/SHiNE/shine-ui`
-- данные: `/home/player/SHiNE/shine-server/data/`
-- логи: `/home/player/SHiNE/shine-server/logs/app.log`
-
-Сервисы:
-
-- `shine-server.service`
-- `caddy.service`
-
-Caddy:
-
-- активный конфиг: `/etc/caddy/Caddyfile`
-- UI root: `/home/player/SHiNE/shine-ui`
-- `/ws` проксируется на `127.0.0.1:7070`
-
-Deploy:
-
-- `./gradlew deployServerProduction`
-- `./gradlew deployUIProduction`
-
-Правило:
-
-- любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
-
-## 2. Второй production-сервер
-
-- SSH: `player@193.8.215.70`
-- домен: `server2.shineup.me`
-- IP: `193.8.215.70`
-- пользователь: `player`
-- базовый путь: `/home/player`
-
-Роль:
-
-- второй production-контур SHiNE;
-- использовать как production-сервер, несмотря на исторические имена deploy-задач
- `deployServerTest2` / `deployUITest2`.
-
-Основные каталоги:
-
-- проект SHiNE: `/home/player/SHiNE`
-- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
-- UI: `/home/player/SHiNE/shine-ui`
-- данные: `/home/player/SHiNE/shine-server/data/`
-- логи: `/home/player/SHiNE/shine-server/logs/app.log`
-
-## 3. Связанные публичные production-публикации на том же хосте
-
-На этом же production-хосте есть отдельная публикация для `shine_payments`:
-
-- каталог: `/home/player/sites/test-solana-tickets.shineup.me`
-- домены:
- - `https://test-solana-tickets.shineup.me`
- - `https://test-solana-tickets.shiningpeople.ru`
-
-Это не второй production-хост SHiNE, а отдельный сайт на том же сервере.
-
-## 4. Какие серверы не считать production
-
-Не production:
-
-- `t1.shineup.me`
-- `t2.shineup.me`
-- `t3.shineup.me`
-- `t4.shineup.me`
diff --git a/Deploy/README.md b/Deploy/README.md
deleted file mode 100644
index 0f730fcf..00000000
--- a/Deploy/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Deploy
-
-Подробности о том, где что задеплоено в SHiNE, нужно искать в папке `Deploy/`.
-
-Эта папка служит краткой картой окружений:
-
-- [TEST_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/TEST_SERVERS.md) — тестовые стенды;
-- [PRODUCTION_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/PRODUCTION_SERVERS.md) — production-контур и связанные публичные публикации.
-
-Ниже краткая сводка.
-
-## Основные публичные контуры
-
-- Production SHiNE:
- - `player@shineup.me`
- - домен `shineup.me`
- - IP `178.208.64.62`
-- Второй production SHiNE:
- - `player@193.8.215.70`
- - домен `server2.shineup.me`
- - IP `193.8.215.70`
-## Отдельный quad-devnet стенд
-
-На отдельном VPS `178.208.90.249` подняты 4 независимых test/devnet-инстанса:
-
-- `t1.shineup.me`
-- `t2.shineup.me`
-- `t3.shineup.me`
-- `t4.shineup.me`
-
-## Важно
-
-- Production-контура SHiNE сейчас два: `shineup.me` и `server2.shineup.me`.
-- `t1..t4.shineup.me` — это отдельные тестовые/devnet-контуры, не production.
-- Любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
diff --git a/Deploy/TEST_SERVERS.md b/Deploy/TEST_SERVERS.md
deleted file mode 100644
index eb214bb2..00000000
--- a/Deploy/TEST_SERVERS.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# Тестовые серверы SHiNE
-
-Этот файл описывает тестовые стенды, которые сейчас фигурируют в проекте.
-
-## 1. Исторический `test2`, теперь второй production-сервер
-
-- SSH: `player@193.8.215.70`
-- Домен: `server2.shineup.me`
-- IP: `193.8.215.70`
-- Назначение: второй production-контур SHiNE
-
-Структура:
-
-- каталог SHiNE: `/home/player/SHiNE`
-- сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
-- UI: `/home/player/SHiNE/shine-ui`
-- данные: `/home/player/SHiNE/shine-server/data/`
-- логи: `/home/player/SHiNE/shine-server/logs/app.log`
-
-Сервисы:
-
-- `shine-server.service`
-- `caddy.service`
-
-Deploy:
-
-- `./gradlew deployServer`
-- `./gradlew deployServerTest2`
-- `./gradlew deployUI`
-- `./gradlew deployUITest2`
-
-Примечания:
-
-- этот хост больше не считать test-контуром;
-- исторические имена deploy-задач `deployServerTest2` / `deployUITest2` сохранены, но сам хост считать production;
-- задача `deployUITest2` по умолчанию выкладывает UI на `server2.shineup.me`, а не на `t2.shineup.me`;
-- при описании окружений перечислять его как второй production-сервер.
-
-## 2. Отдельный quad-devnet стенд `t1..t4`
-
-- VPS: `178.208.90.249`
-- пользователь: `player`
-- назначение: 4 независимых SHiNE-инстанса на Solana `devnet`
-
-Домены и логины:
-
-- `server_t1` -> `https://t1.shineup.me`
-- `server_t2` -> `https://t2.shineup.me`
-- `server_t3` -> `https://t3.shineup.me`
-- `server_t4` -> `https://t4.shineup.me`
-
-Каталоги:
-
-- `/home/player/t1/server`
-- `/home/player/t1/UI`
-- `/home/player/t2/server`
-- `/home/player/t2/UI`
-- `/home/player/t3/server`
-- `/home/player/t3/UI`
-- `/home/player/t4/server`
-- `/home/player/t4/UI`
-
-Подробная памятка на самом VPS:
-
-- `/home/player/Agents.md`
-
-Порты и systemd:
-
-- `t1` -> `7101` -> `shine-t1.service`
-- `t2` -> `7102` -> `shine-t2.service`
-- `t3` -> `7103` -> `shine-t3.service`
-- `t4` -> `7104` -> `shine-t4.service`
-
-Что важно по конфигу каждого инстанса:
-
-- отдельный `/home/player/tX/server/application.properties`
-- `server.port=710X`
-- `server.SHiNE.login=server_tX`
-- `db.path=data/shine.sqlite`
-- `solana.cluster=devnet`
-- `solana.rpcUrl=https://api.devnet.solana.com`
-- `server.ui.indexPath=/home/player/tX/UI/index.html`
-- `server.info.url=https://tX.shineup.me`
-
-UI каждого инстанса:
-
-- живёт в отдельной копии `shine-UI`;
-- использует свой `js/deploy-config.js`;
-- по умолчанию смотрит именно на свой `tX.shineup.me`.
-
-Caddy на стенде:
-
-- конфиг: `/etc/caddy/Caddyfile`
-- статика: `/home/player/tX/UI`
-- `/ws` проксируется на `127.0.0.1:710X`
-
-Operational-нюанс:
-
-- при одновременных рестартах возможны `HTTP 429` от `api.devnet.solana.com`;
-- поэтому сервисы `shine-t1..shine-t4` лучше перезапускать по одному, с паузой.
-
-## 3. Что проверять первым делом
-
-Для любого test-контура полезны такие быстрые проверки:
-
-```bash
-curl -I https://server2.shineup.me
-curl -I https://t1.shineup.me
-curl -I https://t2.shineup.me
-curl -I https://t3.shineup.me
-curl -I https://t4.shineup.me
-```
-
-Для quad-devnet VPS:
-
-```bash
-sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
-```
-
-Для второго production-контура:
-
-```bash
-sudo systemctl --no-pager --full status shine-server caddy
-```
diff --git a/SHiNE-server/AGENTS.md b/SHiNE-server/AGENTS.md
index 357a5731..573f3bea 100644
--- a/SHiNE-server/AGENTS.md
+++ b/SHiNE-server/AGENTS.md
@@ -54,21 +54,11 @@ shine-UI/server-ui.html
## Деплой
-```
-./gradlew deployServer
-./gradlew deployUI
-```
-
-Default deploy по умолчанию идёт на `server2.shineup.me` (`player@193.8.215.70`).
-
-Production deploy:
-
-```
-./gradlew deployServerProduction
-./gradlew deployUIProduction
-```
-
-Любые изменения на `shineup.me` делать только после отдельного явного подтверждения пользователя.
+- Основные инструкции по деплою находятся в `../deploy/AGENTS.md`.
+- Deploy выполнять shell-скриптами из `../deploy/scripts/`.
+- Gradle deploy-задачи не использовать: Gradle остаётся для сборки и локального запуска.
+- Любые изменения на production (`shineup.me`, `server2.shineup.me`) делать только после отдельного явного подтверждения пользователя.
+- Перед production deploy обязательно обновить/проверить backup в `deploy/backup/archive/`.
Логи на проде:
- `/home/player/SHiNE/shine-server/logs/app.log`
diff --git a/SHiNE-server/src/main/resources/application.properties b/SHiNE-server/src/main/resources/application.properties
index 307739f3..b910adbd 100644
--- a/SHiNE-server/src/main/resources/application.properties
+++ b/SHiNE-server/src/main/resources/application.properties
@@ -44,7 +44,7 @@ webpush.vapid.subject=mailto:admin@shine.local
# Тогда сервер будет выдавать временный username/password (TTL).
# ------------------------------------------------------------
call.ice.stun.urls=stun:stun.l.google.com:19302
-call.ice.turn.urls=turn:185.229.109.118:3478?transport=udp,turn:185.229.109.118:3478?transport=tcp
+call.ice.turn.urls=turn:turn1.shineup.me:3478?transport=udp,turn:turn1.shineup.me:3478?transport=tcp
call.ice.turn.ttlSec=600
call.ice.turn.userPrefix=shine
call.ice.turn.sharedSecret=
@@ -58,12 +58,24 @@ call.ice.turn.password=
# Каждый блок описывает один TURN-узел. Новые узлы добавляются по индексу.
# Приоритет авторизации на узел: sharedSecret -> статические username/password.
# ------------------------------------------------------------
-call.ice.turn.servers.1.id=shineup-main-185
-call.ice.turn.servers.1.urls=turn:185.229.109.118:3478?transport=udp,turn:185.229.109.118:3478?transport=tcp
-call.ice.turn.servers.1.sharedSecret=def6d444734d380d2f67a9d345b1debf985eaba0973c343e392c060d97c30106
+call.ice.turn.servers.1.id=turn1
+call.ice.turn.servers.1.urls=turn:turn1.shineup.me:3478?transport=udp,turn:turn1.shineup.me:3478?transport=tcp
+call.ice.turn.servers.1.sharedSecret=
call.ice.turn.servers.1.username=
call.ice.turn.servers.1.password=
+call.ice.turn.servers.2.id=turn2
+call.ice.turn.servers.2.urls=turn:turn2.shineup.me:3478?transport=udp,turn:turn2.shineup.me:3478?transport=tcp
+call.ice.turn.servers.2.sharedSecret=
+call.ice.turn.servers.2.username=
+call.ice.turn.servers.2.password=
+
+call.ice.turn.servers.3.id=turn3
+call.ice.turn.servers.3.urls=turn:turn3.shineup.me:3478?transport=udp,turn:turn3.shineup.me:3478?transport=tcp
+call.ice.turn.servers.3.sharedSecret=
+call.ice.turn.servers.3.username=
+call.ice.turn.servers.3.password=
+
# ------------------------------------------------------------
# Временные debug HTTP API для тестирования соединений
# true - endpoint'ы /debug/ws/* включены (только при наличии .debug-token)
diff --git a/TODO/2026-06-26_1800_корректное_завершение_за_30с.md b/TODO/2026-06-26_1800_корректное_завершение_за_30с.md
index 8a786128..c525498b 100644
--- a/TODO/2026-06-26_1800_корректное_завершение_за_30с.md
+++ b/TODO/2026-06-26_1800_корректное_завершение_за_30с.md
@@ -35,5 +35,5 @@
## Какие документы потом обновить
-- `Deploy/`;
+- `deploy/`;
- `docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
diff --git a/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md b/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md
index 4d845621..cc980c7e 100644
--- a/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md
+++ b/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md
@@ -56,11 +56,9 @@
- Код формирования репоста в `auth-service.js` не удалён: его можно будет использовать как основу при возвращении к задаче.
- Код отображения target-полей и перехода к оригиналу не удалён: он нужен для будущей проверки и возможной совместимости с уже созданными тестовыми блоками.
-## Почему это не лежит в Pending_Features
+## Почему это лежит в TODO
-`docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки.
-
-Репосты сейчас не подходят под этот статус: они не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому старый pending-файл удалён, а задача перенесена сюда как будущая.
+Репосты сейчас не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому задача остаётся в TODO как будущая.
## Что сделать при возврате к реализации
@@ -86,7 +84,7 @@
- `docs/Blockchain/CHANGELOG.md`;
- `docs/API/04_Add_Block_to_Blockchain_API.md`;
- документы API чтения каналов/тредов, если изменятся поля ответа.
-9. После реализации перенести задачу из `TODO/` в `docs/Pending_Features/` как фичу, требующую ручной проверки.
+9. После реализации отдельно согласовать ручную проверку пользовательского сценария.
## Минимальный чек-лист ручной проверки в будущем
diff --git a/TODO/medium/2026-05-25_1106_shine_balance_wallet.md b/TODO/medium/2026-05-25_1106_shine_balance_wallet.md
index 05ead4ad..cb735c8b 100644
--- a/TODO/medium/2026-05-25_1106_shine_balance_wallet.md
+++ b/TODO/medium/2026-05-25_1106_shine_balance_wallet.md
@@ -50,7 +50,7 @@
- `docs/Blockchain/`, если появятся или изменятся блоки баланса.
- `docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
- `docs/API/`, если меняется серверный API.
-- `docs/Pending_Features/` - добавить файл ручной проверки после реализации.
+- после реализации отдельно согласовать ручную проверку.
- Документацию Solana-регистрации, если баланс будет связан с Solana-модулем.
## Минимальная проверка в будущем
diff --git a/TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md b/TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md
index b57d06f9..12d561b6 100644
--- a/TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md
+++ b/TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md
@@ -37,9 +37,8 @@
## Что обновить при возврате
-- `docs/Pending_Features/README.md`
+- после реализации отдельно согласовать ручную проверку
- `shine-UI/js/pages/connect-device-view.js`
- `shine-UI/js/pages/device-qr-view.js`
- `shine-UI/js/services/qr-key-transfer-service.js`
- документацию по ключам, если формат переноса меняется
-
diff --git a/TODO/near/2026-05-25_1106_wallet_topup_solana_arweave.md b/TODO/near/2026-05-25_1106_wallet_topup_solana_arweave.md
index 2d228ac6..4de664eb 100644
--- a/TODO/near/2026-05-25_1106_wallet_topup_solana_arweave.md
+++ b/TODO/near/2026-05-25_1106_wallet_topup_solana_arweave.md
@@ -58,7 +58,7 @@
## Документы, которые обновить при реализации
- Документацию UI/кошельков, если такая есть.
-- `docs/Pending_Features/` - добавить файл ручной проверки после реализации.
+- после реализации отдельно согласовать ручную проверку.
- `docs/API/`, только если появится новый серверный API или логирование.
## Минимальная проверка
diff --git a/TODO/Децентрализация/запись_блокчейнов_в_arweave.md b/TODO/Децентрализация/запись_блокчейнов_в_arweave.md
index b0bf088f..1d702707 100644
--- a/TODO/Децентрализация/запись_блокчейнов_в_arweave.md
+++ b/TODO/Децентрализация/запись_блокчейнов_в_arweave.md
@@ -22,7 +22,7 @@
- `docs/Blockchain/README.md`;
- `docs/Blockchain/CHANGELOG.md`;
-- документы deploy/секретов в `Deploy/`, если появятся новые параметры.
+- документы deploy/секретов в `deploy/`, если появятся новые параметры.
## Статус
diff --git a/VERSION.properties b/VERSION.properties
index aac20b46..4c15330c 100644
--- a/VERSION.properties
+++ b/VERSION.properties
@@ -1,2 +1,2 @@
-client.version=1.2.330
-server.version=1.2.302
+client.version=1.2.331
+server.version=1.2.303
diff --git a/build.gradle b/build.gradle
index 3c786e10..8014732a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
plugins {
id 'java'
id 'application'
-проверь ещё id 'com.github.johnrengelman.shadow' version '8.1.1'
+ id 'com.github.johnrengelman.shadow' version '8.1.1'
}
def appVersionProps = new Properties()
@@ -185,60 +185,6 @@ tasks.named('build') {
finalizedBy tasks.named('integrationTest')
}
-tasks.register('deployServerProduction', JavaExec) {
- group = "!!deployment"
- description = "Production deploy: build → upload to shineup.me → restart service (только после явного подтверждения)"
-
- classpath = sourceSets.test.runtimeClasspath
- mainClass = "test.it.IT_DeployRestartNoCleanNoTestsMain"
- workingDir = file('SHiNE-server')
-
- dependsOn shadowJar
- systemProperty "it.remoteHost", System.getProperty("it.remoteHost", "shineup.me")
- systemProperty "it.remoteUser", System.getProperty("it.remoteUser", "player")
- systemProperty "it.remoteDir", System.getProperty("it.remoteDir", "/home/player/SHiNE/shine-server")
- systemProperty "it.service", System.getProperty("it.service", "shine-server")
- systemProperty "it.localJar", System.getProperty("it.localJar", "build/libs/shine-server.jar")
-
- dependsOn testClasses
-}
-
-tasks.register('deployUIProduction', Exec) {
- group = "!!deployment"
- description = "Production UI deploy: shineup.me (только после явного подтверждения)"
- workingDir = rootDir
- commandLine 'bash', file('deploy_shine-ui_production_shineupme.sh').absolutePath
-}
-
-tasks.register('deployServer', Exec) {
- group = "!!deployment"
- description = "Default deploy server: server2.shineup.me"
- dependsOn shadowJar
- workingDir = rootDir
- environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath
- commandLine 'bash', file('deploy_shine-server_server2shineupme.sh').absolutePath
-}
-
-tasks.register('deployUI', Exec) {
- group = "!!deployment"
- description = "Default deploy UI: server2.shineup.me"
- workingDir = rootDir
- commandLine 'bash', file('deploy_shine-ui_production_server2shineupme.sh').absolutePath
-}
-
-tasks.register('deployServerTest2') {
- group = "!!deployment"
- description = "Явный алиас второго production deploy server: server2.shineup.me"
- dependsOn tasks.named('deployServer')
-}
-
-tasks.register('deployUITest2') {
- group = "!!deployment"
- description = "Явный алиас второго production deploy UI: server2.shineup.me"
- dependsOn tasks.named('deployUI')
-}
-
-
tasks.register('startLocal', Exec) {
group = "!!run"
description = "Builds server, starts local WS server and local HTTP UI for end-to-end local testing"
diff --git a/deploy/AGENTS.md b/deploy/AGENTS.md
new file mode 100644
index 00000000..f83d1ee0
--- /dev/null
+++ b/deploy/AGENTS.md
@@ -0,0 +1,57 @@
+# AGENTS для deploy
+
+## Главное
+
+- Все вопросы деплоя SHiNE решать через эту папку `deploy/`.
+- Скрипты лежат в `deploy/scripts/`.
+- Production-серверы: `shineup.me` и `server2.shineup.me`.
+- Test/devnet серверы: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
+- TURN-серверы: `turn1.shineup.me`, `turn2.shineup.me`, `turn3.shineup.me`.
+- В deploy-документах и скриптах использовать домены, а не IP.
+
+## Production safety
+
+- Любой deploy на `shineup.me` или `server2.shineup.me` выполнять только после отдельного явного подтверждения пользователя.
+- Перед production deploy обязательно проверить, что свежий бэкап лежит в `deploy/backup/archive/`.
+- Production wrappers требуют наличие `deploy/backup/archive//MANIFEST.txt`.
+- Секреты, `.env`, JWK, TURN shared secrets и приватные ключи не коммитить.
+
+## Как деплоить
+
+Сервер:
+
+```bash
+bash deploy/scripts/_server.sh
+```
+
+UI:
+
+```bash
+bash deploy/scripts/_ui.sh
+```
+
+Для настоящего `t2.shineup.me` использовать:
+
+```bash
+bash deploy/scripts/test_t2_server.sh
+bash deploy/scripts/test_t2_ui.sh
+```
+
+Не путать `server2.shineup.me` и `t2.shineup.me`.
+
+## Документы
+
+- `README.md` — карта deploy-папки.
+- `PRODUCTION_SERVERS.md` — production.
+- `TEST_SERVERS.md` — test/devnet.
+- `TURN_SERVERS.md` — TURN.
+- `CONFIGURE_TURN_IN_SHINE.md` — подключение TURN к SHiNE backend.
+- `SETUP_SERVER_FROM_ZERO.md` — сервер + Caddy + UI с нуля.
+- `SETUP_TURN_SERVER.md` — TURN с нуля.
+- `backup/README.md` — backup.
+
+## Gradle
+
+- Gradle deploy-задачи удалены.
+- Для сборки jar общий server deploy script вызывает `./gradlew shadowJar`.
+- Для локального запуска можно использовать `./gradlew startLocal`.
diff --git a/docs/deploy/agent-bot-coder-local-systemd.md b/deploy/AGENT_BOT_CODER_LOCAL_SYSTEMD.md
similarity index 99%
rename from docs/deploy/agent-bot-coder-local-systemd.md
rename to deploy/AGENT_BOT_CODER_LOCAL_SYSTEMD.md
index bc446945..d458841c 100644
--- a/docs/deploy/agent-bot-coder-local-systemd.md
+++ b/deploy/AGENT_BOT_CODER_LOCAL_SYSTEMD.md
@@ -1,17 +1,20 @@
# Локальный деплой SHiNE-agent-bot-coder (systemd, пользователь ai)
## Где находится сервис
+
- Папка сервиса: `SHiNE-agent-bot-coder/`
- Systemd unit: `SHiNE-agent-bot-coder/scripts/systemd/shine-agent-bot-coder.service`
- Скрипт установки: `SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh`
## Предусловия
+
1. Заполнен `.env` на основе `.env.example`.
2. Доступен рабочий Codex CLI:
- `/home/ai/.cache/JetBrains/IntelliJIdea2026.1/aia/codex/bin/codex-x86_64-unknown-linux-musl`
3. На машине установлен `systemd --user`.
## Установка
+
Из корня репозитория:
```bash
@@ -19,18 +22,21 @@ bash SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh
```
Скрипт:
+
1. проверяет наличие `python3`;
2. копирует unit в `~/.config/systemd/user/`;
3. делает `systemctl --user daemon-reload`;
4. включает автозапуск и стартует сервис.
## Проверка
+
```bash
systemctl --user status shine-agent-bot-coder --no-pager
journalctl --user -u shine-agent-bot-coder -f
```
## Перезапуск после изменений
+
```bash
systemctl --user restart shine-agent-bot-coder
```
diff --git a/deploy/CONFIGURE_TURN_IN_SHINE.md b/deploy/CONFIGURE_TURN_IN_SHINE.md
new file mode 100644
index 00000000..d35fb917
--- /dev/null
+++ b/deploy/CONFIGURE_TURN_IN_SHINE.md
@@ -0,0 +1,60 @@
+# Подключение TURN к SHiNE-серверу
+
+Клиент звонков запрашивает ICE-конфиг у backend через WS-операцию `GetCallIceConfig` и использует её для `RTCPeerConnection`.
+
+## Репозиторный конфиг
+
+В `SHiNE-server/src/main/resources/application.properties` можно хранить только публичные домены и пустые placeholders.
+
+Пример:
+
+```properties
+call.ice.stun.urls=stun:stun.l.google.com:19302
+call.ice.turn.urls=turn:turn1.shineup.me:3478?transport=udp,turn:turn1.shineup.me:3478?transport=tcp
+call.ice.turn.ttlSec=600
+call.ice.turn.userPrefix=shine
+call.ice.turn.sharedSecret=
+
+call.ice.turn.servers.1.id=turn1
+call.ice.turn.servers.1.urls=turn:turn1.shineup.me:3478?transport=udp,turn:turn1.shineup.me:3478?transport=tcp
+call.ice.turn.servers.1.sharedSecret=
+```
+
+## Production override
+
+Реальные `sharedSecret`, статические логины/пароли и другие секреты задавать только на сервере через внешний `application.properties` или override-конфиг. В git их не хранить.
+
+Рекомендуемый режим:
+
+- на coturn включить `use-auth-secret`;
+- в coturn задать `static-auth-secret=`;
+- в SHiNE-сервере задать такой же `call.ice.turn.sharedSecret` или `call.ice.turn.servers.N.sharedSecret`;
+- сервер будет выдавать короткоживущие `turnUsername` / `turnPassword` с TTL.
+
+Fallback-режим:
+
+```properties
+call.ice.turn.sharedSecret=
+call.ice.turn.username=turn_user
+call.ice.turn.password=turn_password
+```
+
+Fallback тоже не должен хранить реальные credentials в git.
+
+## Деплой после изменения TURN-настроек
+
+Для production сначала обновить бэкап в `deploy/backup/archive/`, затем выполнить нужный server deploy script:
+
+```bash
+bash deploy/scripts/production_shineupme_server.sh
+```
+
+Если менялись только серверные TURN-настройки во внешнем override-конфиге, достаточно перезапустить соответствующий `shine-server.service`.
+
+## Проверка звонка
+
+1. Авторизоваться двумя клиентами.
+2. Запустить звонок.
+3. Проверить, что звонок устанавливается в сети, где прямой P2P затруднён.
+4. В диагностике звонков смотреть `CallDeliveryReport`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
+5. Если TURN недоступен, клиент должен откатиться к STUN-конфигу по умолчанию.
diff --git a/deploy/PRODUCTION_SERVERS.md b/deploy/PRODUCTION_SERVERS.md
new file mode 100644
index 00000000..0d9b82d9
--- /dev/null
+++ b/deploy/PRODUCTION_SERVERS.md
@@ -0,0 +1,59 @@
+# Production-серверы SHiNE
+
+Production-контуров два. В документах и скриптах использовать домены, а не IP: физический VPS можно заменить без изменения deploy-логики.
+
+## Основной production
+
+- Домен: `shineup.me`
+- SSH: `player@shineup.me`
+- Логин сервера SHiNE: `shineupme`
+- Базовый каталог: `/home/player/SHiNE`
+- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
+- UI: `/home/player/SHiNE/shine-ui`
+- Данные: `/home/player/SHiNE/shine-server/data/`
+- Логи: `/home/player/SHiNE/shine-server/logs/app.log`
+- systemd service: `shine-server.service`
+- Caddy site: `shineup.me`
+- WebSocket: `/ws` -> `127.0.0.1:7070`
+- Solana cluster: `mainnet-beta`
+
+Deploy:
+
+```bash
+bash deploy/scripts/production_shineupme_server.sh
+bash deploy/scripts/production_shineupme_ui.sh
+```
+
+## Второй production
+
+- Домен: `server2.shineup.me`
+- SSH: `player@server2.shineup.me`
+- Логин сервера SHiNE: `server2shineupme`
+- Базовый каталог: `/home/player/SHiNE`
+- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
+- UI: `/home/player/SHiNE/shine-ui`
+- Данные: `/home/player/SHiNE/shine-server/data/`
+- Логи: `/home/player/SHiNE/shine-server/logs/app.log`
+- systemd service: `shine-server.service`
+- Caddy site: `server2.shineup.me`
+- WebSocket: `/ws` -> `127.0.0.1:7070`
+- Solana cluster: `mainnet-beta`
+
+Deploy:
+
+```bash
+bash deploy/scripts/production_server2_server.sh
+bash deploy/scripts/production_server2_ui.sh
+```
+
+## Обязательное правило backup
+
+Перед любым production deploy нужно обновить локальный бэкап в `deploy/backup/archive/`.
+
+Production wrappers проверяют наличие `MANIFEST.txt` в `deploy/backup/archive//`. Если свежий бэкап не скопирован, deploy должен быть остановлен.
+
+## Запрещено
+
+- Не использовать IP как основной target deploy.
+- Не возвращать старые deploy-алиасы с названием `test2`.
+- Не считать `t2.shineup.me` вторым production: это test/devnet.
diff --git a/deploy/README.md b/deploy/README.md
new file mode 100644
index 00000000..78bd95ba
--- /dev/null
+++ b/deploy/README.md
@@ -0,0 +1,78 @@
+# Deploy SHiNE
+
+Эта папка — единая точка входа по деплою SHiNE.
+
+## Правило
+
+- Все deploy-документы, deploy-скрипты и backup-инструкции держать здесь.
+- Deploy-привязки задавать через домены, а не через IP.
+- Production-серверы менять только после отдельного явного подтверждения пользователя.
+- Production deploy выполнять только после обновления локального бэкапа в `deploy/backup/archive/`.
+
+## Контуры
+
+- Основной production: `shineup.me`.
+- Второй production: `server2.shineup.me`.
+- Test/devnet стенд: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
+
+## Основные документы
+
+- `PRODUCTION_SERVERS.md` — production-контуры.
+- `TEST_SERVERS.md` — test/devnet-контуры.
+- `TURN_SERVERS.md` — TURN-серверы.
+- `CONFIGURE_TURN_IN_SHINE.md` — как подключить TURN к SHiNE backend.
+- `SETUP_SERVER_FROM_ZERO.md` — настройка SHiNE-сервера и UI с нуля.
+- `SETUP_TURN_SERVER.md` — настройка TURN через Caddy/DNS/TLS.
+- `AGENT_BOT_CODER_LOCAL_SYSTEMD.md` — локальный systemd-deploy Telegram-бота агента-кодера.
+- `AGENTS.md` — правила для агента при деплое.
+
+## Скрипты
+
+Общие скрипты:
+
+- `scripts/deploy_server.sh` — обновить существующий серверный jar и перезапустить systemd service.
+- `scripts/deploy_ui.sh` — обновить существующий UI, проверить Caddy root и подставить `deploy-config.js`.
+
+Production wrappers:
+
+- `scripts/production_shineupme_server.sh`
+- `scripts/production_shineupme_ui.sh`
+- `scripts/production_server2_server.sh`
+- `scripts/production_server2_ui.sh`
+
+Test/devnet wrappers:
+
+- `scripts/test_t1_server.sh`
+- `scripts/test_t1_ui.sh`
+- `scripts/test_t2_server.sh`
+- `scripts/test_t2_ui.sh`
+- `scripts/test_t3_server.sh`
+- `scripts/test_t3_ui.sh`
+- `scripts/test_t4_server.sh`
+- `scripts/test_t4_ui.sh`
+
+## Примеры
+
+UI на настоящий тестовый `t2.shineup.me`:
+
+```bash
+bash deploy/scripts/test_t2_ui.sh
+```
+
+Сервер на настоящий тестовый `t2.shineup.me`:
+
+```bash
+bash deploy/scripts/test_t2_server.sh
+```
+
+Production UI на `server2.shineup.me`:
+
+```bash
+bash deploy/scripts/production_server2_ui.sh
+```
+
+Production server на `shineup.me`:
+
+```bash
+bash deploy/scripts/production_shineupme_server.sh
+```
diff --git a/deploy/SETUP_SERVER_FROM_ZERO.md b/deploy/SETUP_SERVER_FROM_ZERO.md
new file mode 100644
index 00000000..de4b6f6d
--- /dev/null
+++ b/deploy/SETUP_SERVER_FROM_ZERO.md
@@ -0,0 +1,113 @@
+# Настройка SHiNE-сервера с нуля
+
+Инструкция описывает базовую подготовку существующего Linux/VPS-хоста под SHiNE server + UI. Привязка должна идти к домену, а не к IP.
+
+## 1. DNS
+
+Создать DNS-запись домена:
+
+- production: `shineup.me` или `server2.shineup.me`;
+- test/devnet: `t1.shineup.me` ... `t4.shineup.me`.
+
+После смены физического сервера достаточно обновить DNS.
+
+## 2. Пользователь и пакеты
+
+```bash
+sudo apt update
+sudo apt install -y openjdk-17-jre-headless rsync caddy
+sudo useradd -m -s /bin/bash player || true
+```
+
+## 3. Каталоги
+
+Production:
+
+```bash
+mkdir -p /home/player/SHiNE/shine-server/data
+mkdir -p /home/player/SHiNE/shine-server/logs
+mkdir -p /home/player/SHiNE/shine-ui
+```
+
+Test/devnet:
+
+```bash
+mkdir -p /home/player/tX/server/data
+mkdir -p /home/player/tX/server/logs
+mkdir -p /home/player/tX/UI
+```
+
+## 4. application.properties
+
+Каждый сервер должен иметь локальный внешний конфиг в рабочей директории сервиса.
+
+Production пример:
+
+```properties
+server.port=7070
+server.SHiNE.login=shineupme
+db.path=data/shine.sqlite
+server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html
+server.info.url=https://shineup.me
+server.info.origin=production
+solana.cluster=mainnet-beta
+```
+
+Test/devnet пример:
+
+```properties
+server.port=7102
+server.SHiNE.login=server_t2
+db.path=data/shine.sqlite
+server.ui.indexPath=/home/player/t2/UI/index.html
+server.info.url=https://t2.shineup.me
+server.info.origin=devnet
+solana.cluster=devnet
+solana.rpcUrl=https://api.devnet.solana.com
+```
+
+## 5. Caddy
+
+Минимальный site block:
+
+```caddyfile
+t2.shineup.me {
+ encode zstd gzip
+
+ @ws path /ws /ws/*
+ handle @ws {
+ reverse_proxy 127.0.0.1:7102
+ }
+
+ handle {
+ root * /home/player/t2/UI
+ try_files {path} /index.html
+ file_server
+ header -Etag
+ header {
+ Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
+ Pragma "no-cache"
+ Expires "0"
+ }
+ }
+}
+```
+
+Проверка:
+
+```bash
+sudo caddy validate --config /etc/caddy/Caddyfile
+sudo systemctl restart caddy
+curl -I https://t2.shineup.me
+```
+
+## 6. Первый deploy
+
+Из репозитория:
+
+```bash
+bash deploy/scripts/test_t2_server.sh
+bash deploy/scripts/test_t2_ui.sh
+```
+
+Для production перед этими командами обязательно обновить `deploy/backup/archive/`.
diff --git a/deploy/SETUP_TURN_SERVER.md b/deploy/SETUP_TURN_SERVER.md
new file mode 100644
index 00000000..cb2d291f
--- /dev/null
+++ b/deploy/SETUP_TURN_SERVER.md
@@ -0,0 +1,85 @@
+# Настройка TURN-сервера
+
+TURN-хосты SHiNE должны использовать домены:
+
+- `turn1.shineup.me`
+- `turn2.shineup.me`
+- `turn3.shineup.me`
+
+IP не фиксировать в документах и скриптах как основной идентификатор.
+
+## 1. DNS
+
+Создать или обновить DNS `A/AAAA` для нужного `turnX.shineup.me`.
+
+## 2. Пакеты
+
+```bash
+sudo apt update
+sudo apt install -y coturn caddy
+```
+
+Можно использовать вспомогательный скрипт на самом TURN-хосте:
+
+```bash
+sudo bash deploy/scripts/setup_turn_coturn.sh --realm turn1.shineup.me --secret CHANGE_ME_LONG_RANDOM_SECRET
+```
+
+## 3. coturn
+
+Секреты не хранить в git. Настраивать на сервере через `/etc/turnserver.conf` или отдельный secret/override.
+
+Минимальная схема:
+
+```conf
+listening-port=3478
+tls-listening-port=5349
+fingerprint
+lt-cred-mech
+realm=turn1.shineup.me
+server-name=turn1.shineup.me
+use-auth-secret
+static-auth-secret=
+no-multicast-peers
+no-cli
+```
+
+Включить сервис:
+
+```bash
+sudo systemctl enable coturn
+sudo systemctl restart coturn
+```
+
+## 4. Caddy
+
+Caddy можно использовать для HTTPS health/check endpoint и автоматического TLS на домене.
+
+Пример:
+
+```caddyfile
+turn1.shineup.me {
+ respond /health "ok" 200
+}
+```
+
+Проверка:
+
+```bash
+sudo caddy validate --config /etc/caddy/Caddyfile
+sudo systemctl restart caddy
+curl -I https://turn1.shineup.me/health
+```
+
+## 5. Проверка портов
+
+```bash
+sudo systemctl --no-pager --full status coturn caddy
+sudo ss -lntup | grep -E ':(3478|5349)'
+```
+
+## 6. Важное
+
+- TURN credentials и shared secret не коммитить.
+- Если TURN домен переносится на другой VPS, менять DNS, а не код приложения.
+- После изменения TURN-адресов обновить серверные/UI настройки, где они используются.
diff --git a/deploy/TEST_SERVERS.md b/deploy/TEST_SERVERS.md
new file mode 100644
index 00000000..95af47d1
--- /dev/null
+++ b/deploy/TEST_SERVERS.md
@@ -0,0 +1,61 @@
+# Test/devnet серверы SHiNE
+
+Отдельный test/devnet стенд состоит из четырёх независимых SHiNE-инстансов:
+
+- `t1.shineup.me`
+- `t2.shineup.me`
+- `t3.shineup.me`
+- `t4.shineup.me`
+
+Это не production. Все deploy-цели задаются через домены.
+
+## Общие параметры
+
+- SSH: `player@tX.shineup.me`
+- Solana cluster: `devnet`
+- Solana RPC: `https://api.devnet.solana.com`
+- Caddy config: `/etc/caddy/Caddyfile`
+
+## Инстансы
+
+| Домен | Логин сервера | Сервер | UI | Порт | systemd |
+|---|---|---|---|---|---|
+| `t1.shineup.me` | `server_t1` | `/home/player/t1/server` | `/home/player/t1/UI` | `7101` | `shine-t1.service` |
+| `t2.shineup.me` | `server_t2` | `/home/player/t2/server` | `/home/player/t2/UI` | `7102` | `shine-t2.service` |
+| `t3.shineup.me` | `server_t3` | `/home/player/t3/server` | `/home/player/t3/UI` | `7103` | `shine-t3.service` |
+| `t4.shineup.me` | `server_t4` | `/home/player/t4/server` | `/home/player/t4/UI` | `7104` | `shine-t4.service` |
+
+## Deploy
+
+```bash
+bash deploy/scripts/test_t1_server.sh
+bash deploy/scripts/test_t1_ui.sh
+
+bash deploy/scripts/test_t2_server.sh
+bash deploy/scripts/test_t2_ui.sh
+
+bash deploy/scripts/test_t3_server.sh
+bash deploy/scripts/test_t3_ui.sh
+
+bash deploy/scripts/test_t4_server.sh
+bash deploy/scripts/test_t4_ui.sh
+```
+
+## Проверка
+
+```bash
+curl -I https://t1.shineup.me
+curl -I https://t2.shineup.me
+curl -I https://t3.shineup.me
+curl -I https://t4.shineup.me
+```
+
+На сервере:
+
+```bash
+sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
+```
+
+## Operational-нюанс
+
+Официальный `https://api.devnet.solana.com` может отдавать `HTTP 429`, если несколько инстансов одновременно делают bootstrap PDA/sync-серверов. Если после деплоя какой-то инстанс не подтянул `sync_servers`, рестартовать сервисы по одному с паузой.
diff --git a/deploy/TURN_SERVERS.md b/deploy/TURN_SERVERS.md
new file mode 100644
index 00000000..3107cb69
--- /dev/null
+++ b/deploy/TURN_SERVERS.md
@@ -0,0 +1,40 @@
+# TURN-серверы SHiNE
+
+TURN-серверы использовать и документировать через домены, а не через IP.
+
+## Текущие домены
+
+- `turn1.shineup.me`
+- `turn2.shineup.me`
+- `turn3.shineup.me`
+
+## Назначение
+
+TURN нужен для WebRTC-звонков, когда прямое peer-to-peer соединение невозможно из-за NAT/firewall.
+
+## Базовые ожидания
+
+- DNS каждого `turnX.shineup.me` указывает на актуальный физический сервер TURN.
+- TLS/HTTPS и вспомогательная маршрутизация обслуживаются через Caddy, если на сервере есть web endpoint для проверки.
+- `coturn` слушает TURN/STUN порты согласно локальному `turnserver.conf`.
+- Секреты TURN не хранить в git.
+
+## Что проверять
+
+```bash
+dig +short turn1.shineup.me
+dig +short turn2.shineup.me
+dig +short turn3.shineup.me
+```
+
+На каждом TURN-хосте:
+
+```bash
+sudo systemctl --no-pager --full status coturn caddy
+sudo ss -lntup | grep -E ':(3478|5349)'
+```
+
+## Где настраивать
+
+- TURN-хост с нуля: `SETUP_TURN_SERVER.md`.
+- Подключение TURN к SHiNE backend: `CONFIGURE_TURN_IN_SHINE.md`.
diff --git a/server-backup/AGENTS.md b/deploy/backup/AGENTS.md
similarity index 63%
rename from server-backup/AGENTS.md
rename to deploy/backup/AGENTS.md
index 5ecf1863..7d01fb60 100644
--- a/server-backup/AGENTS.md
+++ b/deploy/backup/AGENTS.md
@@ -1,7 +1,7 @@
-# AGENTS для server-backup
+# AGENTS для deploy/backup
## Назначение
-- Папка `server-backup/` хранит:
+- Папка `deploy/backup/` хранит:
- тяжёлые локальные бэкапы сервера (НЕ в git);
- лёгкую схему восстановления (в git), чтобы можно было поднять сервер даже без полного архива.
@@ -11,19 +11,20 @@
- `backup-version.properties` — версия контура бэкапа.
## Правила
-- Полный бэкап складывать только в `server-backup/archive/`.
-- `server-backup/archive/**` не коммитить.
+- Полный бэкап складывать только в `deploy/backup/archive/`.
+- `deploy/backup/archive/**` не коммитить, кроме `.gitkeep`.
+- Перед production deploy проверить, что свежий бэкап скопирован в `deploy/backup/archive/<дата>/` и есть `MANIFEST.txt`.
- Любое изменение схемы восстановления фиксировать в git.
- После обновления схемы увеличивать `backup.schema.version`.
- После нового полного бэкапа увеличивать `backup.full.version`.
## Как обновлять бэкап
1. Обновить схему:
- - `bash server-backup/scheme/shineup.me/scripts/refresh_scheme.sh`
+ - `bash deploy/backup/scheme/shineup.me/scripts/refresh_scheme.sh`
2. Сделать новый полный бэкап:
- - `bash server-backup/scheme/shineup.me/scripts/backup_full.sh`
-3. Проверить `server-backup/archive/<дата>/MANIFEST.txt`.
-4. Поднять версии в `server-backup/backup-version.properties`.
+ - `bash deploy/backup/scheme/shineup.me/scripts/backup_full.sh`
+3. Проверить `deploy/backup/archive/<дата>/MANIFEST.txt`.
+4. Поднять версии в `deploy/backup/backup-version.properties`.
## Как восстанавливать
-- Смотреть `server-backup/scheme/shineup.me/docs/RESTORE.md`.
+- Смотреть `deploy/backup/scheme/shineup.me/docs/RESTORE.md`.
diff --git a/deploy/backup/README.md b/deploy/backup/README.md
new file mode 100644
index 00000000..38a089df
--- /dev/null
+++ b/deploy/backup/README.md
@@ -0,0 +1,9 @@
+# deploy/backup
+
+- `archive/` — локальные полные бэкапы по датам (не коммитятся, кроме `.gitkeep`).
+- `scheme/` — лёгкая схема восстановления (коммитится).
+- `backup-version.properties` — версии схемы и полного бэкапа.
+
+Production deploy требует, чтобы перед запуском свежий бэкап был скопирован в `deploy/backup/archive//` и содержал `MANIFEST.txt`.
+
+Основной сервер-источник: `shineup.me`.
diff --git a/server-backup/archive/.gitkeep b/deploy/backup/archive/.gitkeep
similarity index 100%
rename from server-backup/archive/.gitkeep
rename to deploy/backup/archive/.gitkeep
diff --git a/server-backup/backup-version.properties b/deploy/backup/backup-version.properties
similarity index 100%
rename from server-backup/backup-version.properties
rename to deploy/backup/backup-version.properties
diff --git a/server-backup/scheme/shineup.me/captures/01_host_disk.txt b/deploy/backup/scheme/shineup.me/captures/01_host_disk.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/01_host_disk.txt
rename to deploy/backup/scheme/shineup.me/captures/01_host_disk.txt
diff --git a/server-backup/scheme/shineup.me/captures/02_enabled_services.txt b/deploy/backup/scheme/shineup.me/captures/02_enabled_services.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/02_enabled_services.txt
rename to deploy/backup/scheme/shineup.me/captures/02_enabled_services.txt
diff --git a/server-backup/scheme/shineup.me/captures/03_running_services.txt b/deploy/backup/scheme/shineup.me/captures/03_running_services.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/03_running_services.txt
rename to deploy/backup/scheme/shineup.me/captures/03_running_services.txt
diff --git a/server-backup/scheme/shineup.me/captures/04_listen_ports.txt b/deploy/backup/scheme/shineup.me/captures/04_listen_ports.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/04_listen_ports.txt
rename to deploy/backup/scheme/shineup.me/captures/04_listen_ports.txt
diff --git a/server-backup/scheme/shineup.me/captures/05_docker_ps.txt b/deploy/backup/scheme/shineup.me/captures/05_docker_ps.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/05_docker_ps.txt
rename to deploy/backup/scheme/shineup.me/captures/05_docker_ps.txt
diff --git a/server-backup/scheme/shineup.me/captures/06_home_player_sizes.txt b/deploy/backup/scheme/shineup.me/captures/06_home_player_sizes.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/06_home_player_sizes.txt
rename to deploy/backup/scheme/shineup.me/captures/06_home_player_sizes.txt
diff --git a/server-backup/scheme/shineup.me/captures/07_var_sizes.txt b/deploy/backup/scheme/shineup.me/captures/07_var_sizes.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/07_var_sizes.txt
rename to deploy/backup/scheme/shineup.me/captures/07_var_sizes.txt
diff --git a/server-backup/scheme/shineup.me/captures/UPDATED_AT_UTC.txt b/deploy/backup/scheme/shineup.me/captures/UPDATED_AT_UTC.txt
similarity index 100%
rename from server-backup/scheme/shineup.me/captures/UPDATED_AT_UTC.txt
rename to deploy/backup/scheme/shineup.me/captures/UPDATED_AT_UTC.txt
diff --git a/server-backup/scheme/shineup.me/configs/Caddyfile b/deploy/backup/scheme/shineup.me/configs/Caddyfile
similarity index 100%
rename from server-backup/scheme/shineup.me/configs/Caddyfile
rename to deploy/backup/scheme/shineup.me/configs/Caddyfile
diff --git a/server-backup/scheme/shineup.me/configs/systemd/agent-memory.service b/deploy/backup/scheme/shineup.me/configs/systemd/agent-memory.service
similarity index 100%
rename from server-backup/scheme/shineup.me/configs/systemd/agent-memory.service
rename to deploy/backup/scheme/shineup.me/configs/systemd/agent-memory.service
diff --git a/server-backup/scheme/shineup.me/configs/systemd/shine-server.service b/deploy/backup/scheme/shineup.me/configs/systemd/shine-server.service
similarity index 100%
rename from server-backup/scheme/shineup.me/configs/systemd/shine-server.service
rename to deploy/backup/scheme/shineup.me/configs/systemd/shine-server.service
diff --git a/server-backup/scheme/shineup.me/configs/turnserver.conf b/deploy/backup/scheme/shineup.me/configs/turnserver.conf
similarity index 99%
rename from server-backup/scheme/shineup.me/configs/turnserver.conf
rename to deploy/backup/scheme/shineup.me/configs/turnserver.conf
index 4d8d5cfb..4ef9e9f9 100644
--- a/server-backup/scheme/shineup.me/configs/turnserver.conf
+++ b/deploy/backup/scheme/shineup.me/configs/turnserver.conf
@@ -706,4 +706,4 @@ syslog
#no-tlsv1
#no-tlsv1_1
#no-tlsv1_2
-static-auth-secret=def6d444734d380d2f67a9d345b1debf985eaba0973c343e392c060d97c30106
+static-auth-secret=
diff --git a/server-backup/scheme/shineup.me/docs/RESTORE.md b/deploy/backup/scheme/shineup.me/docs/RESTORE.md
similarity index 76%
rename from server-backup/scheme/shineup.me/docs/RESTORE.md
rename to deploy/backup/scheme/shineup.me/docs/RESTORE.md
index 00613434..cfb2cd84 100644
--- a/server-backup/scheme/shineup.me/docs/RESTORE.md
+++ b/deploy/backup/scheme/shineup.me/docs/RESTORE.md
@@ -12,18 +12,18 @@ sudo apt install -y rsync caddy coturn docker.io
```
## 3. Восстановление файлов из полного бэкапа
-Предполагается, что полный бэкап лежит локально в `server-backup/archive/YYYY-MM-DD/`.
+Предполагается, что полный бэкап лежит локально в `deploy/backup/archive/YYYY-MM-DD/`.
```bash
-rsync -a server-backup/archive/YYYY-MM-DD/home-player/SHiNE/ player@NEW_SERVER:/home/player/SHiNE/
-rsync -a server-backup/archive/YYYY-MM-DD/home-player/sites/ player@NEW_SERVER:/home/player/sites/
-rsync -a server-backup/archive/YYYY-MM-DD/home-player/gitea/ player@NEW_SERVER:/home/player/gitea/
-rsync -a server-backup/archive/YYYY-MM-DD/home-player/agent-memory/ player@NEW_SERVER:/home/player/agent-memory/
+rsync -a deploy/backup/archive/YYYY-MM-DD/home-player/SHiNE/ player@NEW_SERVER:/home/player/SHiNE/
+rsync -a deploy/backup/archive/YYYY-MM-DD/home-player/sites/ player@NEW_SERVER:/home/player/sites/
+rsync -a deploy/backup/archive/YYYY-MM-DD/home-player/gitea/ player@NEW_SERVER:/home/player/gitea/
+rsync -a deploy/backup/archive/YYYY-MM-DD/home-player/agent-memory/ player@NEW_SERVER:/home/player/agent-memory/
-rsync -a server-backup/archive/YYYY-MM-DD/etc-system/caddy/ player@NEW_SERVER:/tmp/restore-caddy/
-rsync -a server-backup/archive/YYYY-MM-DD/var-lib/caddy/ player@NEW_SERVER:/tmp/restore-var-lib-caddy/
-rsync -a server-backup/archive/YYYY-MM-DD/etc-system/turnserver.conf player@NEW_SERVER:/tmp/turnserver.conf
-rsync -a server-backup/archive/YYYY-MM-DD/etc-system/*.service player@NEW_SERVER:/tmp/
+rsync -a deploy/backup/archive/YYYY-MM-DD/etc-system/caddy/ player@NEW_SERVER:/tmp/restore-caddy/
+rsync -a deploy/backup/archive/YYYY-MM-DD/var-lib/caddy/ player@NEW_SERVER:/tmp/restore-var-lib-caddy/
+rsync -a deploy/backup/archive/YYYY-MM-DD/etc-system/turnserver.conf player@NEW_SERVER:/tmp/turnserver.conf
+rsync -a deploy/backup/archive/YYYY-MM-DD/etc-system/*.service player@NEW_SERVER:/tmp/
```
Далее на новом сервере:
diff --git a/server-backup/scheme/shineup.me/scripts/backup_full.sh b/deploy/backup/scheme/shineup.me/scripts/backup_full.sh
similarity index 100%
rename from server-backup/scheme/shineup.me/scripts/backup_full.sh
rename to deploy/backup/scheme/shineup.me/scripts/backup_full.sh
diff --git a/server-backup/scheme/shineup.me/scripts/refresh_scheme.sh b/deploy/backup/scheme/shineup.me/scripts/refresh_scheme.sh
similarity index 100%
rename from server-backup/scheme/shineup.me/scripts/refresh_scheme.sh
rename to deploy/backup/scheme/shineup.me/scripts/refresh_scheme.sh
diff --git a/deploy/scripts/deploy_server.sh b/deploy/scripts/deploy_server.sh
new file mode 100755
index 00000000..e3647e03
--- /dev/null
+++ b/deploy/scripts/deploy_server.sh
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
+
+REMOTE_HOST="${REMOTE_HOST:?REMOTE_HOST is required, example: player@shineup.me}"
+TARGET_DOMAIN="${TARGET_DOMAIN:?TARGET_DOMAIN is required, example: shineup.me}"
+REMOTE_SERVER_DIR="${REMOTE_SERVER_DIR:?REMOTE_SERVER_DIR is required}"
+REMOTE_LOGS_DIR="${REMOTE_LOGS_DIR:-$REMOTE_SERVER_DIR/logs}"
+REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_SERVER_DIR/data}"
+REMOTE_SERVICE_NAME="${REMOTE_SERVICE_NAME:?REMOTE_SERVICE_NAME is required}"
+SERVER_PORT="${SERVER_PORT:?SERVER_PORT is required}"
+LOCAL_JAR="${LOCAL_JAR:-$ROOT_DIR/SHiNE-server/build/libs/shine-server.jar}"
+BUILD_JAR="${BUILD_JAR:-1}"
+REQUIRE_BACKUP="${REQUIRE_BACKUP:-0}"
+BACKUP_DIR="${BACKUP_DIR:-$ROOT_DIR/deploy/backup/archive}"
+
+if [[ "$REQUIRE_BACKUP" == "1" ]]; then
+ if ! find "$BACKUP_DIR" -mindepth 2 -maxdepth 2 -name MANIFEST.txt -print -quit 2>/dev/null | grep -q .; then
+ echo "ERROR: для production deploy сначала положите свежий бэкап в $BACKUP_DIR//MANIFEST.txt" >&2
+ exit 1
+ fi
+fi
+
+if [[ "$BUILD_JAR" == "1" ]]; then
+ echo "==> Building server jar"
+ (cd "$ROOT_DIR" && ./gradlew shadowJar)
+fi
+
+if [[ ! -f "$LOCAL_JAR" ]]; then
+ echo "ERROR: локальный jar не найден: $LOCAL_JAR" >&2
+ exit 1
+fi
+
+echo "==> Deploy server to $TARGET_DOMAIN ($REMOTE_HOST)"
+ssh -o BatchMode=yes -o ConnectTimeout=20 "$REMOTE_HOST" "echo SSH OK" >/dev/null
+ssh "$REMOTE_HOST" "sudo -n true"
+ssh "$REMOTE_HOST" "java -version >/dev/null 2>&1"
+
+TMP_DIR="$(mktemp -d)"
+cleanup() {
+ rm -rf "$TMP_DIR"
+}
+trap cleanup EXIT
+
+cat >"$TMP_DIR/$REMOTE_SERVICE_NAME.service" </dev/null"
+
+echo "Всё хорошо: сервер $TARGET_DOMAIN обновлён и сервис $REMOTE_SERVICE_NAME перезапущен"
diff --git a/deploy_shine-PWA.sh b/deploy/scripts/deploy_ui.sh
similarity index 81%
rename from deploy_shine-PWA.sh
rename to deploy/scripts/deploy_ui.sh
index c63dd683..f1924068 100755
--- a/deploy_shine-PWA.sh
+++ b/deploy/scripts/deploy_ui.sh
@@ -1,16 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
-SRC_DIR="shine-UI"
-REMOTE_HOST="${REMOTE_HOST:-player@shineup.me}"
-REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}"
-EXPECTED_CADDY_UI_ROOT="${EXPECTED_CADDY_UI_ROOT:-/home/player/SHiNE/shine-ui}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
+
+SRC_DIR="$ROOT_DIR/shine-UI"
+REMOTE_HOST="${REMOTE_HOST:?REMOTE_HOST is required, example: player@shineup.me}"
+REMOTE_UI_DIR="${REMOTE_UI_DIR:?REMOTE_UI_DIR is required}"
+EXPECTED_CADDY_UI_ROOT="${EXPECTED_CADDY_UI_ROOT:-$REMOTE_UI_DIR}"
ALLOW_CADDY_MISMATCH="${ALLOW_CADDY_MISMATCH:-0}"
-EXPECTED_CADDY_SITE="${EXPECTED_CADDY_SITE:-shineup.me}"
+EXPECTED_CADDY_SITE="${EXPECTED_CADDY_SITE:?EXPECTED_CADDY_SITE is required}"
+TARGET_URL="${TARGET_URL:-https://${EXPECTED_CADDY_SITE}}"
+DEPLOY_SERVER_LOGIN="${DEPLOY_SERVER_LOGIN:-}"
+DEPLOY_SERVER_ADDRESS="${DEPLOY_SERVER_ADDRESS:-}"
+DEPLOY_SOLANA_CLUSTER="${DEPLOY_SOLANA_CLUSTER:-}"
+DEPLOY_SOLANA_ENDPOINT="${DEPLOY_SOLANA_ENDPOINT:-}"
+REQUIRE_BACKUP="${REQUIRE_BACKUP:-0}"
+BACKUP_DIR="${BACKUP_DIR:-$ROOT_DIR/deploy/backup/archive}"
+VERSION_FILE="$ROOT_DIR/VERSION.properties"
BUILD_VERSION="$(date -u +%Y%m%d%H%M%S)"
-VERSION_FILE="VERSION.properties"
export BUILD_VERSION
-TMP_DIR="$(mktemp -d)"
+
+if [[ "$REQUIRE_BACKUP" == "1" ]]; then
+ if ! find "$BACKUP_DIR" -mindepth 2 -maxdepth 2 -name MANIFEST.txt -print -quit 2>/dev/null | grep -q .; then
+ echo "ERROR: для production deploy сначала положите свежий бэкап в $BACKUP_DIR//MANIFEST.txt" >&2
+ exit 1
+ fi
+fi
if [[ ! -f "$VERSION_FILE" ]]; then
echo "ERROR: version file not found: $VERSION_FILE" >&2
@@ -24,18 +40,12 @@ if [[ -z "$CLIENT_VERSION" ]]; then
fi
export CLIENT_VERSION
-TARGET_URL="${TARGET_URL:-https://${EXPECTED_CADDY_SITE}}"
-REMOTE_DIR="${REMOTE_UI_DIR}"
-DEPLOY_SERVER_LOGIN="${DEPLOY_SERVER_LOGIN:-}"
-DEPLOY_SERVER_ADDRESS="${DEPLOY_SERVER_ADDRESS:-}"
-DEPLOY_SOLANA_CLUSTER="${DEPLOY_SOLANA_CLUSTER:-}"
-DEPLOY_SOLANA_ENDPOINT="${DEPLOY_SOLANA_ENDPOINT:-}"
DEPLOY_SOLANA_CLUSTER_NORMALIZED="$DEPLOY_SOLANA_CLUSTER"
-
if [[ "$DEPLOY_SOLANA_CLUSTER_NORMALIZED" == "mainnet" ]]; then
DEPLOY_SOLANA_CLUSTER_NORMALIZED="mainnet-beta"
fi
+TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
@@ -48,7 +58,7 @@ fi
echo "==> Preparing staged UI copy with build version: $BUILD_VERSION"
echo "==> Client version from $VERSION_FILE: $CLIENT_VERSION"
-echo "==> Deploy target: $TARGET_URL ($REMOTE_DIR)"
+echo "==> Deploy target: $TARGET_URL ($REMOTE_UI_DIR)"
rsync -a "$SRC_DIR"/ "$TMP_DIR"/
DEPLOY_CONFIG_FILE="$TMP_DIR/js/deploy-config.js"
@@ -156,12 +166,14 @@ elif [[ "$ROOT_CHECK_OUTPUT" != *"$EXPECTED_CADDY_UI_ROOT"* ]]; then
echo "WARN: proceeding due to ALLOW_CADDY_MISMATCH=1"
fi
-echo "==> Preparing remote directory: $REMOTE_DIR"
-ssh "$REMOTE_HOST" "sudo mkdir -p '$REMOTE_DIR'"
+echo "==> Preparing remote directory: $REMOTE_UI_DIR"
+ssh "$REMOTE_HOST" "sudo mkdir -p '$REMOTE_UI_DIR'"
-echo "==> Syncing staged files to $REMOTE_DIR"
+echo "==> Syncing staged files to $REMOTE_UI_DIR"
rsync -rlvz --delete --omit-dir-times --no-perms --no-owner --no-group \
--rsync-path="sudo rsync" \
- "$TMP_DIR"/ "$REMOTE_HOST":"$REMOTE_DIR"/
+ "$TMP_DIR"/ "$REMOTE_HOST":"$REMOTE_UI_DIR"/
+
+ssh "$REMOTE_HOST" "sudo find '$REMOTE_UI_DIR' -type d -exec chmod o+rx {} +; sudo find '$REMOTE_UI_DIR' -type f -exec chmod o+r {} +"
echo "Всё хорошо: $TARGET_URL"
diff --git a/deploy/scripts/production_server2_server.sh b/deploy/scripts/production_server2_server.sh
new file mode 100755
index 00000000..c3645c47
--- /dev/null
+++ b/deploy/scripts/production_server2_server.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@server2.shineup.me" \
+TARGET_DOMAIN="server2.shineup.me" \
+REMOTE_SERVER_DIR="/home/player/SHiNE/shine-server" \
+REMOTE_SERVICE_NAME="shine-server" \
+SERVER_PORT="7070" \
+REQUIRE_BACKUP="1" \
+bash "$(dirname "$0")/deploy_server.sh"
diff --git a/deploy/scripts/production_server2_ui.sh b/deploy/scripts/production_server2_ui.sh
new file mode 100755
index 00000000..aa69af7f
--- /dev/null
+++ b/deploy/scripts/production_server2_ui.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@server2.shineup.me" \
+REMOTE_UI_DIR="/home/player/SHiNE/shine-ui" \
+EXPECTED_CADDY_UI_ROOT="/home/player/SHiNE/shine-ui" \
+EXPECTED_CADDY_SITE="server2.shineup.me" \
+DEPLOY_SERVER_LOGIN="server2shineupme" \
+DEPLOY_SERVER_ADDRESS="server2.shineup.me" \
+DEPLOY_SOLANA_CLUSTER="mainnet" \
+DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \
+TARGET_URL="https://server2.shineup.me" \
+REQUIRE_BACKUP="1" \
+bash "$(dirname "$0")/deploy_ui.sh"
diff --git a/deploy/scripts/production_shineupme_server.sh b/deploy/scripts/production_shineupme_server.sh
new file mode 100755
index 00000000..5b7c554b
--- /dev/null
+++ b/deploy/scripts/production_shineupme_server.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@shineup.me" \
+TARGET_DOMAIN="shineup.me" \
+REMOTE_SERVER_DIR="/home/player/SHiNE/shine-server" \
+REMOTE_SERVICE_NAME="shine-server" \
+SERVER_PORT="7070" \
+REQUIRE_BACKUP="1" \
+bash "$(dirname "$0")/deploy_server.sh"
diff --git a/deploy_shine-ui_production_shineupme.sh b/deploy/scripts/production_shineupme_ui.sh
old mode 100644
new mode 100755
similarity index 87%
rename from deploy_shine-ui_production_shineupme.sh
rename to deploy/scripts/production_shineupme_ui.sh
index e61a55c7..37fc0474
--- a/deploy_shine-ui_production_shineupme.sh
+++ b/deploy/scripts/production_shineupme_ui.sh
@@ -10,4 +10,5 @@ DEPLOY_SERVER_ADDRESS="shineup.me" \
DEPLOY_SOLANA_CLUSTER="mainnet" \
DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \
TARGET_URL="https://shineup.me" \
-bash "$(dirname "$0")/deploy_shine-PWA.sh"
+REQUIRE_BACKUP="1" \
+bash "$(dirname "$0")/deploy_ui.sh"
diff --git a/scripts/setup_turn_coturn.sh b/deploy/scripts/setup_turn_coturn.sh
similarity index 81%
rename from scripts/setup_turn_coturn.sh
rename to deploy/scripts/setup_turn_coturn.sh
index 5a98944e..20494924 100755
--- a/scripts/setup_turn_coturn.sh
+++ b/deploy/scripts/setup_turn_coturn.sh
@@ -5,10 +5,10 @@ set -euo pipefail
# Запускать НА TURN-сервере под root.
#
# Пример:
-# sudo bash scripts/setup_turn_coturn.sh --secret "CHANGE_ME_LONG_SECRET" --realm "shineup.me"
+# sudo bash deploy/scripts/setup_turn_coturn.sh --secret "CHANGE_ME_LONG_SECRET" --realm "turn1.shineup.me"
SECRET=""
-REALM="shineup.me"
+REALM="turn1.shineup.me"
MIN_PORT="49160"
MAX_PORT="49200"
@@ -46,9 +46,12 @@ export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y coturn
-PUBLIC_IP="$(hostname -I | awk '{print $1}')"
+PUBLIC_IP="$(getent ahostsv4 "$REALM" | awk '{print $1; exit}')"
if [[ -z "${PUBLIC_IP}" ]]; then
- echo "Не удалось определить public ip автоматически, укажите вручную в /etc/turnserver.conf" >&2
+ PUBLIC_IP="$(hostname -I | awk '{print $1}')"
+fi
+if [[ -z "${PUBLIC_IP}" ]]; then
+ echo "Не удалось определить public ip автоматически по домену ${REALM}, укажите вручную в /etc/turnserver.conf" >&2
PUBLIC_IP="0.0.0.0"
fi
diff --git a/deploy/scripts/test_t1_server.sh b/deploy/scripts/test_t1_server.sh
new file mode 100755
index 00000000..f38fbdf8
--- /dev/null
+++ b/deploy/scripts/test_t1_server.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@t1.shineup.me" \
+TARGET_DOMAIN="t1.shineup.me" \
+REMOTE_SERVER_DIR="/home/player/t1/server" \
+REMOTE_SERVICE_NAME="shine-t1" \
+SERVER_PORT="7101" \
+bash "$(dirname "$0")/deploy_server.sh"
diff --git a/deploy_shine-ui_t1.sh b/deploy/scripts/test_t1_ui.sh
old mode 100644
new mode 100755
similarity index 81%
rename from deploy_shine-ui_t1.sh
rename to deploy/scripts/test_t1_ui.sh
index bd09bb5d..c097b1fc
--- a/deploy_shine-ui_t1.sh
+++ b/deploy/scripts/test_t1_ui.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
-REMOTE_HOST="player@178.208.90.249" \
+REMOTE_HOST="player@t1.shineup.me" \
REMOTE_UI_DIR="/home/player/t1/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t1/UI" \
EXPECTED_CADDY_SITE="t1.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t1.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t1.shineup.me" \
-bash "$(dirname "$0")/deploy_shine-PWA.sh"
+bash "$(dirname "$0")/deploy_ui.sh"
diff --git a/deploy/scripts/test_t2_server.sh b/deploy/scripts/test_t2_server.sh
new file mode 100755
index 00000000..1a34c459
--- /dev/null
+++ b/deploy/scripts/test_t2_server.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@t2.shineup.me" \
+TARGET_DOMAIN="t2.shineup.me" \
+REMOTE_SERVER_DIR="/home/player/t2/server" \
+REMOTE_SERVICE_NAME="shine-t2" \
+SERVER_PORT="7102" \
+bash "$(dirname "$0")/deploy_server.sh"
diff --git a/deploy_shine-ui_t2.sh b/deploy/scripts/test_t2_ui.sh
old mode 100644
new mode 100755
similarity index 81%
rename from deploy_shine-ui_t2.sh
rename to deploy/scripts/test_t2_ui.sh
index 443ce206..fbeeaf61
--- a/deploy_shine-ui_t2.sh
+++ b/deploy/scripts/test_t2_ui.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
-REMOTE_HOST="player@178.208.90.249" \
+REMOTE_HOST="player@t2.shineup.me" \
REMOTE_UI_DIR="/home/player/t2/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t2/UI" \
EXPECTED_CADDY_SITE="t2.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t2.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t2.shineup.me" \
-bash "$(dirname "$0")/deploy_shine-PWA.sh"
+bash "$(dirname "$0")/deploy_ui.sh"
diff --git a/deploy/scripts/test_t3_server.sh b/deploy/scripts/test_t3_server.sh
new file mode 100755
index 00000000..68a609df
--- /dev/null
+++ b/deploy/scripts/test_t3_server.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@t3.shineup.me" \
+TARGET_DOMAIN="t3.shineup.me" \
+REMOTE_SERVER_DIR="/home/player/t3/server" \
+REMOTE_SERVICE_NAME="shine-t3" \
+SERVER_PORT="7103" \
+bash "$(dirname "$0")/deploy_server.sh"
diff --git a/deploy_shine-ui_t3.sh b/deploy/scripts/test_t3_ui.sh
old mode 100644
new mode 100755
similarity index 81%
rename from deploy_shine-ui_t3.sh
rename to deploy/scripts/test_t3_ui.sh
index ff9d48dd..fd76ef8e
--- a/deploy_shine-ui_t3.sh
+++ b/deploy/scripts/test_t3_ui.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
-REMOTE_HOST="player@178.208.90.249" \
+REMOTE_HOST="player@t3.shineup.me" \
REMOTE_UI_DIR="/home/player/t3/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t3/UI" \
EXPECTED_CADDY_SITE="t3.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t3.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t3.shineup.me" \
-bash "$(dirname "$0")/deploy_shine-PWA.sh"
+bash "$(dirname "$0")/deploy_ui.sh"
diff --git a/deploy/scripts/test_t4_server.sh b/deploy/scripts/test_t4_server.sh
new file mode 100755
index 00000000..5a4c6ba9
--- /dev/null
+++ b/deploy/scripts/test_t4_server.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REMOTE_HOST="player@t4.shineup.me" \
+TARGET_DOMAIN="t4.shineup.me" \
+REMOTE_SERVER_DIR="/home/player/t4/server" \
+REMOTE_SERVICE_NAME="shine-t4" \
+SERVER_PORT="7104" \
+bash "$(dirname "$0")/deploy_server.sh"
diff --git a/deploy_shine-ui_t4.sh b/deploy/scripts/test_t4_ui.sh
old mode 100644
new mode 100755
similarity index 81%
rename from deploy_shine-ui_t4.sh
rename to deploy/scripts/test_t4_ui.sh
index 151d1971..415f1124
--- a/deploy_shine-ui_t4.sh
+++ b/deploy/scripts/test_t4_ui.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
-REMOTE_HOST="player@178.208.90.249" \
+REMOTE_HOST="player@t4.shineup.me" \
REMOTE_UI_DIR="/home/player/t4/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t4/UI" \
EXPECTED_CADDY_SITE="t4.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t4.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t4.shineup.me" \
-bash "$(dirname "$0")/deploy_shine-PWA.sh"
+bash "$(dirname "$0")/deploy_ui.sh"
diff --git a/deploy_shine-server_server2shineupme.sh b/deploy_shine-server_server2shineupme.sh
deleted file mode 100644
index b846ef2d..00000000
--- a/deploy_shine-server_server2shineupme.sh
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
-TARGET_DOMAIN="${TARGET_DOMAIN:-server2.shineup.me}"
-REMOTE_BASE="${REMOTE_BASE:-/home/player/SHiNE}"
-REMOTE_SERVER_DIR="${REMOTE_SERVER_DIR:-$REMOTE_BASE/shine-server}"
-REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_SERVER_DIR/data}"
-REMOTE_LOGS_DIR="${REMOTE_LOGS_DIR:-$REMOTE_SERVER_DIR/logs}"
-REMOTE_UI_DIR="${REMOTE_UI_DIR:-$REMOTE_BASE/shine-ui}"
-REMOTE_SERVICE_NAME="${REMOTE_SERVICE_NAME:-shine-server}"
-LOCAL_JAR="${LOCAL_JAR:-SHiNE-server/build/libs/shine-server.jar}"
-
-TMP_DIR="$(mktemp -d)"
-cleanup() {
- rm -rf "$TMP_DIR"
-}
-trap cleanup EXIT
-
-if [[ ! -f "$LOCAL_JAR" ]]; then
- echo "ERROR: локальный jar не найден: $LOCAL_JAR" >&2
- exit 1
-fi
-
-ssh -o BatchMode=yes -o ConnectTimeout=20 "$TARGET_HOST" "echo SSH OK" >/dev/null
-ssh "$TARGET_HOST" "sudo -n true"
-ssh "$TARGET_HOST" "java -version >/dev/null 2>&1"
-
-cat >"$TMP_DIR/shine-server.service" <` и отдельная автономная страница `shine-UI/promo-code-generator.html` для генерации promo-кода через браузерный кошелёк или через ручной ввод Base58 seed 32 bytes;
-- что проверять:
- - admin-транзакцией создать или обновить `promo_seller_pda`;
- - сгенерировать promo-код на странице `promo-code-generator.html` в режиме wallet extension;
- - сгенерировать promo-код на той же странице в режиме ручного Base58 seed;
- - открыть HTML локально как отдельный файл без сервера и убедиться, что генерация в режиме Base58 seed продолжает работать;
- - зарегистрировать короткий/premium логин по promo-коду;
- - убедиться, что без promo-кода тот же логин не проходит `shine_login_guard`;
- - убедиться, что после успешной регистрации `remaining_sales` уменьшается на 1;
- - проверить отказ при исчерпанной квоте, неверной подписи и логине короче `min_login_length`;
-- ожидаемый результат:
- - promo-регистрация проходит только при валидной подписи продавца и соблюдении лимитов;
- - обычная регистрация без promo-кода продолжает работать как раньше;
- - страница генерации выдаёт строку формата `1seller-signatureBase58` в обоих режимах;
- - single-file HTML работает локально оффлайн минимум в режиме ручного Base58 seed;
-- статус:
- - `pending`
diff --git a/docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md b/docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md
deleted file mode 100644
index d309652f..00000000
--- a/docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Синхронизация mainnet-адресов Solana по UI/серверу/ESP32
-
-- статус: `pending`
-
-## Кратко
-
-Во все основные клиентские и серверные точки проекта протянуты актуальные mainnet-адреса программ:
-
-- `shine_login_guard`: `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh`
-- `shine_users`: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
-- `shine_payments`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
-
-Также по умолчанию переключены основные RPC defaults на `https://api.mainnet-beta.solana.com` там, где это нужно для рабочего mainnet-сценария.
-
-## Что проверять
-
-1. Основной UI:
- - отображение и использование новых program id;
- - регистрация/чтение Solana PDA в mainnet;
- - экран кошелька и покупка лимита.
-
-2. Server UI:
- - создание server PDA в mainnet;
- - чтение PDA;
- - обновление PDA;
- - корректная блокировка devnet-автопополнения при mainnet endpoint.
-
-3. Browser plugin wallet:
- - резолв `user_pda` и `server PDA` через mainnet RPC.
-
-4. Web UI `shine_payments`:
- - чтение очередей;
- - покупка билета;
- - admin/manager/dao tools открываются с новыми program id и mainnet RPC.
-
-5. ESP32 homeserver UI:
- - отображаются новые program id;
- - дефолтный Solana RPC теперь mainnet.
-
-6. Временный режим регистрации:
- - в основном UI логины длиной 5-7 символов без временного кода не проходят;
- - в основном UI логины длиной 5-7 символов с временным кодом проходят локальную проверку;
- - в основном UI любой другой временный код отклоняется сразу;
- - ESP32 и прямой on-chain вызов `shine_users` полагаются на текущий временный `shine_login_guard`, то есть валидные логины длиной от 5 символов допускаются без словарной классификации.
-
-## Ожидаемый результат
-
-- Во всех перечисленных поверхностях используются новые mainnet program id.
-- Запросы по умолчанию идут в mainnet, кроме явно devnet-утилит.
-- Нет обращений к старым devnet program id.
-- Временный UI-барьер для коротких логинов работает так, как задумано, и не ломает обычную регистрацию для логинов от 8 символов.
diff --git a/docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md b/docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md
deleted file mode 100644
index 71e5a523..00000000
--- a/docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Временный режим коротких логинов
-
-- статус: `pending`
-
-## Кратко
-
-Временно упрощён `shine_login_guard`:
-
-- on-chain допускаются любые валидные логины длиной от `5` символов;
-- словарная premium/trademark-классификация временно отключена.
-
-Поверх этого основной UI вводит временное ограничение:
-
-- логины длиной `8+` проходят обычную проверку;
-- логины длиной `5..7` допускаются только при вводе временного кода;
-- любой другой код отклоняется сразу.
-
-ESP32 в этом временном режиме не ограничивается UI-кодом и использует только on-chain правила.
-
-## Что проверять
-
-1. Основной UI:
- - логин длиной `4` символа отклоняется;
- - логин длиной `5..7` без временного кода отклоняется;
- - логин длиной `5..7` с корректным временным кодом проходит локальную проверку;
- - логин длиной `5..7` с любым другим кодом отклоняется;
- - логин длиной `8+` проходит без временного кода.
-
-2. Solana on-chain:
- - `shine_login_guard` возвращает `CLASS_FREE` для валидных логинов длиной от `5`;
- - `shine_login_guard` возвращает `CLASS_PREMIUM` для логинов короче `5` или с невалидными символами.
-
-3. ESP32 / прямой вызов:
- - валидный логин длиной `5+` регистрируется без временного кода;
- - логин короче `5` не регистрируется.
-
-## Ожидаемый результат
-
-- Основной UI удерживает обычных пользователей от коротких логинов без временного кода.
-- Свои пользователи могут временно регистрировать короткие логины через UI-код.
-- ESP32 продолжает работать без отдельной промо-логики.
-- On-chain логика остаётся простой и компактной.
diff --git a/docs/Pending_Features/2026-07-09_0815_4_test_servers_devnet_vps.md b/docs/Pending_Features/2026-07-09_0815_4_test_servers_devnet_vps.md
deleted file mode 100644
index 623ef4cc..00000000
--- a/docs/Pending_Features/2026-07-09_0815_4_test_servers_devnet_vps.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# 4 тестовых SHiNE-сервера на одном VPS (`178.208.90.249`)
-
-- статус: `pending`
-
-## Кратко
-
-Поднят отдельный тестовый стенд:
-- `t1.shineup.me`
-- `t2.shineup.me`
-- `t3.shineup.me`
-- `t4.shineup.me`
-
-Каждый домен ведёт на свой SHiNE-инстанс под пользователем `player`, все инстансы работают через Solana `devnet`.
-
-## Что проверять
-
-- UI каждого домена открывается без ошибок:
- - `https://t1.shineup.me`
- - `https://t2.shineup.me`
- - `https://t3.shineup.me`
- - `https://t4.shineup.me`
-- регистрация/логин клиента может работать через каждый из этих серверов
-- WebSocket-подключение реально идёт на свой инстанс, а не в соседний
-- чтение PDA и server-ui формы по умолчанию используют `devnet`
-- межсерверный sync после ручных тестов действительно использует `access_servers/sync_servers`
-- не происходит путаницы данных между `t1/t2/t3/t4`
-
-## Ожидаемый результат
-
-- каждый домен работает независимо
-- у каждого инстанса своя БД и свои локальные данные
-- `Caddy` корректно проксирует `/ws` на `7101..7104`
-- серверы не падают после старта
-- если devnet RPC временно даёт `429`, последовательный рестарт по одному инстансу позволяет подтянуть `sync_servers`
-
-## Примечание
-
-Подробная схема размещения описана в:
-- [docs/deploy/test-server/178.208.90.249_quad_devnet.md](/home/ai/work/SHiNE/SHiNE-server-sha256/docs/deploy/test-server/178.208.90.249_quad_devnet.md)
diff --git a/docs/Pending_Features/2026-07-10_1248_call_summary_dm_from_initiator.md b/docs/Pending_Features/2026-07-10_1248_call_summary_dm_from_initiator.md
deleted file mode 100644
index 2db1f749..00000000
--- a/docs/Pending_Features/2026-07-10_1248_call_summary_dm_from_initiator.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Итог звонка как DM от инициатора
-
-- краткое описание фичи:
- Вместо локальных `call-tech` записей итог звонка теперь отправляется как обычное DM-сообщение только от стороны, которая инициировала звонок.
-
-- что именно проверять:
- 1. Успешный звонок: после завершения инициатор отправляет в чат DM вида `Звонок: 37с` или `Звонок: 2м 14с`.
- 2. Неуспешный звонок без ответа: инициатор отправляет `Звонил, но недозвонился: нет ответа`.
- 3. Неуспешный звонок, когда у адресата нет доставленных сессий: инициатор отправляет `Звонил, но недозвонился: абонент не в сети`.
- 4. Неуспешный звонок при проблеме соединения: инициатор отправляет `Звонил, но недозвонился: не удалось установить соединение`.
- 5. Старые локальные `call-tech` bubble про итог звонка больше не появляются ни у инициатора, ни у принимающей стороны.
-
-- ожидаемый результат:
- Итог звонка виден обеим сторонам как обычное DM-сообщение от инициатора, а локальные служебные записи про итог звонка больше не используются.
-
-- статус:
- pending
diff --git a/docs/Pending_Features/2026-07-10_1748_dm_opaque_body_and_decrypt_fallback.md b/docs/Pending_Features/2026-07-10_1748_dm_opaque_body_and_decrypt_fallback.md
deleted file mode 100644
index 173118a0..00000000
--- a/docs/Pending_Features/2026-07-10_1748_dm_opaque_body_and_decrypt_fallback.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Краткое описание
-
-Сервер перестал валидировать внутреннюю crypto-структуру `body` у контентных DM `type=1/2`.
-UI теперь не отбрасывает такие сообщения: если сообщение не удалось расшифровать, вместо текста показывается заглушка `Неудалось расшифровать сообщение`.
-
-## Что проверять
-
-- Отправка и приём обычных DM между штатными клиентами продолжают работать как раньше.
-- Сообщение с незнакомым форматом `body` у `type=1/2` принимается сервером и доходит до клиента.
-- В UI такое сообщение отображается в чате одной из ожидаемых заглушек:
- - `Формат сообщения не поддерживается`
- - `Не удалось расшифровать сообщение`
- - `Сообщение повреждено`
-- После выхода из аккаунта и повторного входа backlog с такими сообщениями тоже отображается с той же заглушкой.
-- ACK доставки по сессии для такого сообщения не зацикливается и сообщение не приходит бесконечно повторно.
-
-## Ожидаемый результат
-
-Сервер выступает транспортом для opaque DM-body, а клиент при неудачной расшифровке показывает безопасный fallback вместо полного пропуска сообщения с различением основных причин.
-
-## Статус
-
-pending
diff --git a/docs/Pending_Features/2026-07-10_1845_dm_shine_tags_and_safe_preview.md b/docs/Pending_Features/2026-07-10_1845_dm_shine_tags_and_safe_preview.md
deleted file mode 100644
index 98d897c2..00000000
--- a/docs/Pending_Features/2026-07-10_1845_dm_shine_tags_and_safe_preview.md
+++ /dev/null
@@ -1,28 +0,0 @@
-## Краткое описание
-
-В DM добавлен клиентский формат технических вставок в начале plaintext:
-
-- ``
-- ``
-
-UI скрывает такие вставки из текста сообщения, а call-вставки отображает специальным человекочитаемым видом.
-Также добавлена защита от пользовательского текста, начинающегося с `` показывается в чате как специальное call-сообщение, а не как сырой техтекст.
-- При исходящем звонке короче `5` секунд call-summary не отправляется вообще.
-- В списке личных сообщений превью такого сообщения показывает человекочитаемый итог звонка.
-- DM с `Текст ответа` скрывает техблок и показывает только `Текст ответа`.
-- В меню любого DM первым пунктом есть `Ответить`, и отправка из этого режима реально добавляет `` в начало plaintext.
-- Если reply-цель отсутствует, сообщение всё равно показывается как обычный текст ответа.
-- Текст вида `` или похожие HTML-теги не исполняются ни в чате, ни в списке личных сообщений, ни в списке каналов.
-
-## Ожидаемый результат
-
-Технические SHiNE-вставки работают только как управляющие метаданные UI, а отображаемый пользователю текст и превью остаются безопасными и не рендерят HTML.
-
-## Статус
-
-pending
diff --git a/docs/Pending_Features/2026-07-14_1325_fix_call_remote_hangup_and_ack_timeout.md b/docs/Pending_Features/2026-07-14_1325_fix_call_remote_hangup_and_ack_timeout.md
deleted file mode 100644
index d81fe498..00000000
--- a/docs/Pending_Features/2026-07-14_1325_fix_call_remote_hangup_and_ack_timeout.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Исправление завершения звонка и таймаутов ACK
-
-## Краткое описание
-
-Исправлена клиентская логика звонков:
-- входящий экран теперь должен закрываться сразу при `remote_hangup`;
-- после нажатия `Ответить` добавлен таймаут ожидания стадии `connecting`, чтобы окно не висело бесконечно;
-- исходящий таймаут ожидания ACK больше не обрывает звонок слишком рано, если invite уже был доставлен.
-- в локальный app log клиента добавлен точный timeline по этапам звонка: invite, выбор remoteSessionId, accept/offer/answer, ICE, смены `RTCPeerConnection` и финализация.
-- добавлена явная диагностика `SESSION_NOT_FOUND` для звонковых сигналов: отдельный timeline-этап в клиенте и отдельный серверный warn-лог.
-
-## Что проверять
-
-- Если исходящая сторона завершила звонок, входящее окно у второй стороны закрывается сразу.
-- Если принять входящий звонок, а соединение/offer не приходит, экран не висит бесконечно и сам завершается по таймауту.
-- Если invite был доставлен, исходящий звонок не падает через 10 секунд раньше времени только из-за медленного ACK.
-- В app log по звонку видна последовательность этапов с локальными `+N ms`, по которой можно понять, где именно образуется задержка или гонка.
-- Если сигнал звонка не дошёл до целевой сессии, это явно видно и в клиенте, и в серверном логе.
-
-## Ожидаемый результат
-
-- Нет бесконечно висящего окна `Соединяем…` на принимающей стороне.
-- В логах вместо преждевременного `no_ack_10s` для уже доставленного invite появляется более поздний таймаут.
-- При `remote_hangup` звонок визуально завершается сразу.
-- По каждому проблемному звонку есть локальный timeline и тот же краткий timeline попадает в `CallDeliveryReport`.
-- В серверных логах есть строка `CALL_SIGNAL_SESSION_NOT_FOUND ...` с `callId`, `targetSessionId` и количеством активных сессий адресата.
-
-## Статус
-
-`pending`
diff --git a/docs/Pending_Features/2026-07-14_1745_адаптация-шапки-связей.md b/docs/Pending_Features/2026-07-14_1745_адаптация-шапки-связей.md
deleted file mode 100644
index 2bf2f6f8..00000000
--- a/docs/Pending_Features/2026-07-14_1745_адаптация-шапки-связей.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Адаптация верхней панели экрана «Связи»
-
-## Краткое описание
-- Экран графа связей больше не расширяется за границы контейнера. Верхняя панель ограничена границами экрана, чтобы кнопки возврата, поиска и справки не обрезались на узких устройствах.
-
-## Что проверить
-- Открыть экран «Связи» на ширине около 320, 390 и 794 px.
-- Убедиться, что кнопки возврата, «Найти» и «?» полностью видны и нажимаются.
-- Убедиться, что фильтры и граф связей остаются доступными.
-
-## Ожидаемый результат
-- Верхние действия не выходят за левую или правую границу приложения.
-- Заголовок при нехватке места сокращается, а не сдвигает кнопки за край.
-
-## Статус
-- `pending` — локальная проверка на ширине 320 и 794 px пройдена; нужна ручная проверка после публикации на тестовом или production-стенде.
diff --git a/docs/Pending_Features/2026-07-14_1830_кнопка-поиска-каналов.md b/docs/Pending_Features/2026-07-14_1830_кнопка-поиска-каналов.md
deleted file mode 100644
index b96e6788..00000000
--- a/docs/Pending_Features/2026-07-14_1830_кнопка-поиска-каналов.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Кнопка поиска каналов
-
-## Краткое описание
-- Emoji-иконка поиска в верхней панели «Каналов» заменена на контурную кнопку-лупу в стиле интерфейса.
-
-## Что проверить
-- Открыть список каналов.
-- Убедиться, что кнопка поиска видна рядом с кнопкой создания канала и имеет подсказку «Найти канал».
-- Нажать кнопку и убедиться, что открывается прежнее окно поиска каналов.
-
-## Ожидаемый результат
-- Кнопка выглядит как часть общей тёмной панели и не использует emoji.
-- Поиск каналов работает без изменений.
-
-## Статус
-- `pending` — локальная визуальная проверка и открытие окна поиска пройдены; нужна ручная проверка после публикации на тестовом или production-стенде.
diff --git a/docs/Pending_Features/2026-07-14_1915_локальный-тестовый-вход.md b/docs/Pending_Features/2026-07-14_1915_локальный-тестовый-вход.md
deleted file mode 100644
index 6a61be39..00000000
--- a/docs/Pending_Features/2026-07-14_1915_локальный-тестовый-вход.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Локальный тестовый вход
-
-## Краткое описание
-- На `localhost`, `127.0.0.1` и `::1` доступна кнопка «Локальный тестовый вход».
-- Она создаёт только локальный демо-сеанс `local-tester`; пароль, серверная сессия и production не используются.
-- Экран личных сообщений и список каналов открывают автономные локальные состояния без запроса к серверу.
-- Профиль заполняется демонстрационными данными, чтобы интерфейс можно было проверить без серверного пользователя.
-
-## Что проверить
-- Открыть локальный UI и нажать «Локальный тестовый вход».
-- Проверить переход к списку сообщений и доступ к экранам профиля, каналов, связей и настроек.
-- Обновить страницу и убедиться, что локальный сеанс сохраняется.
-- Выйти из сеанса и убедиться, что снова открывается экран старта.
-
-## Ожидаемый результат
-- Локальный режим даёт возможность изучить интерфейс без реальной авторизации.
-- На `shineup.me` и тестовых доменах кнопка отсутствует.
-
-## Статус
-- `pending` — локальный вход, переход к профилю и демонстрационные каналы проверены; нужна ручная проверка выхода из сеанса после публикации на тестовом или production-стенде.
diff --git a/docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md b/docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md
deleted file mode 100644
index 6008e2a6..00000000
--- a/docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Экран проверки public Solana RPC в UI
-
-- статус: `pending`
-
-## Кратко
-
-Добавлен отдельный экран разработчика для проверки публичных mainnet Solana RPC прямо из браузера.
-Экран отправляет реальный JSON-RPC `getVersion` запрос к списку public endpoint-ов и показывает,
-какие ноды реально подходят для browser use.
-
-## Что проверять
-
-- в `Настройки разработчика` появилась кнопка `Solana: проверить public RPC`;
-- экран открывается без ошибок;
-- по каждой mainnet ноде показывается итоговый статус;
-- для доступных нод видно HTTP-статус, время ответа и версию `solana-core`;
-- длинные URL и ошибки не распирают экран по ширине на телефоне.
-
-## Ожидаемый результат
-
-- можно визуально увидеть, какие public Solana RPC доступны именно из браузера;
-- проверка работает без промежуточного сервера;
-- экран остаётся mobile-first и не требует горизонтального скролла.
diff --git a/docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md b/docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md
deleted file mode 100644
index d546b671..00000000
--- a/docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md
+++ /dev/null
@@ -1,20 +0,0 @@
-## Кратко
-- Переделан финальный flow регистрации: сначала экран ожидания подтверждения Solana с прогрессом и повторными проверками, потом отдельный экран успешной регистрации с кнопкой входа в стандартный экран сохранения ключей.
-
-## Что проверять
-- После отправки регистрации открывается экран ожидания с текстом про подтверждение Solana и кликабельным `Tx ID`.
-- Прогресс-бар сначала идёт быстрее, потом заметно замедляется.
-- Первая проверка регистрации начинается примерно через 4 секунды, далее повторяется раз в 2 секунды.
-- Пока подтверждения нет, снизу показываются понятные статусы ожидания.
-- После подтверждения открывается экран «Поздравляем с регистрацией» с одной кнопкой `Войти в аккаунт`.
-- Кнопка `Войти в аккаунт` открывает штатный экран сохранения ключей с тремя галочками.
-- Стрелка назад в левом верхнем углу на обоих экранах возвращает в главное меню.
-- После успешного сохранения ключей регистрационный черновик и адрес кошелька очищаются.
-
-## Ожидаемый результат
-- Пользователь не видит преждевременное поздравление до подтверждения регистрации в Solana.
-- Финальный успех показывается только после появления подтверждения регистрации.
-- Вход после регистрации идёт через тот же стандартный сценарий сохранения ключей, что и в обычном login-flow.
-
-## Статус
-- pending
diff --git a/docs/Pending_Features/README.md b/docs/Pending_Features/README.md
deleted file mode 100644
index 071183a8..00000000
--- a/docs/Pending_Features/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Недопроверенные фичи
-
-Эта папка хранит список доработок, которые уже реализованы, но ещё не подтверждены ручной проверкой.
-
-## Как использовать
-
-1. При каждом коммите с новыми пользовательскими фичами (если нужна ручная проверка) добавить новый файл:
- - формат: `YYYY-MM-DD_HHMM_.md`
- - название `` и текст файла по возможности писать на русском языке
-2. В файле указать:
- - что сделано;
- - как проверять;
- - ожидаемый результат;
- - текущий статус (`pending` / `in_progress` / `done`).
-3. После подтверждения работоспособности — удалить файл фичи из этой папки.
-
-## Важно
-
-- `README.md` не удаляется.
-- Количество недопроверенных фич = число файлов `*.md` в этой папке, кроме `README.md`.
diff --git a/docs/Pending_Features/вроде сделанное/2026-06-20_1839_registration_faq_and_12_words.md b/docs/Pending_Features/вроде сделанное/2026-06-20_1839_registration_faq_and_12_words.md
deleted file mode 100644
index 64ade24f..00000000
--- a/docs/Pending_Features/вроде сделанное/2026-06-20_1839_registration_faq_and_12_words.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Регистрация: FAQ и режим пароля из 12 слов
-
-- краткое описание:
- - на экране регистрации добавлен блок частых вопросов с переходом на отдельный экран справки;
- - добавлен альтернативный режим ввода пароля через 12 полей-слов в кошелёчном формате, которые склеиваются в одну строку без изменения API;
- - такой же режим добавлен и на экран входа по логину и паролю.
-
-- что проверять:
- - на стартовом экране открыть `Зарегистрироваться`;
- - убедиться, что внизу экрана есть кнопки FAQ;
- - открыть несколько вопросов и проверить возврат обратно на регистрацию;
- - включить галочку `Представить пароль в виде 12 слов`;
- - убедиться, что появляется сетка с нумерованными полями в 3 колонки;
- - ввести часть слов, перейти дальше и проверить, что шаг подтверждения и генерация ключей работают;
- - выключить галочку и проверить, что пароль остаётся собранным в одном поле;
- - открыть экран входа по паролю и повторить те же проверки для режима `12 слов`;
- - пройти регистрацию до шага оплаты без ошибок интерфейса.
-
-- ожидаемый результат:
- - FAQ открывается отдельным экраном и содержит понятные ответы;
- - режим `12 слов` не ломает регистрацию и вход и даёт тот же поток, что и обычный пароль;
- - пароль не отправляется в новом формате, а продолжает использоваться как одна строка.
-
-- статус:
- - pending
diff --git a/docs/Pending_Features/вроде сделанное/2026-06-20_2350_test_free_avatar_upload.md b/docs/Pending_Features/вроде сделанное/2026-06-20_2350_test_free_avatar_upload.md
deleted file mode 100644
index 39646d4c..00000000
--- a/docs/Pending_Features/вроде сделанное/2026-06-20_2350_test_free_avatar_upload.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Временная бесплатная загрузка аватара в Arweave
-
-- краткое описание фичи:
- Добавлены два временных `Test...` API для бесплатной загрузки маленьких аватаров в Arweave через серверный кошелёк с лимитом `3` загрузки на пользователя. В UI мастера смены аватара добавлен пункт `Залить аватар бесплатно`.
-
-- что именно проверять:
- 1. Пользователь с активной сессией открывает редактирование профиля.
- 2. По нажатию на аватар открывается мастер `Сменить аватар`.
- 3. В мастере есть пункт `Залить аватар бесплатно`.
- 4. До первой загрузки UI показывает остаток `3 из 3`.
- 5. Маленький JPEG/PNG/WebP после уменьшения до файла <= `128 KB` успешно уходит через `TestUploadFreeAvatar`.
- 6. После загрузки приходит `txId`, и аватар сохраняется в профиль как `avatar.ar`.
- 7. Остаток уменьшается: `2`, `1`, `0`.
- 8. На четвёртой попытке сервер отвечает понятной ошибкой про исчерпанный бесплатный лимит.
- 9. Если итоговый уменьшенный файл всё ещё > `128 KB`, UI не отправляет его и показывает понятную ошибку.
- 10. Если серверный Arweave JWK/path не настроен, UI получает понятную ошибку временной функции.
-
-- ожидаемый результат:
- - первые 3 маленькие аватарки загружаются через серверный Arweave-кошелёк;
- - после каждой успешной загрузки `ava` в профиле указывает на новый `txId`;
- - после исчерпания лимита дальнейшая бесплатная загрузка блокируется без записи в профиль;
- - обычная загрузка через свой Arweave-кошелёк продолжает работать отдельно.
-
-- статус:
- pending
diff --git a/docs/Pending_Features/вроде сделанное/2026-06-24_1825_unified_channels_list_without_stories.md b/docs/Pending_Features/вроде сделанное/2026-06-24_1825_unified_channels_list_without_stories.md
deleted file mode 100644
index 7e4eac91..00000000
--- a/docs/Pending_Features/вроде сделанное/2026-06-24_1825_unified_channels_list_without_stories.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# Общий список каналов без stories
-
-- Краткое описание:
- вкладка `Каналы` переведена на единый список без разделения на "мои" и "подписки".
- Название канала в списке теперь показывается как `login_владельца/название_канала`.
- Служебный канал `stories` скрыт из списка каналов, поиска, подписки и связанных UI-сценариев.
-
-- Что проверять:
- 1. Открыть вкладку `Каналы`.
- 2. Убедиться, что сразу показывается один общий список.
- 3. Проверить, что свои и чужие каналы отображаются вместе.
- 4. Проверить формат названий: `ownerLogin/channelName`.
- 5. Открыть свой канал и убедиться, что внутри сохраняется UI владельца.
- 6. Открыть чужой канал и убедиться, что внутри сохраняется UI подписчика.
- 7. Проверить, что `stories` не отображается:
- - в общем списке;
- - в поиске каналов;
- - в подписке на канал;
- - в списках выбора канала для репоста.
-
-- Ожидаемый результат:
- - вкладка `Каналы` больше не делится на два режима;
- - все видимые каналы идут единым списком;
- - `stories` нигде не виден и не предлагается пользователю;
- - переход в канал сохраняет корректный UI в зависимости от владельца.
-
-- Статус:
- `pending`
diff --git a/docs/Pending_Features/вроде сделанное/2026-06-26_1500_addblock_tmp_bch_recovery.md b/docs/Pending_Features/вроде сделанное/2026-06-26_1500_addblock_tmp_bch_recovery.md
deleted file mode 100644
index de2f63ef..00000000
--- a/docs/Pending_Features/вроде сделанное/2026-06-26_1500_addblock_tmp_bch_recovery.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Crash-safe запись обычного `AddBlock` через `tmp_bch`
-
-## Кратко
-
-Обычный `AddBlock` переведён на схему:
-
-1. сборка `.tmp_bch`;
-2. запись sidecar `.write_check` с `blockNumber` и `blockHash`;
-3. создание пустого marker `.write_pending`;
-4. SQL-транзакция;
-5. атомарная подмена `tmp -> main`;
-6. удаление временных файлов.
-
-## Что проверить
-
-1. Обычный `AddBlock` на свежей цепочке.
-2. Падение до SQL-commit:
- - должны остаться только временные файлы;
- - на старте они должны быть удалены.
-3. Падение после SQL-commit, но до `atomicReplaceBlockchainFile(...)`:
- - на старте recovery должен довести swap до конца.
-4. Падение после `atomicReplaceBlockchainFile(...)`, но до удаления marker/sidecar:
- - на старте recovery должен просто подчистить хвост.
-5. Сценарий без marker:
- - `tmp_bch` / `write_check` считаются мусором и удаляются.
-
-## Ожидаемый результат
-
-- БД и файловая версия цепочки остаются согласованными.
-- Повторный старт сервера не ломает chain и не требует ручной правки файлов.
-- `BlockchainTmpRecoveryOnStartup` корректно обрабатывает и живые остатки, и мусор.
-
-## Статус
-
-`pending`
-
-## Что уже сделано
-
-- В коде есть `tmp_bch`, `write_check` и `write_pending`.
-- `BlockchainWriter` пишет обычный `AddBlock` через временные артефакты.
-- `BlockchainTmpRecoveryOnStartup` умеет добивать или чистить незавершённую запись.
-
-## Что ещё перепроверить
-
-- ручной crash-test на тестовом сервере;
-- совместимость с уже существующими `resync_pending` marker-файлами;
-- отсутствие ложных срабатываний на старых временных файлах.
diff --git a/docs/Pending_Features/вроде сделанное/2026-06-26_1745_proverka_avariynykh_ostanovok.md b/docs/Pending_Features/вроде сделанное/2026-06-26_1745_proverka_avariynykh_ostanovok.md
deleted file mode 100644
index d6dbab0d..00000000
--- a/docs/Pending_Features/вроде сделанное/2026-06-26_1745_proverka_avariynykh_ostanovok.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Проверка аварийных остановок на разных этапах
-
-## Кратко
-
-Нужно отдельно проверить, как сервер восстанавливается после внезапной остановки:
-
-1. во время обычного `AddBlock` / `tmp_bch`-pipeline;
-2. во время `full resync` цепочки;
-3. во время startup recovery, если остановка произошла на предыдущем запуске;
-4. при обычном апгрейде сервиса без явного crash-сценария.
-
-## Что проверять
-
-1. Остановка сервиса до `commit` БД.
-2. Остановка сервиса после `commit`, но до замены `main.bch`.
-3. Остановка сервиса во время `BlockchainResyncCleanupDAO`.
-4. Остановка сервиса во время повторной загрузки цепочки по `GetBlockchainBlock`.
-5. Поведение при обычном `systemctl restart`, когда сервер сам должен добить recovery.
-
-## Ожидаемый результат
-
-- после старта сервер либо дочищает временные артефакты, либо завершает незаконченный `resync`;
-- не остаётся битых `.tmp_bch`, `.write_check`, `.write_pending`, `.resync_pending`;
-- БД и файлы цепочки остаются согласованными;
-- обычная работа сервера не стартует поверх незавершённого recovery.
-
-## Статус
-
-`pending`
diff --git a/docs/Solana_Architecture/README.md b/docs/Solana_Architecture/README.md
index d322f72b..55d37f97 100644
--- a/docs/Solana_Architecture/README.md
+++ b/docs/Solana_Architecture/README.md
@@ -105,7 +105,7 @@ DAO в текущем виде не является отдельной Anchor-
## Правило разделения с основным сервером
-Solana-модуль лежит в основном репозитории как отдельная папка `shine-solana/shine/`, но не подключается автоматически к сборке или деплою основного сервера SHiNE. Команды `deployServer` и `deployUI` не должны деплоить Anchor-программы. Solana build/deploy выполняется отдельно из папки `shine-solana/shine/` по локальным правилам модуля.
+Solana-модуль лежит в основном репозитории как отдельная папка `shine-solana/shine/`, но не подключается автоматически к сборке или деплою основного сервера SHiNE. Скрипты основного server/UI deploy из `deploy/scripts/` не должны деплоить Anchor-программы. Solana build/deploy выполняется отдельно из папки `shine-solana/shine/` по локальным правилам модуля.
## Движение денег
diff --git a/docs/deploy/README.md b/docs/deploy/README.md
deleted file mode 100644
index 0986ae42..00000000
--- a/docs/deploy/README.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Деплой SHiNE (шаблон)
-
-Этот раздел хранит актуальные инструкции по деплою.
-
-## Базовый сервер
-
-- SSH: `player@shineup.me`
-- Домен: `shineup.me`
-- Базовый путь: `/home/player`
-
-Для всех рабочих инструкций и скриптов использовать доменное имя `shineup.me`, а не фиксированный IP:
-
-- актуальный IP должен браться через DNS-резолв на момент подключения;
-- ручное дублирование IP в документации и deploy-скриптах не поддерживать.
-
-## Контуры деплоя
-
-- Production:
- - SSH: `player@shineup.me`
- - Домен: `shineup.me`
- - IP: `178.208.64.62`
-- Second production:
- - SSH: `player@193.8.215.70`
- - Домен: `server2.shineup.me`
- - IP: `193.8.215.70`
-## Локальные команды
-
-- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`
-- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`
-- Production server deploy: `./gradlew deployServerProduction`
-- Production UI deploy: `./gradlew deployUIProduction`
-- Локальный запуск: `./gradlew startLocal`
-
-## Отдельные тестовые VPS
-
-- Отдельный стенд с 4 devnet-серверами на одном VPS описан в:
- - `docs/deploy/test-server/README.md`
-
-## Политика подтверждений
-
-- `shineup.me` и `server2.shineup.me` считать production-контурами.
-- Любые изменения на production-контурах, включая deploy сервера, deploy UI, конфиги, перезапуски и миграции, делать только после отдельного явного подтверждения пользователя.
-## Second production deploy (`server2.shineup.me`)
-
-- Это второй production-сервер SHiNE.
-- Исторические задачи `deployServer`, `deployUI`, `deployServerTest2`, `deployUITest2` по-прежнему могут указывать сюда.
-- Серверный deploy не запускает JUnit/IT-тесты на удалённом сервере.
-- `deployServer` / `deployServerTest2` делают:
- - сборку fat-jar локально;
- - синхронизацию `data/` и `shine.sqlite` с production `shineup.me`;
- - перенос `application.properties` с production с поправкой `server.ui.indexPath` на `/home/player/SHiNE/shine-ui/index.html`;
- - установку `systemd` unit на `193.8.215.70`;
- - перезапуск `shine-server.service`;
- - установку/проверку Caddy для `server2.shineup.me`.
-- `deployUI` / `deployUITest2` публикуют UI в `/home/player/SHiNE/shine-ui` на `193.8.215.70`.
-- Важно: историческое имя `deployUITest2` вводит в заблуждение. По умолчанию эта задача деплоит на `server2.shineup.me`, а не на `t2.shineup.me`.
-
-## UI-деплой и Caddy (обязательно)
-
-- Целевая директория UI-деплоя: `/home/player/SHiNE/shine-ui`.
-- `Caddyfile` на сервере должен смотреть в ту же директорию через `root * /home/player/SHiNE/shine-ui`.
-- В `deploy_shine-PWA.sh` добавлена проверка: скрипт ищет блок `shineup.me { ... }` (или значение `EXPECTED_CADDY_SITE`) и проверяет `root` внутри этого блока.
-- Если `root` внутри целевого блока не совпадает, деплой прерывается с ошибкой.
-- Для ручного обхода проверки (только осознанно): `ALLOW_CADDY_MISMATCH=1 ./gradlew deployUI`.
-- При необходимости можно явно переопределить путь деплоя:
- - `REMOTE_UI_DIR=/нужный/путь ./gradlew deployUI`
- - `EXPECTED_CADDY_UI_ROOT=/нужный/путь ./gradlew deployUI`
- - `EXPECTED_CADDY_SITE=example.com ./gradlew deployUI`
-
-## Временные тестовые сайты Solana tickets
-
-- Для HTML UI программы `shine_payments` используется отдельный временный тестовый сайт.
-- Основной каталог публикации:
- - `/home/player/sites/test-solana-tickets.shineup.me`
-- Рабочие домены:
- - `https://test-solana-tickets.shineup.me`
- - `https://test-solana-tickets.shiningpeople.ru`
-- Назначение:
- - ручная проверка сценариев покупки билетов;
- - проверка DAO-инструментов и лимитов менеджеров;
- - проверка ручного добавления билетов и `step_payout`.
-- Эти сайты не считать основным UI SHiNE; это отдельная тестовая публикация под Solana-часть.
-
-### Важно для локального UI (history-router / Ctrl+F5)
-
-- Локальный UI **обязательно** поднимать только через `./gradlew startLocal`.
-- Эта задача запускает `scripts/local_spa_server.py`, который делает SPA fallback: любой неизвестный путь (`/m/...`, `/channel/...`) возвращает `index.html`.
-- Это обязательно для корректной работы `Ctrl+F5` на внутренних роутов без `404`.
-- Рабочий URL выводится задачей в консоль в формате: `http://localhost:/?localWsPort=`.
-
-## Обязательные правила
-
-1. Перед серверным деплоем проверить локально.
-2. При нестандартном деплое (другой хост, другая структура, ручные шаги) обязательно уточнить у пользователя, нужно ли обновить этот шаблон.
-3. Если деплой-процесс изменился, этот файл и файлы в `servers/` обновлять в том же коммите.
diff --git a/docs/deploy/servers/193.8.215.70_test2_main.md b/docs/deploy/servers/193.8.215.70_test2_main.md
deleted file mode 100644
index 3bab829d..00000000
--- a/docs/deploy/servers/193.8.215.70_test2_main.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Сервер `193.8.215.70` — второй production (`server2.shineup.me`)
-
-- Пользователь: `player`
-- Домен: `server2.shineup.me`
-- Логин сервера: `server2shineupme`
-- Каталог SHiNE: `/home/player/SHiNE`
-- UI публикация для Caddy: `/home/player/SHiNE/shine-ui`
-- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
-- Данные: `/home/player/SHiNE/shine-server/data/`
- - `shine.sqlite`
- - `*.bch`
-- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
-
-## Сервисы
-
-- `shine-server.service` (systemd)
-- `caddy.service` (systemd)
-
-## Статус
-
-- Это второй production-сервер SHiNE.
-- Исторические имена deploy-задач могут по-прежнему ссылаться на него как на `test2`.
-- В документации и в списке окружений считать его production-контуром.
-
-## Caddy
-
-- Конфиг: `/etc/caddy/Caddyfile`
-- Сайты:
- - `server2.shineup.me`
- - `agent.shiningpeople.ru`
-- Для `server2.shineup.me`:
- - `root * /home/player/SHiNE/shine-ui`
- - `try_files {path} /index.html`
- - `reverse_proxy /ws* -> 127.0.0.1:7070`
-
-## Deploy
-
-- Default server deploy:
- - `./gradlew deployServer`
- - `./gradlew deployServerTest2`
-- Default UI deploy:
- - `./gradlew deployUI`
- - `./gradlew deployUITest2`
diff --git a/docs/deploy/servers/shineup.me_main.md b/docs/deploy/servers/shineup.me_main.md
deleted file mode 100644
index 3e947382..00000000
--- a/docs/deploy/servers/shineup.me_main.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Сервер `shineup.me` — основной
-
-- SSH: `player@shineup.me`
-- Определение IP: через DNS-резолв домена `shineup.me` на момент подключения
-- Текущий IP на момент последнего обновления документа: `178.208.64.62`
-- Пользователь: `player`
-- Базовый путь: `/home/player`
-- Каталог SHiNE: `/home/player/SHiNE`
-- UI публикация: `/home/player/SHiNE/shine-ui`
-- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
-- Данные: `/home/player/SHiNE/shine-server/data/`
-- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
-
-## Сервисы
-
-- `shine-server.service` (systemd)
-- `caddy.service` (systemd)
-
-## Caddy
-
-- Активный конфиг (через systemd `ExecStart`): `/etc/caddy/Caddyfile`
-- Для UI:
- - `root * /home/player/SHiNE/shine-ui`
- - `try_files {path} /index.html` (SPA fallback)
- - no-cache заголовки
- - `reverse_proxy /ws* -> 127.0.0.1:7070`
-
-## Дополнительно
-
-- Для отдельной админки `shine_payments` используется каталог:
- - `/home/player/sites/test-solana-tickets.shineup.me`
-- Эта публикация используется как временный тестовый сайт для сценариев покупки билетов и выплат `shine_payments`.
-- Домены этой публикации:
- - `https://test-solana-tickets.shineup.me`
- - `https://test-solana-tickets.shiningpeople.ru`
-- Для всех deploy-скриптов и инструкций использовать именно `player@shineup.me`, без жёсткой фиксации IP.
-
-## Правило изменений
-
-- `shineup.me` — production.
-- Любые изменения на этом сервере делать только после отдельного явного подтверждения пользователя.
-
-## Deploy
-
-- Production deploy-задачи:
- - `./gradlew deployServerProduction`
- - `./gradlew deployUIProduction`
-- Default deploy-задачи `./gradlew deployServer` и `./gradlew deployUI` сюда больше не относятся.
diff --git a/docs/deploy/test-server/178.208.90.249_quad_devnet.md b/docs/deploy/test-server/178.208.90.249_quad_devnet.md
deleted file mode 100644
index adda81e1..00000000
--- a/docs/deploy/test-server/178.208.90.249_quad_devnet.md
+++ /dev/null
@@ -1,173 +0,0 @@
-# VPS `178.208.90.249` (`player`) — 4 тестовых SHiNE-сервера на devnet
-
-## Назначение
-
-Этот VPS используется как отдельный тестовый стенд для одновременного запуска 4 SHiNE-серверов:
-- `server_t1` → `https://t1.shineup.me`
-- `server_t2` → `https://t2.shineup.me`
-- `server_t3` → `https://t3.shineup.me`
-- `server_t4` → `https://t4.shineup.me`
-
-Все 4 инстанса работают через Solana `devnet`.
-
-## Каталоги
-
-Структура в домашней папке пользователя `player`:
-- `/home/player/t1/server`
-- `/home/player/t1/UI`
-- `/home/player/t2/server`
-- `/home/player/t2/UI`
-- `/home/player/t3/server`
-- `/home/player/t3/UI`
-- `/home/player/t4/server`
-- `/home/player/t4/UI`
-
-Дополнительно:
-- подробная памятка на самом сервере: `/home/player/Agents.md`
-
-## Порты и systemd
-
-Соответствие инстансов:
-- `t1` → localhost `7101` → `shine-t1.service`
-- `t2` → localhost `7102` → `shine-t2.service`
-- `t3` → localhost `7103` → `shine-t3.service`
-- `t4` → localhost `7104` → `shine-t4.service`
-
-Каждый unit запускает один и тот же jar:
-- `/home/player/tX/server/shine-server.jar`
-
-Рабочая директория каждого unit:
-- `/home/player/tX/server`
-
-Это важно, потому что сервер читает внешний `application.properties` именно из текущей рабочей директории.
-
-## Конфиги сервера
-
-У каждого инстанса свой файл:
-- `/home/player/tX/server/application.properties`
-
-Ключевые параметры в нём:
-- `server.port=710X`
-- `server.SHiNE.login=server_tX`
-- `db.path=data/shine.sqlite`
-- `solana.cluster=devnet`
-- `solana.rpcUrl=https://api.devnet.solana.com`
-- `server.ui.indexPath=/home/player/tX/UI/index.html`
-- `server.info.url=https://tX.shineup.me`
-- `server.info.origin=devnet`
-
-Важно:
-- Program ID SHiNE одинаковые для mainnet и devnet.
-- Поэтому для перевода инстанса на devnet достаточно правильных `solana.cluster` и `solana.rpcUrl`.
-
-## Конфиги UI
-
-У каждого домена лежит отдельная копия `shine-UI`.
-
-В каждой копии меняется `js/deploy-config.js`:
-- `defaultServerLogin=server_tX`
-- `defaultServerAddress=tX.shineup.me`
-- `defaultSolanaCluster=devnet`
-- `defaultSolanaEndpoint=https://api.devnet.solana.com`
-
-За счёт этого:
-- UI по умолчанию открывает именно свой сервер
-- `server-ui` формы по умолчанию используют именно `devnet`
-
-## Caddy
-
-Конфиг:
-- `/etc/caddy/Caddyfile`
-
-Логика по каждому сайту одинаковая:
-- статические файлы берутся из `/home/player/tX/UI`
-- путь `/ws` проксируется на `127.0.0.1:710X`
-
-Права доступа:
-- каталог `/home/player` и `/home/player/tX` должен быть доступен на `+x` для чтения через Caddy
-- все каталоги и файлы в `/home/player/tX/UI` должны быть читаемы пользователем `caddy`
-
-## Что уже проверено
-
-После настройки было подтверждено:
-- `https://t1.shineup.me` отвечает `HTTP 200`
-- `https://t2.shineup.me` отвечает `HTTP 200`
-- `https://t3.shineup.me` отвечает `HTTP 200`
-- `https://t4.shineup.me` отвечает `HTTP 200`
-- `Caddy` успешно выпустил сертификаты Let's Encrypt на все 4 домена
-- все 4 процесса Java слушают свои порты `7101..7104`
-- в логах есть строка `WS сервер запущен на ws://localhost:710X/ws`
-
-## Важный operational-нюанс
-
-Официальный `https://api.devnet.solana.com` может отдавать `HTTP 429`, если несколько инстансов одновременно делают bootstrap PDA/sync-серверов на старте.
-
-На практике это уже проявилось:
-- `t3` и `t4` подтянули `sync_servers` сразу
-- `t1` и `t2` на первом одновременном старте словили `429`
-- после последовательного рестарта `shine-t1` и `shine-t2` они тоже успешно сохранили `sync_servers`
-
-Если после очередного деплоя какой-то инстанс не подтянул `sync_servers`, сначала делать не массовый рестарт, а по одному:
-
-```bash
-sudo systemctl restart shine-t1
-sleep 8
-sudo systemctl restart shine-t2
-sleep 8
-sudo systemctl restart shine-t3
-sleep 8
-sudo systemctl restart shine-t4
-```
-
-## Как обновлять сервер
-
-1. Локально собрать jar:
-
-```bash
-./gradlew shadowJar
-```
-
-2. Залить `SHiNE-server/build/libs/shine-server.jar` в каждый нужный каталог:
-- `/home/player/t1/server/shine-server.jar`
-- `/home/player/t2/server/shine-server.jar`
-- `/home/player/t3/server/shine-server.jar`
-- `/home/player/t4/server/shine-server.jar`
-
-3. Если менялся runtime-конфиг, обновить соответствующий `application.properties`.
-
-4. Перезапускать сервисы лучше по одному, с паузой, чтобы снизить шанс `429` от devnet RPC.
-
-## Как обновлять UI
-
-1. Взять свежую локальную папку `shine-UI/`.
-2. Для каждой копии подставить свой `deploy-config.js`.
-3. Скопировать файлы в `/home/player/tX/UI/`.
-4. Проверить права чтения для Caddy.
-
-## Полезные команды на VPS
-
-Проверка сервисов:
-
-```bash
-sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
-```
-
-Перезапуск одного инстанса:
-
-```bash
-sudo systemctl restart shine-t1
-```
-
-Логи одного инстанса:
-
-```bash
-tail -n 200 /home/player/t1/server/logs/app.log
-sudo journalctl -u shine-t1 -n 200 --no-pager
-```
-
-Проверка Caddy:
-
-```bash
-sudo caddy validate --config /etc/caddy/Caddyfile
-curl -I https://t1.shineup.me
-```
diff --git a/docs/deploy/test-server/README.md b/docs/deploy/test-server/README.md
deleted file mode 100644
index 33f51f3b..00000000
--- a/docs/deploy/test-server/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Тестовый VPS с 4 SHiNE-серверами
-
-В этой папке описан отдельный тестовый VPS `178.208.90.249`, на котором подняты 4 независимых SHiNE-инстанса под доменами `t1.shineup.me` ... `t4.shineup.me`.
-
-Текущая основная схема:
-- пользователь на VPS: `player`
-- все 4 инстанса работают через `devnet`
-- каждый инстанс имеет свой каталог `server`, свой каталог `UI`, свой `systemd` unit и свой localhost-порт
-- внешний HTTPS и маршрутизацию `/ws` обслуживает один общий `Caddy`
-
-Детальное описание:
-- [178.208.90.249_quad_devnet.md](/home/ai/work/SHiNE/SHiNE-server-sha256/docs/deploy/test-server/178.208.90.249_quad_devnet.md)
diff --git a/docs/instructions/coturn-install.md b/docs/instructions/coturn-install.md
deleted file mode 100644
index a714882a..00000000
--- a/docs/instructions/coturn-install.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Установка TURN сервера (coturn)
-
-## 1. Что нужно
-- Сервер с публичным IP (в примере: `37.214.58.208`).
-- Доступ `root` по SSH.
-- Открытые порты в firewall:
- - `3478/tcp`
- - `3478/udp`
- - диапазон relay-портов, например `49160-49200/udp`
-
-## 2. Быстрая установка (рекомендуется)
-В проекте уже есть готовый скрипт:
-
-```bash
-sudo bash scripts/setup_turn_coturn.sh \
- --secret "CHANGE_ME_LONG_RANDOM_SECRET" \
- --realm "shineup.me"
-```
-
-Что делает скрипт:
-- ставит `coturn`;
-- включает режим `use-auth-secret` (временные логин/пароль);
-- пишет `/etc/turnserver.conf`;
-- включает и перезапускает сервис `coturn`.
-
-## 3. Проверка
-Проверить статус:
-
-```bash
-sudo systemctl status coturn --no-pager
-```
-
-Проверить, что порт слушается:
-
-```bash
-sudo ss -lntup | grep 3478
-```
-
-## 4. Ручная установка (если без скрипта)
-```bash
-sudo apt-get update
-sudo apt-get install -y coturn
-```
-
-Пример `/etc/turnserver.conf`:
-
-```conf
-listening-port=3478
-fingerprint
-lt-cred-mech
-use-auth-secret
-static-auth-secret=CHANGE_ME_LONG_RANDOM_SECRET
-realm=shineup.me
-external-ip=37.214.58.208
-listening-ip=0.0.0.0
-relay-ip=37.214.58.208
-min-port=49160
-max-port=49200
-no-cli
-simple-log
-```
-
-В `/etc/default/coturn`:
-
-```conf
-TURNSERVER_ENABLED=1
-```
-
-Запуск:
-
-```bash
-sudo systemctl enable coturn
-sudo systemctl restart coturn
-```
-
diff --git a/docs/instructions/turn-connect-to-shine-server.md b/docs/instructions/turn-connect-to-shine-server.md
deleted file mode 100644
index ababbc6b..00000000
--- a/docs/instructions/turn-connect-to-shine-server.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Подключение TURN к SHiNE-серверу
-
-Начиная с текущей версии, клиент звонков запрашивает ICE-конфиг у backend через WS-операцию `GetCallIceConfig` и использует её для `RTCPeerConnection`.
-
-## 1. Настройки backend
-Файл: `src/main/resources/application.properties`
-
-Ключи:
-
-```properties
-call.ice.stun.urls=stun:stun.l.google.com:19302
-call.ice.turn.urls=turn:37.214.58.208:3478?transport=udp,turn:37.214.58.208:3478?transport=tcp
-call.ice.turn.ttlSec=600
-call.ice.turn.userPrefix=shine
-call.ice.turn.sharedSecret=CHANGE_ME_LONG_RANDOM_SECRET
-
-# fallback (если не используете shared-secret)
-call.ice.turn.username=
-call.ice.turn.password=
-```
-
-## 2. Рекомендуемый режим (временные credentials)
-- На coturn и на SHiNE-сервере должен быть **одинаковый** secret:
- - coturn: `static-auth-secret=...`
- - SHiNE: `call.ice.turn.sharedSecret=...`
-- Тогда SHiNE выдаёт короткоживущие `turnUsername/turnPassword` (TTL).
-
-## 3. Fallback режим (статический логин/пароль)
-Если временные credentials не используются:
-
-```properties
-call.ice.turn.sharedSecret=
-call.ice.turn.username=turn_user
-call.ice.turn.password=turn_password
-```
-
-## 4. Деплой после изменения
-```bash
-./gradlew deployServerNoCleanNoTests
-./gradlew deployWEB
-```
-
-## 5. Проверка
-1. Авторизоваться двумя клиентами.
-2. Запустить звонок.
-3. Проверить, что звонок устанавливается даже в сети, где прямой P2P затруднён.
-4. Если TURN недоступен, клиент автоматически откатится к STUN-конфигу по умолчанию.
-
diff --git a/scripts/install_test2_caddyfile.sh b/scripts/install_test2_caddyfile.sh
deleted file mode 100644
index 4bcb6c4c..00000000
--- a/scripts/install_test2_caddyfile.sh
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
-TARGET_DOMAIN="${TARGET_DOMAIN:-server2.shineup.me}"
-REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}"
-REMOTE_CADDYFILE="${REMOTE_CADDYFILE:-/etc/caddy/Caddyfile}"
-
-TMP_DIR="$(mktemp -d)"
-cleanup() {
- rm -rf "$TMP_DIR"
-}
-trap cleanup EXIT
-
-cat >"$TMP_DIR/Caddyfile" </dev/null
-ssh "$TARGET_HOST" "sudo -n true"
-rsync -az "$TMP_DIR/Caddyfile" "$TARGET_HOST:/tmp/caddy-test2.new"
-ssh "$TARGET_HOST" "set -euo pipefail; \
- sudo mv -f /tmp/caddy-test2.new '$REMOTE_CADDYFILE'; \
- sudo chown root:root '$REMOTE_CADDYFILE'; \
- sudo caddy validate --config '$REMOTE_CADDYFILE'; \
- sudo systemctl restart caddy"
diff --git a/server-backup/README.md b/server-backup/README.md
deleted file mode 100644
index d40e59fe..00000000
--- a/server-backup/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# server-backup
-
-- `archive/` — локальные полные бэкапы по датам (не коммитятся).
-- `scheme/` — лёгкая схема восстановления (коммитится).
-- `backup-version.properties` — версии схемы и полного бэкапа.
-
-Текущий сервер-источник: `shineup.me`.
diff --git a/shine-UI/js/pages/registration-payment-view.js b/shine-UI/js/pages/registration-payment-view.js
index 02ccdb0b..8240524f 100644
--- a/shine-UI/js/pages/registration-payment-view.js
+++ b/shine-UI/js/pages/registration-payment-view.js
@@ -16,6 +16,7 @@ import {
} from '../services/solana-wallet-service.js';
import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js';
import {
+ checkLoginExistsOnSolana,
formatSolanaErrorDetails,
isUserAlreadyExistsSolanaError,
registerUserOnSolana,
@@ -24,8 +25,13 @@ import { defaultServerLogin } from '../deploy-config.js';
export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false };
const MIN_REQUIRED_SOL = 0.01;
-const AUTO_LOGIN_INITIAL_DELAY_MS = 10000;
+const AUTO_LOGIN_INITIAL_DELAY_MS = 1000;
const AUTO_LOGIN_RETRY_MS = 2000;
+const REGISTRATION_PROGRESS_DURATION_MS = 12000;
+const REGISTRATION_POLL_START_DELAY_MS = 4000;
+const REGISTRATION_POLL_INTERVAL_MS = 2000;
+const REGISTRATION_CONFIRM_TIMEOUT_MS = 25000;
+const REGISTRATION_SUCCESS_SETTLE_DELAY_MS = 2000;
const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => '');
function getExplorerClusterName(endpoint) {
@@ -60,6 +66,10 @@ function getCryptoRuntimeState() {
return { hasCrypto, hasGetRandomValues, hasSubtle, secureContext };
}
+function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+}
+
async function completeRegistrationLogin({ navigate, keyBundle }) {
await authService.reconnect(state.entrySettings.shineServer);
const result = await authService.createSessionForExistingUser(
@@ -299,7 +309,7 @@ export function render({ navigate }) {
}
}
- renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId });
+ renderSolanaRegistrationStage({ navigate, status, keyBundle, registrationTxId });
} catch (error) {
const message = toUserMessage(error, 'Не удалось завершить регистрацию.');
setAuthError(message);
@@ -345,6 +355,192 @@ export function render({ navigate }) {
return screen;
}
+function renderSolanaRegistrationStage({ navigate, status, keyBundle, registrationTxId = '' }) {
+ const screen = document.querySelector('section.stack');
+ if (!screen) return;
+ const headerBackButton = screen.querySelector('.page-header .header-left .icon-btn');
+ const card = screen.querySelector('.card.stack');
+ if (!card) return;
+ card.classList.add('registration-finish-card');
+
+ let progressTimerId = null;
+ let pollStartTimerId = null;
+ let pollTimerId = null;
+ let successTimerId = null;
+ let confirmationInFlight = false;
+ let stageClosed = false;
+ let successPending = false;
+ let timeoutShown = false;
+ const stageStartedAt = Date.now();
+ const txExplorerUrl = makeSolanaExplorerTxUrl(registrationTxId, state.entrySettings.solanaServer);
+
+ const title = document.createElement('h2');
+ title.className = 'registration-finish-title';
+ title.textContent = 'Идёт регистрация в блокчейне...';
+
+ const hint = document.createElement('p');
+ hint.className = 'auth-copy registration-finish-text';
+ hint.textContent = 'Ждём, пока транзакция будет полностью одобрена сетью Solana.';
+
+ const txIdLine = document.createElement('p');
+ txIdLine.className = 'meta-muted registration-finish-tx';
+ if (registrationTxId && txExplorerUrl) {
+ const txLabel = document.createElement('span');
+ txLabel.textContent = 'Tx ID регистрации: ';
+ const txLink = document.createElement('a');
+ txLink.className = 'registration-finish-tx-link';
+ txLink.href = txExplorerUrl;
+ txLink.target = '_blank';
+ txLink.rel = 'noopener noreferrer';
+ txLink.textContent = registrationTxId;
+ txIdLine.append(txLabel, txLink);
+ } else {
+ txIdLine.textContent = 'Tx ID регистрации: ожидаем подтверждённую подпись';
+ }
+
+ const progressWrap = document.createElement('div');
+ progressWrap.className = 'registration-progress';
+
+ const progressBar = document.createElement('div');
+ progressBar.className = 'registration-progress-bar';
+ progressWrap.append(progressBar);
+
+ const progress = document.createElement('p');
+ progress.className = 'meta-muted registration-finish-progress';
+ progress.textContent = 'Подготавливаем проверку подтверждения...';
+
+ const pollStatus = document.createElement('p');
+ pollStatus.className = 'meta-muted registration-finish-progress';
+ pollStatus.textContent = 'Через несколько секунд начнём проверять подтверждение регистрации.';
+
+ const stopTimers = () => {
+ if (progressTimerId) {
+ window.clearInterval(progressTimerId);
+ progressTimerId = null;
+ }
+ if (pollStartTimerId) {
+ window.clearTimeout(pollStartTimerId);
+ pollStartTimerId = null;
+ }
+ if (pollTimerId) {
+ window.clearInterval(pollTimerId);
+ pollTimerId = null;
+ }
+ if (successTimerId) {
+ window.clearTimeout(successTimerId);
+ successTimerId = null;
+ }
+ };
+
+ const updateVisualProgress = () => {
+ const elapsed = Date.now() - stageStartedAt;
+ let widthPercent = 0;
+ if (elapsed <= REGISTRATION_PROGRESS_DURATION_MS) {
+ const phaseRatio = clamp(elapsed / REGISTRATION_PROGRESS_DURATION_MS, 0, 1);
+ widthPercent = 100 * (1 - ((1 - phaseRatio) ** 1.85));
+ } else {
+ const extraRatio = 1 - Math.exp(-(elapsed - REGISTRATION_PROGRESS_DURATION_MS) / 9000);
+ widthPercent = 88 + (10 * clamp(extraRatio, 0, 1));
+ }
+ progressBar.style.width = `${clamp(widthPercent, 0, 98)}%`;
+
+ if (elapsed < REGISTRATION_POLL_START_DELAY_MS) {
+ progress.textContent = 'Отправили регистрацию в сеть. Даём транзакции несколько секунд на обработку.';
+ return;
+ }
+ if (elapsed < REGISTRATION_CONFIRM_TIMEOUT_MS) {
+ progress.textContent = 'Идёт ожидание подтверждения Solana. Индикатор может замедлиться, это нормально.';
+ return;
+ }
+ progress.textContent = 'Подтверждение затянулось. Продолжаем автоматическую проверку.';
+ };
+
+ const finalizeSuccess = () => {
+ if (stageClosed || successPending) return;
+ successPending = true;
+ stopTimers();
+ progress.textContent = 'Подтверждение получено. Завершаем регистрацию...';
+ pollStatus.textContent = 'Запись уже найдена в Solana, готовим финальный экран.';
+ status.style.display = 'none';
+ progressBar.style.transition = `width ${REGISTRATION_SUCCESS_SETTLE_DELAY_MS}ms ease-out`;
+ progressBar.style.width = '100%';
+ successTimerId = window.setTimeout(() => {
+ if (stageClosed) return;
+ stageClosed = true;
+ renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId });
+ }, REGISTRATION_SUCCESS_SETTLE_DELAY_MS);
+ };
+
+ const showTimeoutState = () => {
+ if (timeoutShown || stageClosed) return;
+ timeoutShown = true;
+ status.className = 'status-line is-unavailable';
+ status.textContent = 'Подтверждение не пришло вовремя. Возможно, регистрация уже прошла, а возможно ещё нет. Проверьте Tx ID ниже: мы продолжим автоматическую проверку.';
+ status.style.display = '';
+ pollStatus.textContent = 'Пока подтверждения нет. Подождите ещё немного, мы продолжаем проверять регистрацию.';
+ };
+
+ const tryCheckRegistration = async () => {
+ if (stageClosed || confirmationInFlight) return;
+ confirmationInFlight = true;
+ progress.textContent = 'Проверяем подтверждение регистрации в Solana...';
+ status.style.display = timeoutShown ? '' : 'none';
+ try {
+ const result = await checkLoginExistsOnSolana({
+ login: state.registrationDraft.login,
+ solanaEndpoint: state.entrySettings.solanaServer,
+ });
+ if (result?.exists) {
+ finalizeSuccess();
+ return;
+ }
+ pollStatus.textContent = 'Пока ещё не прошла, подождите ещё немного...';
+ if ((Date.now() - stageStartedAt) >= REGISTRATION_CONFIRM_TIMEOUT_MS) {
+ showTimeoutState();
+ }
+ } catch (error) {
+ console.warn('Registration confirmation check failed', toUserMessage(error, 'registration-confirmation'));
+ pollStatus.textContent = 'Проверяем регистрацию повторно...';
+ if ((Date.now() - stageStartedAt) >= REGISTRATION_CONFIRM_TIMEOUT_MS) {
+ showTimeoutState();
+ }
+ } finally {
+ confirmationInFlight = false;
+ }
+ };
+
+ if (headerBackButton) {
+ const replacement = headerBackButton.cloneNode(true);
+ replacement.addEventListener('click', () => {
+ stageClosed = true;
+ stopTimers();
+ navigate('start-view');
+ });
+ headerBackButton.replaceWith(replacement);
+ }
+
+ card.innerHTML = '';
+ status.style.display = 'none';
+ card.append(title, hint, txIdLine, progressWrap, progress, pollStatus, status);
+
+ updateVisualProgress();
+ progressTimerId = window.setInterval(() => {
+ if (stageClosed) return;
+ updateVisualProgress();
+ if ((Date.now() - stageStartedAt) >= REGISTRATION_CONFIRM_TIMEOUT_MS) {
+ showTimeoutState();
+ }
+ }, 160);
+
+ pollStartTimerId = window.setTimeout(() => {
+ if (stageClosed) return;
+ void tryCheckRegistration();
+ pollTimerId = window.setInterval(() => {
+ void tryCheckRegistration();
+ }, REGISTRATION_POLL_INTERVAL_MS);
+ }, REGISTRATION_POLL_START_DELAY_MS);
+}
+
function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = '' }) {
const screen = document.querySelector('section.stack');
if (!screen) return;
@@ -365,7 +561,7 @@ function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId =
const hint = document.createElement('p');
hint.className = 'auth-copy registration-finish-text';
- hint.textContent = 'Подождите 10 секунд, пока обновится транзакция вашей регистрации в блокчейне Solana. После этого вход в аккаунт произойдёт автоматически.';
+ hint.textContent = 'Регистрация подтверждена в блокчейне Solana. Сейчас выполним автоматический вход в аккаунт.';
const txIdLine = document.createElement('p');
txIdLine.className = 'meta-muted registration-finish-tx';
diff --git a/tools/understand-anything-lab/README.md b/tools/understand-anything-lab/README.md
index 19078330..82067cdf 100644
--- a/tools/understand-anything-lab/README.md
+++ b/tools/understand-anything-lab/README.md
@@ -48,7 +48,7 @@
## Что не делать без отдельного решения
- Не включать `Understand Anything` в Gradle-сборку.
-- Не добавлять его в `deployServer` или `deployUI`.
+- Не добавлять его в основной server/UI deploy из `deploy/scripts/`.
- Не переносить серверные модули в новую папку одновременно с этим экспериментом.
- Не включать auto-update hook через `/understand --auto-update`, пока не понятно, нужен ли граф в каждом commit.
@@ -62,4 +62,3 @@
```
До такого решения артефакты графа лучше считать локальными.
-