Навести порядок в deploy и документации проекта

This commit is contained in:
AidarKC
2026-07-20 17:28:35 +04:00
parent fa1f7358b7
commit d2f65b169c
99 changed files with 1035 additions and 1708 deletions
+3 -2
View File
@@ -13,6 +13,7 @@ build/
.kotlin .kotlin
### IntelliJ IDEA ### ### IntelliJ IDEA ###
.idea/
.idea/modules.xml .idea/modules.xml
.idea/jarRepositories.xml .idea/jarRepositories.xml
.idea/compiler.xml .idea/compiler.xml
@@ -102,8 +103,8 @@ ESP32/**/*.d
ESP32/**/*.a ESP32/**/*.a
# Полные серверные бэкапы (тяжёлые архивы, не коммитим) # Полные серверные бэкапы (тяжёлые архивы, не коммитим)
server-backup/archive/** deploy/backup/archive/**
!server-backup/archive/.gitkeep !deploy/backup/archive/.gitkeep
# Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим # Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим
.claude/ .claude/
-8
View File
@@ -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
Generated
-1
View File
@@ -1 +0,0 @@
shine-server-server
-10
View File
@@ -1,10 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" build-on-make="true" name="server:jar">
<output-path>$PROJECT_DIR$/out/artifacts/server_jar</output-path>
<root id="archive" name="server.jar">
<element id="directory" name="META-INF">
<element id="file-copy" path="$PROJECT_DIR$/META-INF/MANIFEST.MF" />
</element>
</root>
</artifact>
</component>
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/shine-server-blockchain" />
<option value="$PROJECT_DIR$/shine-server-config" />
<option value="$PROJECT_DIR$/shine-server-crypto" />
<option value="$PROJECT_DIR$/shine-server-db" />
<option value="$PROJECT_DIR$/shine-server-geo" />
<option value="$PROJECT_DIR$/shine-server-log" />
<option value="$PROJECT_DIR$/shine-server-net-protocol" />
<option value="$PROJECT_DIR$/shine-server-net-server" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17 (2)" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+10 -24
View File
@@ -36,7 +36,7 @@
- Модуль логически связан с SHiNE, но не должен автоматически подключаться к сборке или деплою основного сервера без отдельного решения. - Модуль логически связан с SHiNE, но не должен автоматически подключаться к сборке или деплою основного сервера без отдельного решения.
- В Solana-модуле действуют локальные инструкции `shine-solana/shine/AGENTS.md`; при изменениях внутри модуля сначала читать их. - В Solana-модуле действуют локальные инструкции `shine-solana/shine/AGENTS.md`; при изменениях внутри модуля сначала читать их.
- В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги. - В 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/сервере находится в: - Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в:
- `docs/Инициализация_Solana_регистрации/README.md` - `docs/Инициализация_Solana_регистрации/README.md`
- Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте. - Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте.
@@ -86,18 +86,17 @@
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется. - Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
## Deploy ## Deploy
- Все документы и заметки по деплою хранить в папке `Deploy/`. - Все документы, инструкции, backup-правила и скрипты деплоя хранить в папке `deploy/`.
- Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`). - Подробные правила для агента по деплою находятся в `deploy/AGENTS.md`; перед любым deploy читать этот файл.
- Второй production-хост SHiNE: `player@193.8.215.70` (`server2.shineup.me`). - Production-серверы SHiNE: `shineup.me` и `server2.shineup.me`.
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`). - Тестовые/devnet серверы SHiNE: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
- В deploy-документах и скриптах использовать домены, а не IP.
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке. - По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`. - Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя. - Любые изменения и любой деплой на production (`shineup.me` и `server2.shineup.me`) выполнять только после отдельного явного подтверждения пользователя.
- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `server2.shineup.me`. - Перед production deploy обязательно проверить/обновить бэкап в `deploy/backup/archive/`.
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`. - Если пользователь пишет просто `задеплой` без уточнения production/test, уточнить целевой контур; не выбирать production автоматически.
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`. - Deploy выполнять shell-скриптами из `deploy/scripts/`; Gradle deploy-задачи не использовать.
- Production server deploy: `./gradlew deployServerProduction`.
- Production UI deploy: `./gradlew deployUIProduction`.
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`). - Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя. - Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`. - Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
@@ -123,19 +122,6 @@
- `unknown_error` - `unknown_error`
- В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`. - В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
## Недопроверенные фичи (обязательно)
- Папка для учёта недопроверенных фич: `docs/Pending_Features/`.
- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `docs/Pending_Features/`.
- Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_<short-feature-name>.md`.
- Имена новых файлов и краткие описания фич по возможности писать на русском языке.
- Внутри файла обязательно указывать:
- краткое описание фичи;
- что именно проверять;
- ожидаемый результат;
- статус (например: `pending`, `in_progress`, `done`).
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
- В `docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
## Будущие фичи / TODO ## Будущие фичи / TODO
- Папка для задач, сознательно отложенных на будущее: `TODO/`. - Папка для задач, сознательно отложенных на будущее: `TODO/`.
- Точка входа по планам: `TODO/README.md`. - Точка входа по планам: `TODO/README.md`.
-93
View File
@@ -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`
-35
View File
@@ -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` делать только после отдельного подтверждения пользователя.
-124
View File
@@ -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
```
+5 -15
View File
@@ -54,21 +54,11 @@ shine-UI/server-ui.html
## Деплой ## Деплой
``` - Основные инструкции по деплою находятся в `../deploy/AGENTS.md`.
./gradlew deployServer - Deploy выполнять shell-скриптами из `../deploy/scripts/`.
./gradlew deployUI - Gradle deploy-задачи не использовать: Gradle остаётся для сборки и локального запуска.
``` - Любые изменения на production (`shineup.me`, `server2.shineup.me`) делать только после отдельного явного подтверждения пользователя.
- Перед production deploy обязательно обновить/проверить backup в `deploy/backup/archive/`.
Default deploy по умолчанию идёт на `server2.shineup.me` (`player@193.8.215.70`).
Production deploy:
```
./gradlew deployServerProduction
./gradlew deployUIProduction
```
Любые изменения на `shineup.me` делать только после отдельного явного подтверждения пользователя.
Логи на проде: Логи на проде:
- `/home/player/SHiNE/shine-server/logs/app.log` - `/home/player/SHiNE/shine-server/logs/app.log`
@@ -44,7 +44,7 @@ webpush.vapid.subject=mailto:admin@shine.local
# Тогда сервер будет выдавать временный username/password (TTL). # Тогда сервер будет выдавать временный username/password (TTL).
# ------------------------------------------------------------ # ------------------------------------------------------------
call.ice.stun.urls=stun:stun.l.google.com:19302 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.ttlSec=600
call.ice.turn.userPrefix=shine call.ice.turn.userPrefix=shine
call.ice.turn.sharedSecret= call.ice.turn.sharedSecret=
@@ -58,12 +58,24 @@ call.ice.turn.password=
# Каждый блок описывает один TURN-узел. Новые узлы добавляются по индексу. # Каждый блок описывает один TURN-узел. Новые узлы добавляются по индексу.
# Приоритет авторизации на узел: sharedSecret -> статические username/password. # Приоритет авторизации на узел: sharedSecret -> статические username/password.
# ------------------------------------------------------------ # ------------------------------------------------------------
call.ice.turn.servers.1.id=shineup-main-185 call.ice.turn.servers.1.id=turn1
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.urls=turn:turn1.shineup.me:3478?transport=udp,turn:turn1.shineup.me:3478?transport=tcp
call.ice.turn.servers.1.sharedSecret=def6d444734d380d2f67a9d345b1debf985eaba0973c343e392c060d97c30106 call.ice.turn.servers.1.sharedSecret=
call.ice.turn.servers.1.username= call.ice.turn.servers.1.username=
call.ice.turn.servers.1.password= 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 для тестирования соединений # Временные debug HTTP API для тестирования соединений
# true - endpoint'ы /debug/ws/* включены (только при наличии .debug-token) # true - endpoint'ы /debug/ws/* включены (только при наличии .debug-token)
@@ -35,5 +35,5 @@
## Какие документы потом обновить ## Какие документы потом обновить
- `Deploy/`; - `deploy/`;
- `docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления. - `docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
@@ -56,11 +56,9 @@
- Код формирования репоста в `auth-service.js` не удалён: его можно будет использовать как основу при возвращении к задаче. - Код формирования репоста в `auth-service.js` не удалён: его можно будет использовать как основу при возвращении к задаче.
- Код отображения target-полей и перехода к оригиналу не удалён: он нужен для будущей проверки и возможной совместимости с уже созданными тестовыми блоками. - Код отображения target-полей и перехода к оригиналу не удалён: он нужен для будущей проверки и возможной совместимости с уже созданными тестовыми блоками.
## Почему это не лежит в Pending_Features ## Почему это лежит в TODO
`docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки. Репосты сейчас не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому задача остаётся в TODO как будущая.
Репосты сейчас не подходят под этот статус: они не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому старый pending-файл удалён, а задача перенесена сюда как будущая.
## Что сделать при возврате к реализации ## Что сделать при возврате к реализации
@@ -86,7 +84,7 @@
- `docs/Blockchain/CHANGELOG.md`; - `docs/Blockchain/CHANGELOG.md`;
- `docs/API/04_Add_Block_to_Blockchain_API.md`; - `docs/API/04_Add_Block_to_Blockchain_API.md`;
- документы API чтения каналов/тредов, если изменятся поля ответа. - документы API чтения каналов/тредов, если изменятся поля ответа.
9. После реализации перенести задачу из `TODO/` в `docs/Pending_Features/` как фичу, требующую ручной проверки. 9. После реализации отдельно согласовать ручную проверку пользовательского сценария.
## Минимальный чек-лист ручной проверки в будущем ## Минимальный чек-лист ручной проверки в будущем
@@ -50,7 +50,7 @@
- `docs/Blockchain/`, если появятся или изменятся блоки баланса. - `docs/Blockchain/`, если появятся или изменятся блоки баланса.
- `docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат. - `docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
- `docs/API/`, если меняется серверный API. - `docs/API/`, если меняется серверный API.
- `docs/Pending_Features/` - добавить файл ручной проверки после реализации. - после реализации отдельно согласовать ручную проверку.
- Документацию Solana-регистрации, если баланс будет связан с Solana-модулем. - Документацию Solana-регистрации, если баланс будет связан с Solana-модулем.
## Минимальная проверка в будущем ## Минимальная проверка в будущем
@@ -37,9 +37,8 @@
## Что обновить при возврате ## Что обновить при возврате
- `docs/Pending_Features/README.md` - после реализации отдельно согласовать ручную проверку
- `shine-UI/js/pages/connect-device-view.js` - `shine-UI/js/pages/connect-device-view.js`
- `shine-UI/js/pages/device-qr-view.js` - `shine-UI/js/pages/device-qr-view.js`
- `shine-UI/js/services/qr-key-transfer-service.js` - `shine-UI/js/services/qr-key-transfer-service.js`
- документацию по ключам, если формат переноса меняется - документацию по ключам, если формат переноса меняется
@@ -58,7 +58,7 @@
## Документы, которые обновить при реализации ## Документы, которые обновить при реализации
- Документацию UI/кошельков, если такая есть. - Документацию UI/кошельков, если такая есть.
- `docs/Pending_Features/` - добавить файл ручной проверки после реализации. - после реализации отдельно согласовать ручную проверку.
- `docs/API/`, только если появится новый серверный API или логирование. - `docs/API/`, только если появится новый серверный API или логирование.
## Минимальная проверка ## Минимальная проверка
@@ -22,7 +22,7 @@
- `docs/Blockchain/README.md`; - `docs/Blockchain/README.md`;
- `docs/Blockchain/CHANGELOG.md`; - `docs/Blockchain/CHANGELOG.md`;
- документы deploy/секретов в `Deploy/`, если появятся новые параметры. - документы deploy/секретов в `deploy/`, если появятся новые параметры.
## Статус ## Статус
+2 -2
View File
@@ -1,2 +1,2 @@
client.version=1.2.330 client.version=1.2.331
server.version=1.2.302 server.version=1.2.303
+1 -55
View File
@@ -1,7 +1,7 @@
plugins { plugins {
id 'java' id 'java'
id 'application' id 'application'
проверь ещё id 'com.github.johnrengelman.shadow' version '8.1.1' id 'com.github.johnrengelman.shadow' version '8.1.1'
} }
def appVersionProps = new Properties() def appVersionProps = new Properties()
@@ -185,60 +185,6 @@ tasks.named('build') {
finalizedBy tasks.named('integrationTest') 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) { tasks.register('startLocal', Exec) {
group = "!!run" group = "!!run"
description = "Builds server, starts local WS server and local HTTP UI for end-to-end local testing" description = "Builds server, starts local WS server and local HTTP UI for end-to-end local testing"
+57
View File
@@ -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/<date>/MANIFEST.txt`.
- Секреты, `.env`, JWK, TURN shared secrets и приватные ключи не коммитить.
## Как деплоить
Сервер:
```bash
bash deploy/scripts/<target>_server.sh
```
UI:
```bash
bash deploy/scripts/<target>_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`.
@@ -1,17 +1,20 @@
# Локальный деплой SHiNE-agent-bot-coder (systemd, пользователь ai) # Локальный деплой SHiNE-agent-bot-coder (systemd, пользователь ai)
## Где находится сервис ## Где находится сервис
- Папка сервиса: `SHiNE-agent-bot-coder/` - Папка сервиса: `SHiNE-agent-bot-coder/`
- Systemd unit: `SHiNE-agent-bot-coder/scripts/systemd/shine-agent-bot-coder.service` - Systemd unit: `SHiNE-agent-bot-coder/scripts/systemd/shine-agent-bot-coder.service`
- Скрипт установки: `SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh` - Скрипт установки: `SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh`
## Предусловия ## Предусловия
1. Заполнен `.env` на основе `.env.example`. 1. Заполнен `.env` на основе `.env.example`.
2. Доступен рабочий Codex CLI: 2. Доступен рабочий Codex CLI:
- `/home/ai/.cache/JetBrains/IntelliJIdea2026.1/aia/codex/bin/codex-x86_64-unknown-linux-musl` - `/home/ai/.cache/JetBrains/IntelliJIdea2026.1/aia/codex/bin/codex-x86_64-unknown-linux-musl`
3. На машине установлен `systemd --user`. 3. На машине установлен `systemd --user`.
## Установка ## Установка
Из корня репозитория: Из корня репозитория:
```bash ```bash
@@ -19,18 +22,21 @@ bash SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh
``` ```
Скрипт: Скрипт:
1. проверяет наличие `python3`; 1. проверяет наличие `python3`;
2. копирует unit в `~/.config/systemd/user/`; 2. копирует unit в `~/.config/systemd/user/`;
3. делает `systemctl --user daemon-reload`; 3. делает `systemctl --user daemon-reload`;
4. включает автозапуск и стартует сервис. 4. включает автозапуск и стартует сервис.
## Проверка ## Проверка
```bash ```bash
systemctl --user status shine-agent-bot-coder --no-pager systemctl --user status shine-agent-bot-coder --no-pager
journalctl --user -u shine-agent-bot-coder -f journalctl --user -u shine-agent-bot-coder -f
``` ```
## Перезапуск после изменений ## Перезапуск после изменений
```bash ```bash
systemctl --user restart shine-agent-bot-coder systemctl --user restart shine-agent-bot-coder
``` ```
+60
View File
@@ -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=<secret-on-server-only>`;
- в 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-конфигу по умолчанию.
+59
View File
@@ -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/<date>/`. Если свежий бэкап не скопирован, deploy должен быть остановлен.
## Запрещено
- Не использовать IP как основной target deploy.
- Не возвращать старые deploy-алиасы с названием `test2`.
- Не считать `t2.shineup.me` вторым production: это test/devnet.
+78
View File
@@ -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
```
+113
View File
@@ -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/`.
+85
View File
@@ -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=<secret-on-server-only>
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 настройки, где они используются.
+61
View File
@@ -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`, рестартовать сервисы по одному с паузой.
+40
View File
@@ -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`.
@@ -1,7 +1,7 @@
# AGENTS для server-backup # AGENTS для deploy/backup
## Назначение ## Назначение
- Папка `server-backup/` хранит: - Папка `deploy/backup/` хранит:
- тяжёлые локальные бэкапы сервера (НЕ в git); - тяжёлые локальные бэкапы сервера (НЕ в git);
- лёгкую схему восстановления (в git), чтобы можно было поднять сервер даже без полного архива. - лёгкую схему восстановления (в git), чтобы можно было поднять сервер даже без полного архива.
@@ -11,19 +11,20 @@
- `backup-version.properties` — версия контура бэкапа. - `backup-version.properties` — версия контура бэкапа.
## Правила ## Правила
- Полный бэкап складывать только в `server-backup/archive/`. - Полный бэкап складывать только в `deploy/backup/archive/`.
- `server-backup/archive/**` не коммитить. - `deploy/backup/archive/**` не коммитить, кроме `.gitkeep`.
- Перед production deploy проверить, что свежий бэкап скопирован в `deploy/backup/archive/<дата>/` и есть `MANIFEST.txt`.
- Любое изменение схемы восстановления фиксировать в git. - Любое изменение схемы восстановления фиксировать в git.
- После обновления схемы увеличивать `backup.schema.version`. - После обновления схемы увеличивать `backup.schema.version`.
- После нового полного бэкапа увеличивать `backup.full.version`. - После нового полного бэкапа увеличивать `backup.full.version`.
## Как обновлять бэкап ## Как обновлять бэкап
1. Обновить схему: 1. Обновить схему:
- `bash server-backup/scheme/shineup.me/scripts/refresh_scheme.sh` - `bash deploy/backup/scheme/shineup.me/scripts/refresh_scheme.sh`
2. Сделать новый полный бэкап: 2. Сделать новый полный бэкап:
- `bash server-backup/scheme/shineup.me/scripts/backup_full.sh` - `bash deploy/backup/scheme/shineup.me/scripts/backup_full.sh`
3. Проверить `server-backup/archive/<дата>/MANIFEST.txt`. 3. Проверить `deploy/backup/archive/<дата>/MANIFEST.txt`.
4. Поднять версии в `server-backup/backup-version.properties`. 4. Поднять версии в `deploy/backup/backup-version.properties`.
## Как восстанавливать ## Как восстанавливать
- Смотреть `server-backup/scheme/shineup.me/docs/RESTORE.md`. - Смотреть `deploy/backup/scheme/shineup.me/docs/RESTORE.md`.
+9
View File
@@ -0,0 +1,9 @@
# deploy/backup
- `archive/` — локальные полные бэкапы по датам (не коммитятся, кроме `.gitkeep`).
- `scheme/` — лёгкая схема восстановления (коммитится).
- `backup-version.properties` — версии схемы и полного бэкапа.
Production deploy требует, чтобы перед запуском свежий бэкап был скопирован в `deploy/backup/archive/<date>/` и содержал `MANIFEST.txt`.
Основной сервер-источник: `shineup.me`.
@@ -706,4 +706,4 @@ syslog
#no-tlsv1 #no-tlsv1
#no-tlsv1_1 #no-tlsv1_1
#no-tlsv1_2 #no-tlsv1_2
static-auth-secret=def6d444734d380d2f67a9d345b1debf985eaba0973c343e392c060d97c30106 static-auth-secret=<secret-on-server-only>
@@ -12,18 +12,18 @@ sudo apt install -y rsync caddy coturn docker.io
``` ```
## 3. Восстановление файлов из полного бэкапа ## 3. Восстановление файлов из полного бэкапа
Предполагается, что полный бэкап лежит локально в `server-backup/archive/YYYY-MM-DD/`. Предполагается, что полный бэкап лежит локально в `deploy/backup/archive/YYYY-MM-DD/`.
```bash ```bash
rsync -a server-backup/archive/YYYY-MM-DD/home-player/SHiNE/ player@NEW_SERVER:/home/player/SHiNE/ rsync -a deploy/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 deploy/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 deploy/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/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 deploy/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 deploy/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 deploy/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/*.service player@NEW_SERVER:/tmp/
``` ```
Далее на новом сервере: Далее на новом сервере:
+81
View File
@@ -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/<date>/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" <<EOF
[Unit]
Description=SHiNE Server ($TARGET_DOMAIN)
After=network.target
[Service]
Type=simple
User=player
Group=player
WorkingDirectory=$REMOTE_SERVER_DIR
ExecStart=/usr/bin/java -Dserver.port=$SERVER_PORT -jar $REMOTE_SERVER_DIR/shine-server.jar
Restart=always
RestartSec=3
StandardOutput=append:$REMOTE_LOGS_DIR/app.log
StandardError=append:$REMOTE_LOGS_DIR/app.log
[Install]
WantedBy=multi-user.target
EOF
ssh "$REMOTE_HOST" "mkdir -p '$REMOTE_SERVER_DIR' '$REMOTE_DATA_DIR' '$REMOTE_LOGS_DIR'"
rsync -az --timeout=120 "$LOCAL_JAR" "$REMOTE_HOST:$REMOTE_SERVER_DIR/shine-server.jar"
rsync -az "$TMP_DIR/$REMOTE_SERVICE_NAME.service" "$REMOTE_HOST:/tmp/$REMOTE_SERVICE_NAME.service"
ssh "$REMOTE_HOST" "set -euo pipefail; \
sudo mv -f '/tmp/$REMOTE_SERVICE_NAME.service' '/etc/systemd/system/$REMOTE_SERVICE_NAME.service'; \
sudo chown root:root '/etc/systemd/system/$REMOTE_SERVICE_NAME.service'; \
sudo chown -R player:player '$REMOTE_SERVER_DIR'; \
touch '$REMOTE_LOGS_DIR/app.log'; \
sudo systemctl daemon-reload; \
sudo systemctl enable '$REMOTE_SERVICE_NAME'; \
sudo systemctl restart '$REMOTE_SERVICE_NAME'; \
sudo systemctl --no-pager --full status '$REMOTE_SERVICE_NAME' >/dev/null"
echo "Всё хорошо: сервер $TARGET_DOMAIN обновлён и сервис $REMOTE_SERVICE_NAME перезапущен"
@@ -1,16 +1,32 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
SRC_DIR="shine-UI" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REMOTE_HOST="${REMOTE_HOST:-player@shineup.me}" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}"
EXPECTED_CADDY_UI_ROOT="${EXPECTED_CADDY_UI_ROOT:-/home/player/SHiNE/shine-ui}" 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}" 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)" BUILD_VERSION="$(date -u +%Y%m%d%H%M%S)"
VERSION_FILE="VERSION.properties"
export BUILD_VERSION 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/<date>/MANIFEST.txt" >&2
exit 1
fi
fi
if [[ ! -f "$VERSION_FILE" ]]; then if [[ ! -f "$VERSION_FILE" ]]; then
echo "ERROR: version file not found: $VERSION_FILE" >&2 echo "ERROR: version file not found: $VERSION_FILE" >&2
@@ -24,18 +40,12 @@ if [[ -z "$CLIENT_VERSION" ]]; then
fi fi
export CLIENT_VERSION 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" DEPLOY_SOLANA_CLUSTER_NORMALIZED="$DEPLOY_SOLANA_CLUSTER"
if [[ "$DEPLOY_SOLANA_CLUSTER_NORMALIZED" == "mainnet" ]]; then if [[ "$DEPLOY_SOLANA_CLUSTER_NORMALIZED" == "mainnet" ]]; then
DEPLOY_SOLANA_CLUSTER_NORMALIZED="mainnet-beta" DEPLOY_SOLANA_CLUSTER_NORMALIZED="mainnet-beta"
fi fi
TMP_DIR="$(mktemp -d)"
cleanup() { cleanup() {
rm -rf "$TMP_DIR" rm -rf "$TMP_DIR"
} }
@@ -48,7 +58,7 @@ fi
echo "==> Preparing staged UI copy with build version: $BUILD_VERSION" echo "==> Preparing staged UI copy with build version: $BUILD_VERSION"
echo "==> Client version from $VERSION_FILE: $CLIENT_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"/ rsync -a "$SRC_DIR"/ "$TMP_DIR"/
DEPLOY_CONFIG_FILE="$TMP_DIR/js/deploy-config.js" 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" echo "WARN: proceeding due to ALLOW_CADDY_MISMATCH=1"
fi fi
echo "==> Preparing remote directory: $REMOTE_DIR" echo "==> Preparing remote directory: $REMOTE_UI_DIR"
ssh "$REMOTE_HOST" "sudo mkdir -p '$REMOTE_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 -rlvz --delete --omit-dir-times --no-perms --no-owner --no-group \
--rsync-path="sudo rsync" \ --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" echo "Всё хорошо: $TARGET_URL"
+10
View File
@@ -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"
+14
View File
@@ -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"
+10
View File
@@ -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"
@@ -10,4 +10,5 @@ DEPLOY_SERVER_ADDRESS="shineup.me" \
DEPLOY_SOLANA_CLUSTER="mainnet" \ DEPLOY_SOLANA_CLUSTER="mainnet" \
DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \ DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \
TARGET_URL="https://shineup.me" \ TARGET_URL="https://shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh" REQUIRE_BACKUP="1" \
bash "$(dirname "$0")/deploy_ui.sh"
@@ -5,10 +5,10 @@ set -euo pipefail
# Запускать НА TURN-сервере под root. # Запускать НА 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="" SECRET=""
REALM="shineup.me" REALM="turn1.shineup.me"
MIN_PORT="49160" MIN_PORT="49160"
MAX_PORT="49200" MAX_PORT="49200"
@@ -46,9 +46,12 @@ export DEBIAN_FRONTEND=noninteractive
apt-get update -y apt-get update -y
apt-get install -y coturn 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 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" PUBLIC_IP="0.0.0.0"
fi fi
+9
View File
@@ -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"
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \ REMOTE_HOST="player@t1.shineup.me" \
REMOTE_UI_DIR="/home/player/t1/UI" \ REMOTE_UI_DIR="/home/player/t1/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t1/UI" \ EXPECTED_CADDY_UI_ROOT="/home/player/t1/UI" \
EXPECTED_CADDY_SITE="t1.shineup.me" \ EXPECTED_CADDY_SITE="t1.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t1.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t1.shineup.me" \ TARGET_URL="https://t1.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh" bash "$(dirname "$0")/deploy_ui.sh"
+9
View File
@@ -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"
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \ REMOTE_HOST="player@t2.shineup.me" \
REMOTE_UI_DIR="/home/player/t2/UI" \ REMOTE_UI_DIR="/home/player/t2/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t2/UI" \ EXPECTED_CADDY_UI_ROOT="/home/player/t2/UI" \
EXPECTED_CADDY_SITE="t2.shineup.me" \ EXPECTED_CADDY_SITE="t2.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t2.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t2.shineup.me" \ TARGET_URL="https://t2.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh" bash "$(dirname "$0")/deploy_ui.sh"
+9
View File
@@ -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"
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \ REMOTE_HOST="player@t3.shineup.me" \
REMOTE_UI_DIR="/home/player/t3/UI" \ REMOTE_UI_DIR="/home/player/t3/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t3/UI" \ EXPECTED_CADDY_UI_ROOT="/home/player/t3/UI" \
EXPECTED_CADDY_SITE="t3.shineup.me" \ EXPECTED_CADDY_SITE="t3.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t3.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t3.shineup.me" \ TARGET_URL="https://t3.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh" bash "$(dirname "$0")/deploy_ui.sh"
+9
View File
@@ -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"
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \ REMOTE_HOST="player@t4.shineup.me" \
REMOTE_UI_DIR="/home/player/t4/UI" \ REMOTE_UI_DIR="/home/player/t4/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t4/UI" \ EXPECTED_CADDY_UI_ROOT="/home/player/t4/UI" \
EXPECTED_CADDY_SITE="t4.shineup.me" \ EXPECTED_CADDY_SITE="t4.shineup.me" \
@@ -10,4 +10,4 @@ DEPLOY_SERVER_ADDRESS="t4.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t4.shineup.me" \ TARGET_URL="https://t4.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh" bash "$(dirname "$0")/deploy_ui.sh"
-63
View File
@@ -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" <<EOF
[Unit]
Description=SHiNE Server
After=network.target
[Service]
Type=simple
User=player
Group=player
WorkingDirectory=$REMOTE_SERVER_DIR
ExecStart=/usr/bin/java -Dserver.port=7070 -jar $REMOTE_SERVER_DIR/shine-server.jar
Restart=always
RestartSec=3
StandardOutput=append:$REMOTE_LOGS_DIR/app.log
StandardError=append:$REMOTE_LOGS_DIR/app.log
[Install]
WantedBy=multi-user.target
EOF
TARGET_HOST="$TARGET_HOST" TARGET_DOMAIN="$TARGET_DOMAIN" REMOTE_UI_DIR="$REMOTE_UI_DIR" \
bash "$(dirname "$0")/scripts/install_test2_caddyfile.sh"
ssh "$TARGET_HOST" "mkdir -p '$REMOTE_SERVER_DIR' '$REMOTE_DATA_DIR' '$REMOTE_LOGS_DIR' '$REMOTE_UI_DIR'"
rsync -az --timeout=120 "$LOCAL_JAR" "$TARGET_HOST:$REMOTE_SERVER_DIR/shine-server.jar"
rsync -az "$TMP_DIR/shine-server.service" "$TARGET_HOST:/tmp/shine-server.service"
ssh "$TARGET_HOST" "set -euo pipefail; \
sudo mv -f /tmp/shine-server.service /etc/systemd/system/shine-server.service; \
sudo chown root:root /etc/systemd/system/shine-server.service; \
sudo chown -R player:player '$REMOTE_SERVER_DIR'; \
touch '$REMOTE_LOGS_DIR/app.log'; \
sudo systemctl daemon-reload; \
sudo systemctl enable '$REMOTE_SERVICE_NAME'; \
sudo systemctl restart '$REMOTE_SERVICE_NAME'"
@@ -1,24 +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}"
TARGET_HOST="$TARGET_HOST" TARGET_DOMAIN="$TARGET_DOMAIN" REMOTE_UI_DIR="$REMOTE_UI_DIR" \
bash "$(dirname "$0")/scripts/install_test2_caddyfile.sh"
REMOTE_HOST="$TARGET_HOST" \
REMOTE_UI_DIR="$REMOTE_UI_DIR" \
EXPECTED_CADDY_UI_ROOT="$REMOTE_UI_DIR" \
EXPECTED_CADDY_SITE="$TARGET_DOMAIN" \
DEPLOY_SERVER_LOGIN="server2shineupme" \
DEPLOY_SERVER_ADDRESS="$TARGET_DOMAIN" \
DEPLOY_SOLANA_CLUSTER="mainnet" \
DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \
TARGET_URL="https://$TARGET_DOMAIN" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
ssh "$TARGET_HOST" "sudo chmod o+x /home/player /home/player/SHiNE '$REMOTE_UI_DIR'; \
sudo find '$REMOTE_UI_DIR' -type d -exec chmod o+rx {} +; \
sudo find '$REMOTE_UI_DIR' -type f -exec chmod o+r {} +"
+2 -2
View File
@@ -189,7 +189,7 @@
3. Переносить эти изменения назад в код минимально необходимыми правками. 3. Переносить эти изменения назад в код минимально необходимыми правками.
4. Если из Figma следует уже не только визуальная, но и UX-логическая правка, отдельно проверить, что она согласована пользователем. 4. Если из Figma следует уже не только визуальная, но и UX-логическая правка, отдельно проверить, что она согласована пользователем.
## Когда нужно добавить заметку в Pending_Features ## Когда нужно отдельно согласовать ручную проверку
Если после изменения по Figma: Если после изменения по Figma:
- поменялась логика flow; - поменялась логика flow;
@@ -197,7 +197,7 @@
- нужен реальный прогон на test2; - нужен реальный прогон на test2;
- затронута интеграция с Solana; - затронута интеграция с Solana;
тогда нужно добавить файл в `docs/Pending_Features/`. тогда нужно отдельно согласовать ручную проверку с пользователем.
## Что пока не оформлено для Miro ## Что пока не оформлено для Miro
@@ -1,38 +0,0 @@
# Поддержать проект Сияние
Статус: `pending`
## Кратко
В `shine-UI/js/pages/wallet-view.js` добавлен новый раздел `Поддержать проект Сияние` с тремя входами:
1. купить билет;
2. посмотреть билет по номеру;
3. сгенерировать новую пару ключей.
## Что проверить
1. Открыть `Кошелёк`.
2. Перейти в `Поддержать проект Сияние`.
3. Проверить экран покупки:
- виден коэффициент;
- виден остаток лимита очереди 1;
- виден расчет в SOL;
- кнопка `Справка` открывает отдельный экран;
- покупка блокируется, если сумма больше остатка лимита.
4. Проверить экран просмотра:
- `12` ищется как билет очереди 1;
- `2-5` и `3 8` ищутся как билеты очередей 2 и 3;
- показываются статус, количество билетов до него и уже выплаченные значения.
5. Проверить генератор ключей:
- генерируется новая пара ключей;
- публичный и секретный ключи показываются;
- можно скопировать и скачать результат;
- дополнительный текст в поле необязателен.
## Ожидаемый результат
- Экран раздела поддержки открывается из `wallet-view`.
- Покупка билета выполняется по текущему курсу и с допуском 3%.
- По номеру билета показывается понятная сводка по очереди.
- Генерация ключей использует безопасный браузерный рандом и не требует сохранения секретного ключа.
@@ -1,20 +0,0 @@
# Promo-логины через продавцов в Solana
- краткое описание:
- в `shine_users` добавлена поддержка PDA продавцов красивых логинов, promo-подписи `shine_promo_v1:<login>` и отдельная автономная страница `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`
@@ -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 символов.
@@ -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 логика остаётся простой и компактной.
@@ -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)
@@ -1,17 +0,0 @@
# Итог звонка как DM от инициатора
- краткое описание фичи:
Вместо локальных `call-tech` записей итог звонка теперь отправляется как обычное DM-сообщение только от стороны, которая инициировала звонок.
- что именно проверять:
1. Успешный звонок: после завершения инициатор отправляет в чат DM вида `Звонок: 37с` или `Звонок: 2м 14с`.
2. Неуспешный звонок без ответа: инициатор отправляет `Звонил, но недозвонился: нет ответа`.
3. Неуспешный звонок, когда у адресата нет доставленных сессий: инициатор отправляет `Звонил, но недозвонился: абонент не в сети`.
4. Неуспешный звонок при проблеме соединения: инициатор отправляет `Звонил, но недозвонился: не удалось установить соединение`.
5. Старые локальные `call-tech` bubble про итог звонка больше не появляются ни у инициатора, ни у принимающей стороны.
- ожидаемый результат:
Итог звонка виден обеим сторонам как обычное DM-сообщение от инициатора, а локальные служебные записи про итог звонка больше не используются.
- статус:
pending
@@ -1,23 +0,0 @@
## Краткое описание
Сервер перестал валидировать внутреннюю crypto-структуру `body` у контентных DM `type=1/2`.
UI теперь не отбрасывает такие сообщения: если сообщение не удалось расшифровать, вместо текста показывается заглушка `Неудалось расшифровать сообщение`.
## Что проверять
- Отправка и приём обычных DM между штатными клиентами продолжают работать как раньше.
- Сообщение с незнакомым форматом `body` у `type=1/2` принимается сервером и доходит до клиента.
- В UI такое сообщение отображается в чате одной из ожидаемых заглушек:
- `Формат сообщения не поддерживается`
- `Не удалось расшифровать сообщение`
- `Сообщение повреждено`
- После выхода из аккаунта и повторного входа backlog с такими сообщениями тоже отображается с той же заглушкой.
- ACK доставки по сессии для такого сообщения не зацикливается и сообщение не приходит бесконечно повторно.
## Ожидаемый результат
Сервер выступает транспортом для opaque DM-body, а клиент при неудачной расшифровке показывает безопасный fallback вместо полного пропуска сообщения с различением основных причин.
## Статус
pending
@@ -1,28 +0,0 @@
## Краткое описание
В DM добавлен клиентский формат технических вставок в начале plaintext:
- `<SHiNE:reply;v=1;id=...>`
- `<SHiNE:call;v=1;status=...;...>`
UI скрывает такие вставки из текста сообщения, а call-вставки отображает специальным человекочитаемым видом.
Также добавлена защита от пользовательского текста, начинающегося с `<SHiNE:`, и исправлен безопасный рендер превью последнего сообщения в списке личных диалогов.
## Что проверять
- Если пользователь отправляет обычный текст, начинающийся с `<SHiNE:`, он уходит как `< SHiNE:` и отображается как обычный текст.
- DM с `<SHiNE:call...>` показывается в чате как специальное call-сообщение, а не как сырой техтекст.
- При исходящем звонке короче `5` секунд call-summary не отправляется вообще.
- В списке личных сообщений превью такого сообщения показывает человекочитаемый итог звонка.
- DM с `<SHiNE:reply...>Текст ответа` скрывает техблок и показывает только `Текст ответа`.
- В меню любого DM первым пунктом есть `Ответить`, и отправка из этого режима реально добавляет `<SHiNE:reply...>` в начало plaintext.
- Если reply-цель отсутствует, сообщение всё равно показывается как обычный текст ответа.
- Текст вида `<script>alert(1)</script>` или похожие HTML-теги не исполняются ни в чате, ни в списке личных сообщений, ни в списке каналов.
## Ожидаемый результат
Технические SHiNE-вставки работают только как управляющие метаданные UI, а отображаемый пользователю текст и превью остаются безопасными и не рендерят HTML.
## Статус
pending
@@ -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`
@@ -1,16 +0,0 @@
# Адаптация верхней панели экрана «Связи»
## Краткое описание
- Экран графа связей больше не расширяется за границы контейнера. Верхняя панель ограничена границами экрана, чтобы кнопки возврата, поиска и справки не обрезались на узких устройствах.
## Что проверить
- Открыть экран «Связи» на ширине около 320, 390 и 794 px.
- Убедиться, что кнопки возврата, «Найти» и «?» полностью видны и нажимаются.
- Убедиться, что фильтры и граф связей остаются доступными.
## Ожидаемый результат
- Верхние действия не выходят за левую или правую границу приложения.
- Заголовок при нехватке места сокращается, а не сдвигает кнопки за край.
## Статус
- `pending` — локальная проверка на ширине 320 и 794 px пройдена; нужна ручная проверка после публикации на тестовом или production-стенде.
@@ -1,16 +0,0 @@
# Кнопка поиска каналов
## Краткое описание
- Emoji-иконка поиска в верхней панели «Каналов» заменена на контурную кнопку-лупу в стиле интерфейса.
## Что проверить
- Открыть список каналов.
- Убедиться, что кнопка поиска видна рядом с кнопкой создания канала и имеет подсказку «Найти канал».
- Нажать кнопку и убедиться, что открывается прежнее окно поиска каналов.
## Ожидаемый результат
- Кнопка выглядит как часть общей тёмной панели и не использует emoji.
- Поиск каналов работает без изменений.
## Статус
- `pending` — локальная визуальная проверка и открытие окна поиска пройдены; нужна ручная проверка после публикации на тестовом или production-стенде.
@@ -1,20 +0,0 @@
# Локальный тестовый вход
## Краткое описание
- На `localhost`, `127.0.0.1` и `::1` доступна кнопка «Локальный тестовый вход».
- Она создаёт только локальный демо-сеанс `local-tester`; пароль, серверная сессия и production не используются.
- Экран личных сообщений и список каналов открывают автономные локальные состояния без запроса к серверу.
- Профиль заполняется демонстрационными данными, чтобы интерфейс можно было проверить без серверного пользователя.
## Что проверить
- Открыть локальный UI и нажать «Локальный тестовый вход».
- Проверить переход к списку сообщений и доступ к экранам профиля, каналов, связей и настроек.
- Обновить страницу и убедиться, что локальный сеанс сохраняется.
- Выйти из сеанса и убедиться, что снова открывается экран старта.
## Ожидаемый результат
- Локальный режим даёт возможность изучить интерфейс без реальной авторизации.
- На `shineup.me` и тестовых доменах кнопка отсутствует.
## Статус
- `pending` — локальный вход, переход к профилю и демонстрационные каналы проверены; нужна ручная проверка выхода из сеанса после публикации на тестовом или production-стенде.
@@ -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 и не требует горизонтального скролла.
@@ -1,20 +0,0 @@
## Кратко
- Переделан финальный flow регистрации: сначала экран ожидания подтверждения Solana с прогрессом и повторными проверками, потом отдельный экран успешной регистрации с кнопкой входа в стандартный экран сохранения ключей.
## Что проверять
- После отправки регистрации открывается экран ожидания с текстом про подтверждение Solana и кликабельным `Tx ID`.
- Прогресс-бар сначала идёт быстрее, потом заметно замедляется.
- Первая проверка регистрации начинается примерно через 4 секунды, далее повторяется раз в 2 секунды.
- Пока подтверждения нет, снизу показываются понятные статусы ожидания.
- После подтверждения открывается экран «Поздравляем с регистрацией» с одной кнопкой `Войти в аккаунт`.
- Кнопка `Войти в аккаунт` открывает штатный экран сохранения ключей с тремя галочками.
- Стрелка назад в левом верхнем углу на обоих экранах возвращает в главное меню.
- После успешного сохранения ключей регистрационный черновик и адрес кошелька очищаются.
## Ожидаемый результат
- Пользователь не видит преждевременное поздравление до подтверждения регистрации в Solana.
- Финальный успех показывается только после появления подтверждения регистрации.
- Вход после регистрации идёт через тот же стандартный сценарий сохранения ключей, что и в обычном login-flow.
## Статус
- pending
-20
View File
@@ -1,20 +0,0 @@
# Недопроверенные фичи
Эта папка хранит список доработок, которые уже реализованы, но ещё не подтверждены ручной проверкой.
## Как использовать
1. При каждом коммите с новыми пользовательскими фичами (если нужна ручная проверка) добавить новый файл:
- формат: `YYYY-MM-DD_HHMM_<short-feature-name>.md`
- название `<short-feature-name>` и текст файла по возможности писать на русском языке
2. В файле указать:
- что сделано;
- как проверять;
- ожидаемый результат;
- текущий статус (`pending` / `in_progress` / `done`).
3. После подтверждения работоспособности — удалить файл фичи из этой папки.
## Важно
- `README.md` не удаляется.
- Количество недопроверенных фич = число файлов `*.md` в этой папке, кроме `README.md`.
@@ -1,25 +0,0 @@
# Регистрация: FAQ и режим пароля из 12 слов
- краткое описание:
- на экране регистрации добавлен блок частых вопросов с переходом на отдельный экран справки;
- добавлен альтернативный режим ввода пароля через 12 полей-слов в кошелёчном формате, которые склеиваются в одну строку без изменения API;
- такой же режим добавлен и на экран входа по логину и паролю.
- что проверять:
- на стартовом экране открыть `Зарегистрироваться`;
- убедиться, что внизу экрана есть кнопки FAQ;
- открыть несколько вопросов и проверить возврат обратно на регистрацию;
- включить галочку `Представить пароль в виде 12 слов`;
- убедиться, что появляется сетка с нумерованными полями в 3 колонки;
- ввести часть слов, перейти дальше и проверить, что шаг подтверждения и генерация ключей работают;
- выключить галочку и проверить, что пароль остаётся собранным в одном поле;
- открыть экран входа по паролю и повторить те же проверки для режима `12 слов`;
- пройти регистрацию до шага оплаты без ошибок интерфейса.
- ожидаемый результат:
- FAQ открывается отдельным экраном и содержит понятные ответы;
- режим `12 слов` не ломает регистрацию и вход и даёт тот же поток, что и обычный пароль;
- пароль не отправляется в новом формате, а продолжает использоваться как одна строка.
- статус:
- pending
@@ -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
@@ -1,28 +0,0 @@
# Общий список каналов без stories
- Краткое описание:
вкладка `Каналы` переведена на единый список без разделения на "мои" и "подписки".
Название канала в списке теперь показывается как `login_владельца/название_канала`.
Служебный канал `stories` скрыт из списка каналов, поиска, подписки и связанных UI-сценариев.
- Что проверять:
1. Открыть вкладку `Каналы`.
2. Убедиться, что сразу показывается один общий список.
3. Проверить, что свои и чужие каналы отображаются вместе.
4. Проверить формат названий: `ownerLogin/channelName`.
5. Открыть свой канал и убедиться, что внутри сохраняется UI владельца.
6. Открыть чужой канал и убедиться, что внутри сохраняется UI подписчика.
7. Проверить, что `stories` не отображается:
- в общем списке;
- в поиске каналов;
- в подписке на канал;
- в списках выбора канала для репоста.
- Ожидаемый результат:
- вкладка `Каналы` больше не делится на два режима;
- все видимые каналы идут единым списком;
- `stories` нигде не виден и не предлагается пользователю;
- переход в канал сохраняет корректный UI в зависимости от владельца.
- Статус:
`pending`
@@ -1,47 +0,0 @@
# Crash-safe запись обычного `AddBlock` через `tmp_bch`
## Кратко
Обычный `AddBlock` переведён на схему:
1. сборка `<blockchainName>.tmp_bch`;
2. запись sidecar `<blockchainName>.write_check` с `blockNumber` и `blockHash`;
3. создание пустого marker `<blockchainName>.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-файлами;
- отсутствие ложных срабатываний на старых временных файлах.
@@ -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`
+1 -1
View File
@@ -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/` по локальным правилам модуля.
## Движение денег ## Движение денег
-95
View File
@@ -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:<WEB_PORT>/?localWsPort=<WS_PORT>`.
## Обязательные правила
1. Перед серверным деплоем проверить локально.
2. При нестандартном деплое (другой хост, другая структура, ручные шаги) обязательно уточнить у пользователя, нужно ли обновить этот шаблон.
3. Если деплой-процесс изменился, этот файл и файлы в `servers/` обновлять в том же коммите.
@@ -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`
-48
View File
@@ -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` сюда больше не относятся.
@@ -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
```
-12
View File
@@ -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)
-75
View File
@@ -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
```
@@ -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-конфигу по умолчанию.
-79
View File
@@ -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" <<EOF
{
auto_https disable_redirects
}
agent.shiningpeople.ru {
redir / /agent/index.html 308
redir /agent /agent/index.html 308
handle_path /agent/* {
reverse_proxy 127.0.0.1:8765
}
}
$TARGET_DOMAIN {
encode zstd gzip
@ws path /ws /ws/*
handle @ws {
reverse_proxy 127.0.0.1:7070
}
handle {
root * $REMOTE_UI_DIR
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"
}
}
}
:80 {
encode zstd gzip
@ws path /ws /ws/*
handle @ws {
reverse_proxy 127.0.0.1:7070
}
handle {
root * $REMOTE_UI_DIR
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"
}
}
}
EOF
ssh -o BatchMode=yes -o ConnectTimeout=20 "$TARGET_HOST" "echo SSH OK" >/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"
-7
View File
@@ -1,7 +0,0 @@
# server-backup
- `archive/` — локальные полные бэкапы по датам (не коммитятся).
- `scheme/` — лёгкая схема восстановления (коммитится).
- `backup-version.properties` — версии схемы и полного бэкапа.
Текущий сервер-источник: `shineup.me`.
+199 -3
View File
@@ -16,6 +16,7 @@ import {
} from '../services/solana-wallet-service.js'; } from '../services/solana-wallet-service.js';
import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js'; import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js';
import { import {
checkLoginExistsOnSolana,
formatSolanaErrorDetails, formatSolanaErrorDetails,
isUserAlreadyExistsSolanaError, isUserAlreadyExistsSolanaError,
registerUserOnSolana, registerUserOnSolana,
@@ -24,8 +25,13 @@ import { defaultServerLogin } from '../deploy-config.js';
export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false }; export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false };
const MIN_REQUIRED_SOL = 0.01; 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 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 }, () => ''); const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => '');
function getExplorerClusterName(endpoint) { function getExplorerClusterName(endpoint) {
@@ -60,6 +66,10 @@ function getCryptoRuntimeState() {
return { hasCrypto, hasGetRandomValues, hasSubtle, secureContext }; return { hasCrypto, hasGetRandomValues, hasSubtle, secureContext };
} }
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
async function completeRegistrationLogin({ navigate, keyBundle }) { async function completeRegistrationLogin({ navigate, keyBundle }) {
await authService.reconnect(state.entrySettings.shineServer); await authService.reconnect(state.entrySettings.shineServer);
const result = await authService.createSessionForExistingUser( 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) { } catch (error) {
const message = toUserMessage(error, 'Не удалось завершить регистрацию.'); const message = toUserMessage(error, 'Не удалось завершить регистрацию.');
setAuthError(message); setAuthError(message);
@@ -345,6 +355,192 @@ export function render({ navigate }) {
return screen; 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 = '' }) { function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = '' }) {
const screen = document.querySelector('section.stack'); const screen = document.querySelector('section.stack');
if (!screen) return; if (!screen) return;
@@ -365,7 +561,7 @@ function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId =
const hint = document.createElement('p'); const hint = document.createElement('p');
hint.className = 'auth-copy registration-finish-text'; hint.className = 'auth-copy registration-finish-text';
hint.textContent = 'Подождите 10 секунд, пока обновится транзакция вашей регистрации в блокчейне Solana. После этого вход в аккаунт произойдёт автоматически.'; hint.textContent = 'Регистрация подтверждена в блокчейне Solana. Сейчас выполним автоматический вход в аккаунт.';
const txIdLine = document.createElement('p'); const txIdLine = document.createElement('p');
txIdLine.className = 'meta-muted registration-finish-tx'; txIdLine.className = 'meta-muted registration-finish-tx';
+1 -2
View File
@@ -48,7 +48,7 @@
## Что не делать без отдельного решения ## Что не делать без отдельного решения
- Не включать `Understand Anything` в Gradle-сборку. - Не включать `Understand Anything` в Gradle-сборку.
- Не добавлять его в `deployServer` или `deployUI`. - Не добавлять его в основной server/UI deploy из `deploy/scripts/`.
- Не переносить серверные модули в новую папку одновременно с этим экспериментом. - Не переносить серверные модули в новую папку одновременно с этим экспериментом.
- Не включать auto-update hook через `/understand --auto-update`, пока не понятно, нужен ли граф в каждом commit. - Не включать auto-update hook через `/understand --auto-update`, пока не понятно, нужен ли граф в каждом commit.
@@ -62,4 +62,3 @@
``` ```
До такого решения артефакты графа лучше считать локальными. До такого решения артефакты графа лучше считать локальными.