35 changed files with 204 additions and 504 deletions
+3 -5
View File
@@ -86,20 +86,18 @@
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется. - Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
## Deploy ## Deploy
- Все документы и заметки по деплою хранить в папке `Dev_Docs/deploy/`. - Все документы и заметки по деплою хранить в папке `Deploy/`.
- Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`). - Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`).
- Основной test-хост SHiNE: `player@193.8.215.70` (`t.shineup.me`). - Второй production-хост SHiNE: `player@193.8.215.70` (`server2.shineup.me`).
- Резервный test-хост SHiNE: `player@93.170.12.154` (`test.shineup.me`).
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`). - Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке. - По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`. - Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя. - Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя.
- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `t.shineup.me`. - Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `server2.shineup.me`.
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`. - Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`.
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`. - Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`.
- Production server deploy: `./gradlew deployServerProduction`. - Production server deploy: `./gradlew deployServerProduction`.
- Production UI deploy: `./gradlew deployUIProduction`. - Production UI deploy: `./gradlew deployUIProduction`.
- Резервный test deploy на `test.shineup.me`: `./gradlew deployServerTest` и `./gradlew deployUITest`, но пока их не использовать без отдельной причины.
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`). - Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя. - Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`. - Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
+2 -5
View File
@@ -11,11 +11,9 @@
и и
- `player@193.8.215.70` - `player@193.8.215.70`
- домен `t.shineup.me` - домен `server2.shineup.me`
- IP `193.8.215.70` - IP `193.8.215.70`
Отдельно резервный хост `test.shineup.me` сейчас помечен как временно неработающий.
## 1. Основной production-хост ## 1. Основной production-хост
- SSH: `player@shineup.me` - SSH: `player@shineup.me`
@@ -55,7 +53,7 @@ Deploy:
## 2. Второй production-сервер ## 2. Второй production-сервер
- SSH: `player@193.8.215.70` - SSH: `player@193.8.215.70`
- домен: `t.shineup.me` - домен: `server2.shineup.me`
- IP: `193.8.215.70` - IP: `193.8.215.70`
- пользователь: `player` - пользователь: `player`
- базовый путь: `/home/player` - базовый путь: `/home/player`
@@ -89,7 +87,6 @@ Deploy:
Не production: Не production:
- `test.shineup.me` (`93.170.12.154`) — резервный хост, временно не работает
- `t1.shineup.me` - `t1.shineup.me`
- `t2.shineup.me` - `t2.shineup.me`
- `t3.shineup.me` - `t3.shineup.me`
+3 -9
View File
@@ -4,7 +4,7 @@
Эта папка служит краткой картой окружений: Эта папка служит краткой картой окружений:
- [TEST_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/TEST_SERVERS.md) — тестовые серверы и стенды; - [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_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/PRODUCTION_SERVERS.md) — production-контур и связанные публичные публикации.
Ниже краткая сводка. Ниже краткая сводка.
@@ -17,13 +17,8 @@
- IP `178.208.64.62` - IP `178.208.64.62`
- Второй production SHiNE: - Второй production SHiNE:
- `player@193.8.215.70` - `player@193.8.215.70`
- домен `t.shineup.me` - домен `server2.shineup.me`
- IP `193.8.215.70` - IP `193.8.215.70`
- Временно неработающий резервный test:
- `player@93.170.12.154`
- домен `test.shineup.me`
- IP `93.170.12.154`
## Отдельный quad-devnet стенд ## Отдельный quad-devnet стенд
На отдельном VPS `178.208.90.249` подняты 4 независимых test/devnet-инстанса: На отдельном VPS `178.208.90.249` подняты 4 независимых test/devnet-инстанса:
@@ -35,7 +30,6 @@
## Важно ## Важно
- Production-контура SHiNE сейчас два: `shineup.me` и `t.shineup.me`. - Production-контура SHiNE сейчас два: `shineup.me` и `server2.shineup.me`.
- `test.shineup.me` сейчас помечен как временно неработающий резервный test-хост.
- `t1..t4.shineup.me` — это отдельные тестовые/devnet-контуры, не production. - `t1..t4.shineup.me` — это отдельные тестовые/devnet-контуры, не production.
- Любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя. - Любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
+7 -33
View File
@@ -1,11 +1,11 @@
# Тестовые серверы SHiNE # Тестовые серверы SHiNE
Этот файл описывает тестовые контуры, которые сейчас фигурируют в проекте. Этот файл описывает тестовые стенды, которые сейчас фигурируют в проекте.
## 1. Исторический `test2`, теперь второй production-сервер ## 1. Исторический `test2`, теперь второй production-сервер
- SSH: `player@193.8.215.70` - SSH: `player@193.8.215.70`
- Домен: `t.shineup.me` - Домен: `server2.shineup.me`
- IP: `193.8.215.70` - IP: `193.8.215.70`
- Назначение: второй production-контур SHiNE - Назначение: второй production-контур SHiNE
@@ -33,35 +33,10 @@ Deploy:
- этот хост больше не считать test-контуром; - этот хост больше не считать test-контуром;
- исторические имена deploy-задач `deployServerTest2` / `deployUITest2` сохранены, но сам хост считать production; - исторические имена deploy-задач `deployServerTest2` / `deployUITest2` сохранены, но сам хост считать production;
- задача `deployUITest2` по умолчанию выкладывает UI на `server2.shineup.me`, а не на `t2.shineup.me`;
- при описании окружений перечислять его как второй production-сервер. - при описании окружений перечислять его как второй production-сервер.
## 2. Резервный test-сервер, временно неработающий ## 2. Отдельный quad-devnet стенд `t1..t4`
- SSH: `player@93.170.12.154`
- Домен: `test.shineup.me`
- IP: `93.170.12.154`
- Назначение: резервный test
Структура по проектным докам:
- каталог 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`
Deploy:
- `./gradlew deployServerTest`
- `./gradlew deployUITest`
Примечания:
- этот хост резервный;
- сейчас помечен как временно неработающий;
- использовать его без отдельной причины не нужно.
## 3. Отдельный quad-devnet стенд `t1..t4`
- VPS: `178.208.90.249` - VPS: `178.208.90.249`
- пользователь: `player` - пользователь: `player`
@@ -124,13 +99,12 @@ Operational-нюанс:
- при одновременных рестартах возможны `HTTP 429` от `api.devnet.solana.com`; - при одновременных рестартах возможны `HTTP 429` от `api.devnet.solana.com`;
- поэтому сервисы `shine-t1..shine-t4` лучше перезапускать по одному, с паузой. - поэтому сервисы `shine-t1..shine-t4` лучше перезапускать по одному, с паузой.
## 4. Что проверять первым делом ## 3. Что проверять первым делом
Для любого test-контура полезны такие быстрые проверки: Для любого test-контура полезны такие быстрые проверки:
```bash ```bash
curl -I https://t.shineup.me curl -I https://server2.shineup.me
curl -I https://test.shineup.me
curl -I https://t1.shineup.me curl -I https://t1.shineup.me
curl -I https://t2.shineup.me curl -I https://t2.shineup.me
curl -I https://t3.shineup.me curl -I https://t3.shineup.me
@@ -143,7 +117,7 @@ curl -I https://t4.shineup.me
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4 sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
``` ```
Для основного/резервного test: Для второго production-контура:
```bash ```bash
sudo systemctl --no-pager --full status shine-server caddy sudo systemctl --no-pager --full status shine-server caddy
+1 -10
View File
@@ -59,7 +59,7 @@ shine-UI/server-ui.html
./gradlew deployUI ./gradlew deployUI
``` ```
Default deploy по умолчанию идёт на `t.shineup.me` (`player@193.8.215.70`). Default deploy по умолчанию идёт на `server2.shineup.me` (`player@193.8.215.70`).
Production deploy: Production deploy:
@@ -70,15 +70,6 @@ Production deploy:
Любые изменения на `shineup.me` делать только после отдельного явного подтверждения пользователя. Любые изменения на `shineup.me` делать только после отдельного явного подтверждения пользователя.
Резервный test-контур:
```
./gradlew deployServerTest
./gradlew deployUITest
```
`test.shineup.me` считается резервным тестовым сервером и в обычный deploy не включается.
Логи на проде: Логи на проде:
- `/home/player/SHiNE/shine-server/logs/app.log` - `/home/player/SHiNE/shine-server/logs/app.log`
- `/home/player/SHiNE/shine-server/logs/call-delivery-events.log` - `/home/player/SHiNE/shine-server/logs/call-delivery-events.log`
@@ -35,6 +35,5 @@
## Какие документы потом обновить ## Какие документы потом обновить
- `Dev_Docs/deploy/`; - `Deploy/`;
- `Dev_Docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления. - `Dev_Docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
+2 -2
View File
@@ -1,2 +1,2 @@
client.version=1.2.313 client.version=1.2.324
server.version=1.2.292 server.version=1.2.296
+6 -21
View File
@@ -207,12 +207,12 @@ tasks.register('deployUIProduction', Exec) {
group = "!!deployment" group = "!!deployment"
description = "Production UI deploy: shineup.me (только после явного подтверждения)" description = "Production UI deploy: shineup.me (только после явного подтверждения)"
workingDir = rootDir workingDir = rootDir
commandLine 'bash', file('deploy_shine-PWA.sh').absolutePath commandLine 'bash', file('deploy_shine-ui_production_shineupme.sh').absolutePath
} }
tasks.register('deployServer', Exec) { tasks.register('deployServer', Exec) {
group = "!!deployment" group = "!!deployment"
description = "Default deploy server: t.shineup.me" description = "Default deploy server: server2.shineup.me"
dependsOn shadowJar dependsOn shadowJar
workingDir = rootDir workingDir = rootDir
environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath
@@ -221,38 +221,23 @@ tasks.register('deployServer', Exec) {
tasks.register('deployUI', Exec) { tasks.register('deployUI', Exec) {
group = "!!deployment" group = "!!deployment"
description = "Default deploy UI: t.shineup.me" description = "Default deploy UI: server2.shineup.me"
workingDir = rootDir workingDir = rootDir
commandLine 'bash', file('deploy_shine-ui_test2.sh').absolutePath commandLine 'bash', file('deploy_shine-ui_production_server2shineupme.sh').absolutePath
} }
tasks.register('deployServerTest2') { tasks.register('deployServerTest2') {
group = "!!deployment" group = "!!deployment"
description = "Явный алиас основного test deploy server: t.shineup.me" description = "Явный алиас второго production deploy server: server2.shineup.me"
dependsOn tasks.named('deployServer') dependsOn tasks.named('deployServer')
} }
tasks.register('deployUITest2') { tasks.register('deployUITest2') {
group = "!!deployment" group = "!!deployment"
description = "Явный алиас основного test deploy UI: t.shineup.me" description = "Явный алиас второго production deploy UI: server2.shineup.me"
dependsOn tasks.named('deployUI') dependsOn tasks.named('deployUI')
} }
tasks.register('deployServerTest', Exec) {
group = "!!deployment"
description = "Резервный test deploy: test.shineup.me (пока не использовать)"
dependsOn shadowJar
workingDir = rootDir
environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath
commandLine 'bash', file('deploy_shine-server_test.sh').absolutePath
}
tasks.register('deployUITest', Exec) {
group = "!!deployment"
description = "Резервный test UI deploy: test.shineup.me (пока не использовать)"
workingDir = rootDir
commandLine 'bash', file('deploy_shine-ui_test.sh').absolutePath
}
tasks.register('startLocal', Exec) { tasks.register('startLocal', Exec) {
group = "!!run" group = "!!run"
+13
View File
@@ -28,6 +28,13 @@ TARGET_URL="${TARGET_URL:-https://${EXPECTED_CADDY_SITE}}"
REMOTE_DIR="${REMOTE_UI_DIR}" REMOTE_DIR="${REMOTE_UI_DIR}"
DEPLOY_SERVER_LOGIN="${DEPLOY_SERVER_LOGIN:-}" DEPLOY_SERVER_LOGIN="${DEPLOY_SERVER_LOGIN:-}"
DEPLOY_SERVER_ADDRESS="${DEPLOY_SERVER_ADDRESS:-}" DEPLOY_SERVER_ADDRESS="${DEPLOY_SERVER_ADDRESS:-}"
DEPLOY_SOLANA_CLUSTER="${DEPLOY_SOLANA_CLUSTER:-}"
DEPLOY_SOLANA_ENDPOINT="${DEPLOY_SOLANA_ENDPOINT:-}"
DEPLOY_SOLANA_CLUSTER_NORMALIZED="$DEPLOY_SOLANA_CLUSTER"
if [[ "$DEPLOY_SOLANA_CLUSTER_NORMALIZED" == "mainnet" ]]; then
DEPLOY_SOLANA_CLUSTER_NORMALIZED="mainnet-beta"
fi
cleanup() { cleanup() {
rm -rf "$TMP_DIR" rm -rf "$TMP_DIR"
@@ -52,6 +59,12 @@ if [[ -f "$DEPLOY_CONFIG_FILE" ]]; then
if [[ -n "$DEPLOY_SERVER_ADDRESS" ]]; then if [[ -n "$DEPLOY_SERVER_ADDRESS" ]]; then
perl -0pi -e "s/export const defaultServerAddress = '.*?';/export const defaultServerAddress = '$DEPLOY_SERVER_ADDRESS';/s" "$DEPLOY_CONFIG_FILE" perl -0pi -e "s/export const defaultServerAddress = '.*?';/export const defaultServerAddress = '$DEPLOY_SERVER_ADDRESS';/s" "$DEPLOY_CONFIG_FILE"
fi fi
if [[ -n "$DEPLOY_SOLANA_CLUSTER_NORMALIZED" ]]; then
perl -0pi -e "s/export const defaultSolanaCluster = '.*?';/export const defaultSolanaCluster = '$DEPLOY_SOLANA_CLUSTER_NORMALIZED';/s" "$DEPLOY_CONFIG_FILE"
fi
if [[ -n "$DEPLOY_SOLANA_ENDPOINT" ]]; then
perl -0pi -e "s|export const defaultSolanaEndpoint = '.*?';|export const defaultSolanaEndpoint = '$DEPLOY_SOLANA_ENDPOINT';|s" "$DEPLOY_CONFIG_FILE"
fi
fi fi
INDEX_FILE="$TMP_DIR/index.html" INDEX_FILE="$TMP_DIR/index.html"
-128
View File
@@ -1,128 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
PROD_HOST="${PROD_HOST:-player@shineup.me}"
TEST_HOST="${TEST_HOST:-player@93.170.12.154}"
TARGET_DOMAIN="${TARGET_DOMAIN:-test.shineup.me}"
REMOTE_BASE="${REMOTE_BASE:-/home/player/SHiNE}"
REMOTE_SERVER_DIR="${REMOTE_SERVER_DIR:-$REMOTE_BASE/shine-server}"
REMOTE_UI_DIR="${REMOTE_UI_DIR:-$REMOTE_BASE/shine-ui}"
REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_SERVER_DIR/data}"
REMOTE_LOGS_DIR="${REMOTE_LOGS_DIR:-$REMOTE_SERVER_DIR/logs}"
REMOTE_SERVICE_NAME="${REMOTE_SERVICE_NAME:-shine-server}"
LOCAL_JAR="${LOCAL_JAR:-build/libs/shine-server.jar}"
PROD_DATA_DIR="${PROD_DATA_DIR:-/home/player/SHiNE/shine-server/data}"
PROD_APP_PROPS="${PROD_APP_PROPS:-/home/player/SHiNE/shine-server/application.properties}"
TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
require_file() {
local path="$1"
if [[ ! -f "$path" ]]; then
echo "ERROR: файл не найден: $path" >&2
exit 1
fi
}
echo "==> Проверка локального jar"
require_file "$LOCAL_JAR"
jar_size="$(stat -c %s "$LOCAL_JAR")"
if [[ "$jar_size" -lt 10485760 ]]; then
echo "ERROR: jar слишком маленький для fat-jar: $jar_size bytes" >&2
exit 1
fi
echo "==> Проверка SSH и sudo"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$PROD_HOST" "echo SSH OK" >/dev/null
ssh -o BatchMode=yes -o ConnectTimeout=10 "$TEST_HOST" "echo SSH OK" >/dev/null
ssh "$TEST_HOST" "sudo -n true"
echo "==> Подготовка Caddy для $TARGET_DOMAIN"
TEST_HOST="$TEST_HOST" \
TARGET_DOMAIN="$TARGET_DOMAIN" \
REMOTE_UI_DIR="$REMOTE_UI_DIR" \
bash "$(dirname "$0")/scripts/install_test_caddyfile.sh"
echo "==> Забираем продовые данные и application.properties"
mkdir -p "$TMP_DIR/data"
rsync -az --delete "$PROD_HOST:$PROD_DATA_DIR/" "$TMP_DIR/data/"
scp -p "$PROD_HOST:$PROD_APP_PROPS" "$TMP_DIR/application.properties" >/dev/null
if grep -q '^server\.ui\.indexPath=' "$TMP_DIR/application.properties"; then
perl -0pi -e 's@^server\.ui\.indexPath=.*$@server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html@m' "$TMP_DIR/application.properties"
else
printf '\nserver.ui.indexPath=/home/player/SHiNE/shine-ui/index.html\n' >>"$TMP_DIR/application.properties"
fi
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
echo "==> Останавливаем текущий сервер на тестовом хосте"
ssh "$TEST_HOST" "sudo systemctl stop $REMOTE_SERVICE_NAME || true"
echo "==> Создаём каталоги"
ssh "$TEST_HOST" "mkdir -p '$REMOTE_SERVER_DIR' '$REMOTE_DATA_DIR' '$REMOTE_LOGS_DIR' '$REMOTE_UI_DIR' '$REMOTE_BASE/caddy'"
echo "==> Копируем продовую БД и blockchain-данные"
rsync -az --delete "$TMP_DIR/data/" "$TEST_HOST:$REMOTE_DATA_DIR/"
echo "==> Загружаем новый jar и конфиг"
rsync -az --timeout=120 "$LOCAL_JAR" "$TEST_HOST:$REMOTE_SERVER_DIR/shine-server.jar.new"
rsync -az --timeout=30 "$TMP_DIR/application.properties" "$TEST_HOST:$REMOTE_SERVER_DIR/application.properties.new"
rsync -az --timeout=30 "$TMP_DIR/shine-server.service" "$TEST_HOST:/tmp/shine-server.service.new"
echo "==> Применяем systemd unit и файлы сервера"
ssh "$TEST_HOST" "set -euo pipefail; \
mv -f '$REMOTE_SERVER_DIR/shine-server.jar.new' '$REMOTE_SERVER_DIR/shine-server.jar'; \
mv -f '$REMOTE_SERVER_DIR/application.properties.new' '$REMOTE_SERVER_DIR/application.properties'; \
sudo mv -f /tmp/shine-server.service.new /etc/systemd/system/shine-server.service; \
sudo chown root:root /etc/systemd/system/shine-server.service; \
chmod 644 '$REMOTE_SERVER_DIR/application.properties'; \
chmod 664 '$REMOTE_SERVER_DIR/shine-server.jar'; \
mkdir -p '$REMOTE_LOGS_DIR'; \
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'"
echo "==> Ждём порт 7070"
for _ in $(seq 1 50); do
if ssh "$TEST_HOST" "ss -ltn '( sport = :7070 )' | grep -q 7070"; then
echo "==> Порт 7070 поднялся"
break
fi
sleep 1
done
if ! ssh "$TEST_HOST" "ss -ltn '( sport = :7070 )' | grep -q 7070"; then
echo "ERROR: тестовый сервер не поднял порт 7070" >&2
exit 1
fi
echo "==> Проверяем статус сервиса"
ssh "$TEST_HOST" "sudo systemctl --no-pager --full status '$REMOTE_SERVICE_NAME' | sed -n '1,20p'"
echo "test_server_deploy_done"
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail set -euo pipefail
TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}" TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
TARGET_DOMAIN="${TARGET_DOMAIN:-t.shineup.me}" TARGET_DOMAIN="${TARGET_DOMAIN:-server2.shineup.me}"
REMOTE_BASE="${REMOTE_BASE:-/home/player/SHiNE}" REMOTE_BASE="${REMOTE_BASE:-/home/player/SHiNE}"
REMOTE_SERVER_DIR="${REMOTE_SERVER_DIR:-$REMOTE_BASE/shine-server}" REMOTE_SERVER_DIR="${REMOTE_SERVER_DIR:-$REMOTE_BASE/shine-server}"
REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_SERVER_DIR/data}" REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_SERVER_DIR/data}"
@@ -2,7 +2,7 @@
set -euo pipefail set -euo pipefail
TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}" TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
TARGET_DOMAIN="${TARGET_DOMAIN:-t.shineup.me}" TARGET_DOMAIN="${TARGET_DOMAIN:-server2.shineup.me}"
REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}" 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" \ TARGET_HOST="$TARGET_HOST" TARGET_DOMAIN="$TARGET_DOMAIN" REMOTE_UI_DIR="$REMOTE_UI_DIR" \
@@ -12,8 +12,10 @@ REMOTE_HOST="$TARGET_HOST" \
REMOTE_UI_DIR="$REMOTE_UI_DIR" \ REMOTE_UI_DIR="$REMOTE_UI_DIR" \
EXPECTED_CADDY_UI_ROOT="$REMOTE_UI_DIR" \ EXPECTED_CADDY_UI_ROOT="$REMOTE_UI_DIR" \
EXPECTED_CADDY_SITE="$TARGET_DOMAIN" \ EXPECTED_CADDY_SITE="$TARGET_DOMAIN" \
DEPLOY_SERVER_LOGIN="tshineupme" \ DEPLOY_SERVER_LOGIN="server2shineupme" \
DEPLOY_SERVER_ADDRESS="$TARGET_DOMAIN" \ DEPLOY_SERVER_ADDRESS="$TARGET_DOMAIN" \
DEPLOY_SOLANA_CLUSTER="mainnet" \
DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \
TARGET_URL="https://$TARGET_DOMAIN" \ TARGET_URL="https://$TARGET_DOMAIN" \
bash "$(dirname "$0")/deploy_shine-PWA.sh" bash "$(dirname "$0")/deploy_shine-PWA.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_HOST="player@shineup.me" \
REMOTE_UI_DIR="/home/player/SHiNE/shine-ui" \
EXPECTED_CADDY_UI_ROOT="/home/player/SHiNE/shine-ui" \
EXPECTED_CADDY_SITE="shineup.me" \
DEPLOY_SERVER_LOGIN="shineupme" \
DEPLOY_SERVER_ADDRESS="shineup.me" \
DEPLOY_SOLANA_CLUSTER="mainnet" \
DEPLOY_SOLANA_ENDPOINT="https://solana-rpc.publicnode.com" \
TARGET_URL="https://shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \
REMOTE_UI_DIR="/home/player/t1/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t1/UI" \
EXPECTED_CADDY_SITE="t1.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t1" \
DEPLOY_SERVER_ADDRESS="t1.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t1.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \
REMOTE_UI_DIR="/home/player/t2/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t2/UI" \
EXPECTED_CADDY_SITE="t2.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t2" \
DEPLOY_SERVER_ADDRESS="t2.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t2.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \
REMOTE_UI_DIR="/home/player/t3/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t3/UI" \
EXPECTED_CADDY_SITE="t3.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t3" \
DEPLOY_SERVER_ADDRESS="t3.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t3.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_HOST="player@178.208.90.249" \
REMOTE_UI_DIR="/home/player/t4/UI" \
EXPECTED_CADDY_UI_ROOT="/home/player/t4/UI" \
EXPECTED_CADDY_SITE="t4.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t4" \
DEPLOY_SERVER_ADDRESS="t4.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
TARGET_URL="https://t4.shineup.me" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
-20
View File
@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
TEST_HOST="${TEST_HOST:-player@93.170.12.154}"
TARGET_DOMAIN="${TARGET_DOMAIN:-test.shineup.me}"
REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}"
echo "==> Подготовка Caddy для $TARGET_DOMAIN"
TEST_HOST="$TEST_HOST" \
TARGET_DOMAIN="$TARGET_DOMAIN" \
REMOTE_UI_DIR="$REMOTE_UI_DIR" \
bash "$(dirname "$0")/scripts/install_test_caddyfile.sh"
echo "==> Деплой UI на $TARGET_DOMAIN"
REMOTE_HOST="$TEST_HOST" \
REMOTE_UI_DIR="$REMOTE_UI_DIR" \
EXPECTED_CADDY_UI_ROOT="$REMOTE_UI_DIR" \
EXPECTED_CADDY_SITE="$TARGET_DOMAIN" \
TARGET_URL="https://$TARGET_DOMAIN" \
bash "$(dirname "$0")/deploy_shine-PWA.sh"
+1 -1
View File
@@ -48,7 +48,7 @@
# 2026-06-26 17:45:18 +0400 # 2026-06-26 17:45:18 +0400
- Базовый коммит-ориентир: `44a1ba0`. - Базовый коммит-ориентир: `44a1ba0`.
- На `t.shineup.me` подтверждена рабочая схема startup sync и full-resync: - На `server2.shineup.me` подтверждена рабочая схема startup sync и full-resync:
- после рестарта сервер добивает `BlockchainTmpRecovery` и `BlockchainResyncRecovery`; - после рестарта сервер добивает `BlockchainTmpRecovery` и `BlockchainResyncRecovery`;
- `aidartest-001` успешно подтягивается с `shineup.me`; - `aidartest-001` успешно подтягивается с `shineup.me`;
- итоговое локальное состояние по `aidartest-001` дошло до `last_block_number=13`. - итоговое локальное состояние по `aidartest-001` дошло до `last_block_number=13`.
+1 -1
View File
@@ -244,7 +244,7 @@ Full resync запускается только тогда, когда:
### 8.1 Практическая проверка на тестовом сервере ### 8.1 Практическая проверка на тестовом сервере
Проверка на `t.shineup.me` показала, что текущая схема действительно поднимает цепочку при старте: Проверка на `server2.shineup.me` показала, что текущая схема действительно поднимает цепочку при старте:
- после рестарта сервер сначала проходит `BlockchainTmpRecovery`; - после рестарта сервер сначала проходит `BlockchainTmpRecovery`;
- затем обрабатывает `BlockchainResyncRecovery`; - затем обрабатывает `BlockchainResyncRecovery`;
@@ -36,4 +36,4 @@
## Примечание ## Примечание
Подробная схема размещения описана в: Подробная схема размещения описана в:
- [Dev_Docs/deploy/test-server/178.208.90.249_quad_devnet.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Dev_Docs/deploy/test-server/178.208.90.249_quad_devnet.md) - [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,15 +0,0 @@
# Регистрация: глаз у пароля, автопроверка логина и сохранение связки с 12 словами
- краткое описание:
На экране регистрации добавлены кнопка-глаз у поля пароля, автопроверка логина с задержкой около 1.5 секунды и сохранение связки между обычным полем пароля и режимом `12 слов`, пока пользователь вручную не изменит пароль.
- что проверять:
Открыть экран регистрации и убедиться, что справа от поля пароля есть квадратная кнопка-глаз, которая переключает скрытый и открытый показ пароля.
Включить режим `Представить пароль в виде 12 слов`, заполнить несколько или все слова, затем выключить режим и проверить, что в поле пароля появился склеенный пароль.
Снова включить режим `12 слов` без ручного редактирования пароля и проверить, что ранее введённые слова восстановились.
После этого вручную изменить пароль в обычном поле и снова включить режим `12 слов` — набор слов должен быть очищен.
Ввести логин и перестать печатать примерно на 1.5 секунды — под полем логина должен автоматически появиться актуальный статус доступности.
Проверить, что кнопка `Проверить логин` находится ниже статуса, а текст про частые вопросы снизу страницы отсутствует.
- ожидаемый результат:
Регистрация ведёт себя предсказуемо: пароль можно показать или скрыть, слова не теряются при простом скрытии режима `12 слов`, а статус логина обновляется автоматически и вручную.
- статус:
pending
+6 -21
View File
@@ -21,35 +21,26 @@
- IP: `178.208.64.62` - IP: `178.208.64.62`
- Second production: - Second production:
- SSH: `player@193.8.215.70` - SSH: `player@193.8.215.70`
- Домен: `t.shineup.me` - Домен: `server2.shineup.me`
- IP: `193.8.215.70` - IP: `193.8.215.70`
- Reserve test (temporarily down):
- SSH: `player@93.170.12.154`
- Домен: `test.shineup.me`
- IP: `93.170.12.154`
## Локальные команды ## Локальные команды
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2` - Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2` - Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`
- Production server deploy: `./gradlew deployServerProduction` - Production server deploy: `./gradlew deployServerProduction`
- Production UI deploy: `./gradlew deployUIProduction` - Production UI deploy: `./gradlew deployUIProduction`
- Reserve test server deploy: `./gradlew deployServerTest`
- Reserve test UI deploy: `./gradlew deployUITest`
- Локальный запуск: `./gradlew startLocal` - Локальный запуск: `./gradlew startLocal`
## Отдельные тестовые VPS ## Отдельные тестовые VPS
- Отдельный стенд с 4 devnet-серверами на одном VPS описан в: - Отдельный стенд с 4 devnet-серверами на одном VPS описан в:
- `Dev_Docs/deploy/test-server/README.md` - `docs/deploy/test-server/README.md`
## Политика подтверждений ## Политика подтверждений
- `shineup.me` и `t.shineup.me` считать production-контурами. - `shineup.me` и `server2.shineup.me` считать production-контурами.
- Любые изменения на production-контурах, включая deploy сервера, deploy UI, конфиги, перезапуски и миграции, делать только после отдельного явного подтверждения пользователя. - Любые изменения на production-контурах, включая deploy сервера, deploy UI, конфиги, перезапуски и миграции, делать только после отдельного явного подтверждения пользователя.
- `test.shineup.me` считать резервным тестовым хостом, который сейчас временно не работает. ## Second production deploy (`server2.shineup.me`)
## Second production deploy (`t.shineup.me`)
- Это второй production-сервер SHiNE. - Это второй production-сервер SHiNE.
- Исторические задачи `deployServer`, `deployUI`, `deployServerTest2`, `deployUITest2` по-прежнему могут указывать сюда. - Исторические задачи `deployServer`, `deployUI`, `deployServerTest2`, `deployUITest2` по-прежнему могут указывать сюда.
@@ -60,15 +51,9 @@
- перенос `application.properties` с production с поправкой `server.ui.indexPath` на `/home/player/SHiNE/shine-ui/index.html`; - перенос `application.properties` с production с поправкой `server.ui.indexPath` на `/home/player/SHiNE/shine-ui/index.html`;
- установку `systemd` unit на `193.8.215.70`; - установку `systemd` unit на `193.8.215.70`;
- перезапуск `shine-server.service`; - перезапуск `shine-server.service`;
- установку/проверку Caddy для `t.shineup.me`. - установку/проверку Caddy для `server2.shineup.me`.
- `deployUI` / `deployUITest2` публикуют UI в `/home/player/SHiNE/shine-ui` на `193.8.215.70`. - `deployUI` / `deployUITest2` публикуют UI в `/home/player/SHiNE/shine-ui` на `193.8.215.70`.
- Важно: историческое имя `deployUITest2` вводит в заблуждение. По умолчанию эта задача деплоит на `server2.shineup.me`, а не на `t2.shineup.me`.
## Reserve test deploy (`test.shineup.me`)
- `test.shineup.me` считается резервным тестовым сервером.
- Сейчас он помечен как временно неработающий.
- Его настройки и адрес не менять без отдельной задачи.
- Задачи `deployServerTest` и `deployUITest` считаются резервными и требуют отдельной причины.
## UI-деплой и Caddy (обязательно) ## UI-деплой и Caddy (обязательно)
@@ -1,8 +1,8 @@
# Сервер `193.8.215.70` — второй production (`t.shineup.me`) # Сервер `193.8.215.70` — второй production (`server2.shineup.me`)
- Пользователь: `player` - Пользователь: `player`
- Домен: `t.shineup.me` - Домен: `server2.shineup.me`
- Логин сервера: `tshineupme` - Логин сервера: `server2shineupme`
- Каталог SHiNE: `/home/player/SHiNE` - Каталог SHiNE: `/home/player/SHiNE`
- UI публикация для Caddy: `/home/player/SHiNE/shine-ui` - UI публикация для Caddy: `/home/player/SHiNE/shine-ui`
- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar` - Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
@@ -26,9 +26,9 @@
- Конфиг: `/etc/caddy/Caddyfile` - Конфиг: `/etc/caddy/Caddyfile`
- Сайты: - Сайты:
- `t.shineup.me` - `server2.shineup.me`
- `agent.shiningpeople.ru` - `agent.shiningpeople.ru`
- Для `t.shineup.me`: - Для `server2.shineup.me`:
- `root * /home/player/SHiNE/shine-ui` - `root * /home/player/SHiNE/shine-ui`
- `try_files {path} /index.html` - `try_files {path} /index.html`
- `reverse_proxy /ws* -> 127.0.0.1:7070` - `reverse_proxy /ws* -> 127.0.0.1:7070`
@@ -1,41 +0,0 @@
# Сервер `93.170.12.154` — резервный test (`test.shineup.me`)
- Пользователь: `player`
- Каталог SHiNE: `/home/player/SHiNE`
- Домен: `test.shineup.me`
- Роль: резервный тестовый сервер
- 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)
## Статус
- Резервный тестовый сервер для SHiNE.
- Сейчас помечен как временно неработающий.
- Источник данных для тестовой БД: production `shineup.me`.
- Пока не использовать для обычного deploy.
- Основной прод-сервер: `shineup.me` (`185.229.109.118`).
## Caddy
- Конфиг: `/etc/caddy/Caddyfile`
- Настройки:
- `no-store/no-cache` заголовки;
- `try_files {path} /index.html` (SPA fallback);
- `reverse_proxy /ws* -> 127.0.0.1:7070`;
- целевой сайт: `test.shineup.me`.
## Deploy
- Резервные задачи:
- `./gradlew deployServerTest`
- `./gradlew deployUITest`
- Эти задачи пока не использовать без отдельной причины.
+1 -1
View File
@@ -9,4 +9,4 @@
- внешний HTTPS и маршрутизацию `/ws` обслуживает один общий `Caddy` - внешний HTTPS и маршрутизацию `/ws` обслуживает один общий `Caddy`
Детальное описание: Детальное описание:
- [178.208.90.249_quad_devnet.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Dev_Docs/deploy/test-server/178.208.90.249_quad_devnet.md) - [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 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail set -euo pipefail
TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}" TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
TARGET_DOMAIN="${TARGET_DOMAIN:-t.shineup.me}" TARGET_DOMAIN="${TARGET_DOMAIN:-server2.shineup.me}"
REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}" REMOTE_UI_DIR="${REMOTE_UI_DIR:-/home/player/SHiNE/shine-ui}"
REMOTE_CADDYFILE="${REMOTE_CADDYFILE:-/etc/caddy/Caddyfile}" REMOTE_CADDYFILE="${REMOTE_CADDYFILE:-/etc/caddy/Caddyfile}"
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
TEST_HOST="${TEST_HOST:-player@93.170.12.154}"
TARGET_DOMAIN="${TARGET_DOMAIN:-test.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
}
$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
echo "==> Проверка SSH и sudo на $TEST_HOST"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$TEST_HOST" "echo SSH OK" >/dev/null
ssh "$TEST_HOST" "sudo -n true"
echo "==> Установка Caddy-конфига для $TARGET_DOMAIN"
scp -p "$TMP_DIR/Caddyfile" "$TEST_HOST:/tmp/shine-test-caddyfile.new" >/dev/null
ssh "$TEST_HOST" "sudo mkdir -p \"$(dirname "$REMOTE_CADDYFILE")\" && \
sudo mv -f /tmp/shine-test-caddyfile.new \"$REMOTE_CADDYFILE\" && \
sudo chown root:root \"$REMOTE_CADDYFILE\" && \
sudo caddy validate --config \"$REMOTE_CADDYFILE\" && \
sudo systemctl reload caddy"
echo "==> Caddy настроен для $TARGET_DOMAIN"
+3 -12
View File
@@ -103,7 +103,9 @@ export function render({ navigate }) {
draft.shineServer = resolved.wsUrl; draft.shineServer = resolved.wsUrl;
applyStatus(resolved.status, resolved.httpBase); applyStatus(resolved.status, resolved.httpBase);
} else { } else {
const next = await checkServerAvailabilityByKey(field.key, input.value); const next = await checkServerAvailabilityByKey(field.key, input.value, {
solanaEndpoint: draft.solanaServer,
});
applyStatus(next); applyStatus(next);
} }
} finally { } finally {
@@ -171,16 +173,6 @@ export function render({ navigate }) {
actions.append(serverUiButton, cancelButton, saveButton); actions.append(serverUiButton, cancelButton, saveButton);
const help = document.createElement('button');
help.className = 'help-fab';
help.type = 'button';
help.textContent = '?';
help.addEventListener('click', () => {
window.alert(
'Текст для разработчиков: для SHiNE вводится логин серверного аккаунта. Клиент читает его PDA, берёт server_address, показывает точный https-адрес и проверяет доступность WS-канала автоматически.',
);
});
screen.append( screen.append(
renderHeader({ renderHeader({
title: 'Настройки входа', title: 'Настройки входа',
@@ -188,7 +180,6 @@ export function render({ navigate }) {
}), }),
body, body,
actions, actions,
help,
); );
screen.cleanup = () => { screen.cleanup = () => {
+12 -15
View File
@@ -163,6 +163,7 @@ export function render({ navigate }) {
words: passwordWords, words: passwordWords,
onInput: (index, value) => { onInput: (index, value) => {
passwordWords[index] = value; passwordWords[index] = value;
passwordInput.value = composePasswordFromWords(passwordWords);
passwordWordsLinked = true; passwordWordsLinked = true;
syncDraftState(); syncDraftState();
updateWordsPreview(); updateWordsPreview();
@@ -258,11 +259,13 @@ export function render({ navigate }) {
let generationRunId = 0; let generationRunId = 0;
function getCurrentPassword() { function getCurrentPassword() {
return passwordMode === 'words' ? composePasswordFromWords(passwordWords) : String(passwordInput.value || ''); return String(passwordInput.value || '');
} }
function updateWordsPreview() { function updateWordsPreview() {
const password = getCurrentPassword(); const password = passwordMode === 'words'
? composePasswordFromWords(passwordWords)
: String(passwordInput.value || '');
const text = `Итоговая длина пароля: ${password.length} символов.`; const text = `Итоговая длина пароля: ${password.length} символов.`;
wordsPreview.textContent = text; wordsPreview.textContent = text;
passwordLengthText.textContent = text; passwordLengthText.textContent = text;
@@ -306,8 +309,8 @@ export function render({ navigate }) {
function updatePasswordModeVisibility() { function updatePasswordModeVisibility() {
const wordsMode = passwordMode === 'words'; const wordsMode = passwordMode === 'words';
wordsSection.style.display = wordsMode ? 'grid' : 'none'; wordsSection.style.display = wordsMode ? 'grid' : 'none';
if (passwordField) passwordField.style.display = wordsMode ? 'none' : 'grid'; if (passwordField) passwordField.style.display = 'grid';
passwordInputRow.style.display = wordsMode ? 'grid' : 'none'; passwordInputRow.style.display = 'grid';
updateWordsPreview(); updateWordsPreview();
} }
@@ -397,7 +400,6 @@ export function render({ navigate }) {
if (runId !== loginCheckRunId) return false; if (runId !== loginCheckRunId) return false;
const isFree = !check.exists; const isFree = !check.exists;
let className = ''; let className = '';
let precheckWarning = '';
if (isFree) { if (isFree) {
if (promoAccepted) { if (promoAccepted) {
className = 'promo'; className = 'promo';
@@ -410,7 +412,7 @@ export function render({ navigate }) {
className = precheck.className; className = precheck.className;
} catch (precheckError) { } catch (precheckError) {
className = 'free'; className = 'free';
precheckWarning = formatSolanaErrorDetails(precheckError); console.warn('Solana login precheck fallback to free', formatSolanaErrorDetails(precheckError));
} }
} }
} }
@@ -422,12 +424,7 @@ export function render({ navigate }) {
} else if (className === 'promo') { } else if (className === 'promo') {
setStatusMessage('Логин свободен ✅ Временный код принят', 'is-available'); setStatusMessage('Логин свободен ✅ Временный код принят', 'is-available');
} else if (className === 'free') { } else if (className === 'free') {
setStatusMessage( setStatusMessage('Логин свободен ✅', 'is-available');
precheckWarning
? `Логин свободен ✅ (предпроверка Solana недоступна: ${precheckWarning})`
: 'Логин свободен ✅',
'is-available',
);
} else if (className === 'premium') { } else if (className === 'premium') {
setStatusMessage('Логин свободен, но это премиум-логин (покупка через DAO) ❌', 'is-unavailable'); setStatusMessage('Логин свободен, но это премиум-логин (покупка через DAO) ❌', 'is-unavailable');
} else if (className === 'company') { } else if (className === 'company') {
@@ -480,11 +477,11 @@ export function render({ navigate }) {
wordInputs.forEach((input) => { wordInputs.forEach((input) => {
input.value = ''; input.value = '';
}); });
passwordInput.value = '';
}
} else { } else {
passwordInput.value = composePasswordFromWords(passwordWords); passwordInput.value = composePasswordFromWords(passwordWords);
passwordWordsLinked = true; }
} else if (passwordWordsLinked) {
passwordInput.value = composePasswordFromWords(passwordWords);
} }
passwordMode = nextMode; passwordMode = nextMode;
updatePasswordModeVisibility(); updatePasswordModeVisibility();
+54 -59
View File
@@ -24,7 +24,8 @@ 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 SOLANA_SYNC_WAIT_SEC = 12; const AUTO_LOGIN_INITIAL_DELAY_MS = 5000;
const AUTO_LOGIN_RETRY_MS = 1000;
const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => ''); const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => '');
function parseBalanceSol(value) { function parseBalanceSol(value) {
@@ -329,82 +330,76 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) {
const card = screen.querySelector('.card.stack'); const card = screen.querySelector('.card.stack');
if (!card) return; if (!card) return;
let remainingSec = SOLANA_SYNC_WAIT_SEC; let initialDelayId = null;
let timerId = null; let retryTimerId = null;
let loginStarted = false; let loginInFlight = false;
let loginCompleted = false;
const info = document.createElement('p'); const title = document.createElement('h2');
info.className = 'auth-copy'; title.textContent = 'Поздравляем, регистрация завершена';
info.textContent = 'Регистрация завершена успешно.'; title.style.margin = '0';
const hint = document.createElement('p'); const hint = document.createElement('p');
hint.className = 'meta-muted'; hint.className = 'auth-copy';
hint.textContent = 'Нужно подождать 1015 секунд, пока в блокчейне SHiNE обновится ваша запись о регистрации. После этого мы автоматически войдём в ваш аккаунт.'; hint.textContent = 'Осталось подождать несколько секунд, пока обновится запись в блокчейне. После этого приложение автоматически войдёт в ваш аккаунт.';
const timer = document.createElement('p'); const progress = document.createElement('p');
timer.className = 'meta-muted'; progress.className = 'meta-muted';
timer.textContent = `Готовим автоматический вход: ${remainingSec} сек`; progress.textContent = 'Подготавливаем автоматический вход...';
const tryLoginBtn = document.createElement('button'); const altHint = document.createElement('p');
tryLoginBtn.className = 'primary-btn'; altHint.className = 'meta-muted';
tryLoginBtn.type = 'button'; altHint.textContent = 'Или вы можете';
tryLoginBtn.textContent = 'Входим автоматически...';
tryLoginBtn.disabled = true;
const statusHint = document.createElement('p'); const backBtn = document.createElement('button');
statusHint.className = 'meta-muted'; backBtn.className = 'ghost-btn';
statusHint.textContent = 'Ничего нажимать не нужно. Если сервер ответит раньше, войдём сразу.'; backBtn.type = 'button';
backBtn.textContent = 'Вернуться в главное меню';
const stopTimer = () => { const stopAutoLogin = () => {
if (timerId) { if (initialDelayId) {
window.clearInterval(timerId); window.clearTimeout(initialDelayId);
timerId = null; initialDelayId = null;
}
if (retryTimerId) {
window.clearInterval(retryTimerId);
retryTimerId = null;
} }
}; };
const startAutoLogin = async () => { const tryAutoLogin = async () => {
if (loginStarted) return; if (loginCompleted || loginInFlight) return;
loginStarted = true; loginInFlight = true;
stopTimer(); progress.textContent = 'Пробуем автоматически войти в аккаунт...';
status.style.display = 'none'; status.style.display = 'none';
timer.textContent = 'Пробуем автоматически войти...';
tryLoginBtn.textContent = 'Входим...';
try { try {
await completeRegistrationLogin({ navigate, keyBundle }); await completeRegistrationLogin({ navigate, keyBundle });
loginCompleted = true;
stopAutoLogin();
} catch (error) { } catch (error) {
loginStarted = false; if (!loginCompleted) {
status.className = 'status-line is-unavailable'; progress.textContent = 'Ждём обновления записи и скоро попробуем снова...';
status.textContent = toUserMessage(error, 'Пока не удалось автоматически войти. Попробуйте ещё раз через несколько секунд.'); console.warn('Auto login after registration is not ready yet', toUserMessage(error, 'auto-login-wait'));
status.style.display = ''; }
timer.textContent = 'Автовход пока не удался.'; } finally {
tryLoginBtn.disabled = false; loginInFlight = false;
tryLoginBtn.textContent = 'Попробовать войти сейчас';
} }
}; };
const updateTimerUi = () => { backBtn.addEventListener('click', () => {
if (remainingSec > 0) { loginCompleted = true;
timer.textContent = `Готовим автоматический вход: ${remainingSec} сек`; stopAutoLogin();
tryLoginBtn.textContent = `Автовход через ${remainingSec} сек`; navigate('start-view');
tryLoginBtn.disabled = true;
} else {
timer.textContent = 'Запускаем автоматический вход...';
tryLoginBtn.textContent = 'Входим...';
tryLoginBtn.disabled = true;
void startAutoLogin();
}
};
tryLoginBtn.addEventListener('click', async () => {
if (loginStarted) return;
await startAutoLogin();
}); });
card.innerHTML = ''; card.innerHTML = '';
card.append(info, hint, statusHint, timer, tryLoginBtn, status); card.append(title, hint, progress, altHint, backBtn, status);
updateTimerUi();
timerId = window.setInterval(() => { initialDelayId = window.setTimeout(() => {
remainingSec -= 1; if (loginCompleted) return;
updateTimerUi(); void tryAutoLogin();
}, 1000); retryTimerId = window.setInterval(() => {
void tryAutoLogin();
}, AUTO_LOGIN_RETRY_MS);
}, AUTO_LOGIN_INITIAL_DELAY_MS);
} }
+3 -1
View File
@@ -86,7 +86,9 @@ export function render({ navigate }) {
draft.shineServer = resolved.wsUrl; draft.shineServer = resolved.wsUrl;
applyStatus(resolved.status, resolved.httpBase); applyStatus(resolved.status, resolved.httpBase);
} else { } else {
const next = await checkServerAvailabilityByKey(field.key, input.value); const next = await checkServerAvailabilityByKey(field.key, input.value, {
solanaEndpoint: draft.solanaServer,
});
applyStatus(next); applyStatus(next);
} }
} finally { } finally {
@@ -1,5 +1,5 @@
import { resolveShineServerByServerLogin } from './shine-server-resolver.js'; import { resolveShineServerByServerLogin } from './shine-server-resolver.js';
import { SOLANA_ENDPOINT_DEFAULT } from '../solana-programs.js'; import { state } from '../state.js';
function normalizeUrl(value) { function normalizeUrl(value) {
return String(value || '').trim(); return String(value || '').trim();
@@ -121,12 +121,13 @@ export async function resolveAndCheckShineServerLogin(serverLogin, solanaEndpoin
}; };
} }
export async function checkServerAvailabilityByKey(key, url) { export async function checkServerAvailabilityByKey(key, url, options = {}) {
const selectedSolanaEndpoint = normalizeUrl(options?.solanaEndpoint || state.entrySettings.solanaServer);
if (key === 'solanaServer') { if (key === 'solanaServer') {
return (await checkSolanaRpc(url)) ? 'available' : 'unavailable'; return (await checkSolanaRpc(url)) ? 'available' : 'unavailable';
} }
if (key === 'shineServerLogin') { if (key === 'shineServerLogin') {
return (await resolveAndCheckShineServerLogin(url, SOLANA_ENDPOINT_DEFAULT)).status; return (await resolveAndCheckShineServerLogin(url, selectedSolanaEndpoint)).status;
} }
if (key === 'shineServer') { if (key === 'shineServer') {
return (await checkShineWs(url)) ? 'available' : 'unavailable'; return (await checkShineWs(url)) ? 'available' : 'unavailable';
@@ -6,7 +6,6 @@ import {
import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js'; import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js';
const CLASSIFY_LOGIN_INSTRUCTION_TAG = 1; const CLASSIFY_LOGIN_INSTRUCTION_TAG = 1;
const PRECHECK_SIM_PAYER = 'SHiJ58FvbJQJnPEUa5mZNVpWd1ym9CkjjPgeHYzUJmh';
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login='; const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';
let solanaLibPromise = null; let solanaLibPromise = null;
@@ -86,7 +85,6 @@ export async function precheckLoginClassOnSolana({ login, solanaEndpoint }) {
const solana = await loadSolanaLib(); const solana = await loadSolanaLib();
const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed'); const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed');
const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID); const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID);
const payer = new solana.PublicKey(PRECHECK_SIM_PAYER);
const ix = new solana.TransactionInstruction({ const ix = new solana.TransactionInstruction({
programId: loginGuardProgram, programId: loginGuardProgram,
keys: [], keys: [],
@@ -94,7 +92,9 @@ export async function precheckLoginClassOnSolana({ login, solanaEndpoint }) {
}); });
const { blockhash } = await connection.getLatestBlockhash('confirmed'); const { blockhash } = await connection.getLatestBlockhash('confirmed');
const v0Message = new solana.TransactionMessage({ const v0Message = new solana.TransactionMessage({
payerKey: payer, // Для simulateTransaction нужен существующий аккаунт payer.
// Используем сам program id, чтобы не зависеть от внешнего тестового адреса.
payerKey: loginGuardProgram,
recentBlockhash: blockhash, recentBlockhash: blockhash,
instructions: [ix], instructions: [ix],
}).compileToV0Message(); }).compileToV0Message();
+1 -10
View File
@@ -95,16 +95,7 @@ const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
function normalizeStoredSolanaServer(value) { function normalizeStoredSolanaServer(value) {
const raw = String(value || '').trim(); const raw = String(value || '').trim();
if (!raw) return DEFAULT_SOLANA_SERVER; if (!raw) return DEFAULT_SOLANA_SERVER;
const normalized = raw.replace(/\/+$/u, '').toLowerCase(); return raw.replace(/\/+$/u, '');
if (
normalized === 'https://api.devnet.solana.com'
|| normalized === 'http://api.devnet.solana.com'
|| normalized === 'https://api.mainnet-beta.solana.com'
|| normalized === 'http://api.mainnet-beta.solana.com'
) {
return DEFAULT_SOLANA_SERVER;
}
return raw;
} }
export function normalizeDmChatId(value) { export function normalizeDmChatId(value) {