Dexus: KCP One Ply ONNX-бот — установка и эксплуатация
Summary
Состояние на 14.08.2026: на
dexusкруглосуточно запущена отдельная копия KCP One Ply под Play-идентичностьюdexus/kcp-one-ply-1. Две независимые JVM-реплики получают по одному CPU и 640 MiB, а nginxleast_connраспределяет между ними одновременные webhook-запросы. Play разрешает две партии: лестница занимает максимум одну, второй слот зарезервирован человеку. Бот опубликован через существующий Cloudflare Tunnelauroraи находится на рейтинговой лестнице и в human catalog.Первые 53 webhook-доставки все завершились
applied, без отказов и сетевых ошибок. Память контейнера выросла с ~91 MiB в покое до 117–147 MiB под игрой. Одно ядро во время расчёта загружено на 95–98%. Ранняя статистика задержки — p50 2 s, p90 30 s, p99 120 s — показывает, что запас памяти велик, а главный риск остаётся в очень длинном хвосте времени поиска.
Связанные заметки:
- Northflank: два ONNX-бота;
- Где запускать бота: варианты и ёмкость домашней лаборатории;
- Боты на платформах: рантаймы и лимиты;
- Артефакты моделей: хранение, бэкап и деплой;
- Play API: backup и restore.
1. Зачем понадобилась отдельная копия
На Northflank уже работает northflank/kcp-one-ply-1 с тем же алгоритмом, но только
на 0,2 shared vCPU. Он помещается в 512 MiB, однако процессорная доля слишком мала для
тяжёлых позиций. Копия на Dexus нужна как отдельный эксперимент:
- дать модели целое x86-ядро;
- сравнить хвост задержки и качество игры с Northflank;
- не смешивать рейтинговую историю двух существенно разных вычислительных сред;
- проверить будущий режим «играть только во время ночного окна бэкапов».
Play-идентичность поэтому отдельная: dexus/kcp-one-ply-1. Суффикс -1 фиксирует
первую версию алгоритма и модели. Если поменяются веса, признаки или существенная
часть поиска, нужна новая идентичность -2, а не продолжение старого рейтинга.
2. Что реально развёрнуто
flowchart LR P["Play ladder\nplay.jc.id.lv"] -->|"signed HTTPS POST\n/api/webhook"| C["Cloudflare edge\ndexus-kcp.jc.id.lv"] C -->|"Aurora tunnel"| D["Dexus LAN origin\n192.168.10.4:8094"] D --> N["nginx least_conn\n64 MiB"] N --> A["JVM replica A\n1 CPU / 640 MiB"] N --> B["JVM replica B\n1 CPU / 640 MiB"] I["Private GAR image\nmodel + 147-row book"] -->|"one-time LAN transfer"| A I --> B
| Параметр | Значение |
|---|---|
| Хост | dexus, Ubuntu 24.04, linux/amd64 |
| CPU | Intel i5-2500, 4 ядра / 4 потока |
| RAM | около 3,3 GiB доступно ОС |
| Каталог деплоя | /home/jegors/dicechess-kcp-one-ply |
| Compose project | dicechess-dexus-kcp |
| Compose services | kcp-one-ply-a, kcp-one-ply-b, webhook-proxy |
| Container names | dexus-kcp-one-ply-a, dexus-kcp-one-ply-b, dexus-kcp-one-ply-proxy |
| Локальный образ | dicechess-bot-onnx-private:edccf9d |
| Host port | 8094 → nginx 8080 → JVM replicas 8080 |
| Public URL | https://dexus-kcp.jc.id.lv/api/webhook |
| Cloudflare tunnel | существующий dashboard-managed aurora |
| Play identity | dexus/kcp-one-ply-1 |
| Capacity | maxConcurrentGames=2, ladderAllowance=1 |
| Humans catalog | openToHumans=true; один слот зарезервирован direct game |
| Restart policy | unless-stopped |
На Dexus также работают backup-контейнеры bbs и pbs. Старый hunter-all
остановлен и ресурсы одновременно с новым ботом не потребляет.
3. Граница публичного кода и приватных артефактов
Код бота остаётся в публичном rabestro/dicechess-bot-gcp-onnx. Там лежат synthetic-модель и пятистрочный пример книги, достаточные для сборки, тестов и понимания формата.
Production-артефакты публично не публикуются:
kcp_nodice_serving_20260809.onnx;- полная
opening_book.tsvна 147 записей.
Они находятся внутри приватного образа в Google Artifact Registry. На Dexus образ получил только локальный тег; ни Google service-account key, ни постоянное право читать GAR на сервер не копировались.
Исходный образ при развёртывании:
europe-west3-docker.pkg.dev/dice-chess-lab/dicechess/dicechess-bot-onnx:edccf9dПроверенный image ID:
sha256:7458a22d5b18ef993c0a76f52d393798a879a021a9a112295111fd3f7351ebfcImportant
Git SHA в теге недостаточно считать доказательством содержимого. Для точного воспроизведения сверять digest/Image ID и вывод старта с именами модели и книги.
4. Доставка приватного образа без registry-ключа на Dexus
Образ сначала был получен на авторизованной рабочей машине, затем передан по доверенной локальной сети непосредственно в Docker Dexus:
source_image="europe-west3-docker.pkg.dev/dice-chess-lab/dicechess/dicechess-bot-onnx:edccf9d"
docker pull --platform linux/amd64 "$source_image"
docker save "$source_image" | ssh dexus docker load
ssh dexus docker tag "$source_image" dicechess-bot-onnx-private:edccf9dПроверка без раскрытия содержимого модели:
ssh dexus 'docker image inspect dicechess-bot-onnx-private:edccf9d \
--format "{{.Id}} {{.Architecture}}/{{.Os}}"'Плюсы такого способа:
- Dexus не хранит GAR credential;
- секрет реестра не попадает в compose и shell history сервера;
- приватные файлы остаются только внутри локального Docker image store;
- образ можно восстановить с авторизованной машины тем же потоком.
Минус: обновление не происходит через обычный docker compose pull; новый образ нужно
передать вручную и явно поменять тег в compose.
5. Docker Compose
Рабочий /home/jegors/dicechess-kcp-one-ply/compose.yaml:
name: dicechess-dexus-kcp
x-bot-common: &bot-common
image: dicechess-bot-onnx-private:edccf9d
restart: unless-stopped
init: true
cpus: "1.0"
mem_limit: "640m"
memswap_limit: "640m"
pids_limit: 256
environment:
PORT: "8080"
MODEL_PATH: "/models/kcp_nodice_serving_20260809.onnx"
OPENING_BOOK_PATH: "/models/opening_book.tsv"
ORACLE_FEATURES: "kcp"
SEARCH_MODE: "one-ply"
TIME_POLICY: "empirical-v1"
OVERHEAD_BUFFER_MS: "1000"
DEFAULT_THINK_MS: "2000"
JAVA_TOOL_OPTIONS: "-Xmx320m -XX:ActiveProcessorCount=1 -XX:+ExitOnOutOfMemoryError"
DICECHESS_WEBHOOK_SECRET: "${DICECHESS_WEBHOOK_SECRET:-}"
expose:
- "8080"
healthcheck:
test: ["CMD-SHELL", "bash -c '</dev/tcp/127.0.0.1/8080'"]
interval: 10s
timeout: 5s
retries: 6
start_period: 20s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
kcp-one-ply-a:
<<: *bot-common
container_name: dexus-kcp-one-ply-a
kcp-one-ply-b:
<<: *bot-common
container_name: dexus-kcp-one-ply-b
webhook-proxy:
image: nginx:alpine
container_name: dexus-kcp-one-ply-proxy
restart: unless-stopped
init: true
cpus: "0.1"
mem_limit: "64m"
memswap_limit: "64m"
pids_limit: 64
depends_on:
kcp-one-ply-a:
condition: service_healthy
kcp-one-ply-b:
condition: service_healthy
volumes:
- "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
ports:
- "8094:8080"
healthcheck:
test: ["CMD-SHELL", "nc -z 127.0.0.1 8080"]
interval: 10s
timeout: 5s
retries: 6
start_period: 5s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"nginx.conf использует upstream least_conn, две внутренние цели
kcp-one-ply-a:8080 / kcp-one-ply-b:8080, отключённый response buffering и
proxy_read_timeout 125s — чуть больше серверного webhook-window Play.
Почему две одноядерные JVM, а не одна двухъядерная
Strategy.chooseMoves в боте объявлен synchronized: одна ONNX-сессия намеренно
обслуживает только один поиск за раз. Если дать одному процессу 2 CPU и посадить его в
две партии, второй webhook будет ждать monitor lock и тратить часы. Два отдельных
процесса имеют две независимые Strategy и ONNX-сессии; nginx least_conn отправляет
перекрывающиеся запросы свободной реплике.
Почему именно такие лимиты
cpus: "1.0"— жёсткая суммарная квота одного CPU. Это не привязка к конкретному ядру: Linux может переносить процесс, но за период cgroup он не получит больше одного ядра.cpusetсознательно не используется. При жёстком pinning ONNX Runtime пытался выставить affinity собственным worker threads и писал ошибкиpthread_setaffinity_npдля запрещённых CPU. После удаленияcpusetошибки исчезли, а квота сохранилась.mem_limit: 640mна реплику — общий предел JVM heap + metaspace + direct buffers + native ONNX. Два лимита не являются предварительным резервом; реальное потребление каждой реплики после старта около 75–80 MiB и растёт по мере партий.memswap_limit: 640m— реплика не получает дополнительный swap поверх лимита.-Xmx320mоставляет около половины cgroup-памяти нативной части ONNX Runtime.ActiveProcessorCount=1не даёт JVM строить внутренние пулы как для четырёх CPU.ExitOnOutOfMemoryErrorпревращает зависший деградировавший процесс в явный restart.- Ограничение логов не позволяет контейнеру постепенно заполнить системный диск.
6. Секреты и права доступа
Есть два независимых секрета:
- bearer token зарегистрированной Play-идентичности;
- HMAC secret webhook, которым Play подписывает доставки.
Они никогда не лежат в compose, README, git или выводе диагностических команд.
Play token
На рабочем Mac token дополнительно сохранён в Keychain:
service: dicechess-play-bot-token
account: dexus/kcp-one-ply-1На Dexus он лежит отдельно от контейнера:
/home/jegors/dicechess-kcp-one-ply/secrets/play-bot-tokenПрава:
chmod 700 ~/dicechess-kcp-one-ply/secrets
chmod 600 ~/dicechess-kcp-one-ply/secrets/play-bot-tokenToken не передаётся в container environment: он нужен только операторским lifecycle- скриптам для ladder/capacity/status.
Webhook HMAC secret
HMAC secret хранится в:
/home/jegors/dicechess-kcp-one-ply/.envФайл имеет режим 600 и содержит только:
DICECHESS_WEBHOOK_SECRET=<secret>Секрет сервер Play показывает один раз при POST /bot/webhook. Регистрация и запись
должны происходить в одном защищённом shell-сеансе, без печати JSON-ответа:
ssh dexus 'set -euo pipefail
deploy_dir=$HOME/dicechess-kcp-one-ply
token=$(<"$deploy_dir/secrets/play-bot-token")
response=$(curl --fail --silent --show-error \
-X POST https://play-api.jc.id.lv/bot/webhook \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json" \
--data "{\"url\":\"https://dexus-kcp.jc.id.lv/api/webhook\"}")
secret=$(printf "%s" "$response" | jq -er .secret)
[[ "$secret" =~ ^[0-9a-fA-F]{64}$ ]]
umask 077
printf "DICECHESS_WEBHOOK_SECRET=%s\n" "$secret" > "$deploy_dir/.env.next"
mv "$deploy_dir/.env.next" "$deploy_dir/.env"
unset response secret token'Danger
Не запускать эту процедуру с
set -x, не добавлять| jqк полному registration- ответу и не вставлять secret в аргументыdocker compose. Повторная регистрация заменяет прежний secret, поэтому после неё контейнер нужно сразу пересоздать.
7. Публикация через Cloudflare Tunnel
Отдельный cloudflared на Dexus не нужен. Существующий tunnel aurora уже имеет
доступ к LAN и публикует другие локальные сервисы.
В Cloudflare Dashboard добавлен route типа Published application:
| Поле | Значение |
|---|---|
| Tunnel | aurora |
| Hostname | dexus-kcp.jc.id.lv |
| Path | пусто, все пути |
| Service URL | http://192.168.10.4:8094 |
Cloudflare автоматически создал DNS-запись. Входящие порты на домашнем роутере не открывались.
Проверки по слоям:
# На Dexus: контейнер слушает локальный порт.
ssh dexus 'curl -fsS -H "content-type: application/json" \
-d "{\"type\":\"verification\",\"nonce\":\"local-check\"}" \
http://127.0.0.1:8094/api/webhook'
# Из LAN: Aurora действительно достигает Dexus.
ssh aurora 'curl -fsS -H "content-type: application/json" \
-d "{\"type\":\"verification\",\"nonce\":\"lan-check\"}" \
http://192.168.10.4:8094/api/webhook'
# Снаружи: DNS, TLS, tunnel и origin работают вместе.
dig +short dexus-kcp.jc.id.lv
curl -fsS -H 'content-type: application/json' \
-d '{"type":"verification","nonce":"public-check"}' \
https://dexus-kcp.jc.id.lv/api/webhookПоследний ответ должен вернуть тот же nonce. Verification handshake специально работает до установки HMAC secret; игровые доставки без секрета не принимаются.
8. Регистрация в Play
Порядок важен:
- контейнер healthy и отвечает локально;
- Cloudflare public URL проходит verification;
- создать отдельную Play-идентичность;
- сохранить token;
- установить capacity
2иopenToHumans=true; - зарегистрировать webhook и сразу сохранить одноразовый HMAC secret;
- пересоздать контейнер с
.env; - проверить, что Play отвечает
ladderAllowance=1: лестница не сможет занять человеческий слот; - только после повторной проверки вступить в ladder.
Полезные безопасные проверки:
ssh dexus 'token=$(<"$HOME/dicechess-kcp-one-ply/secrets/play-bot-token")
curl -fsS https://play-api.jc.id.lv/bot/webhook \
-H "Authorization: Bearer ${token}" | jq "{url, verifiedAt}"
curl -fsS https://play-api.jc.id.lv/bot/capacity \
-H "Authorization: Bearer ${token}" | jq
unset token'GET /bot/webhook никогда не возвращает HMAC secret повторно.
9. Обычная эксплуатация
В каталоге деплоя находятся три скрипта, режим 750:
start-and-join.sh— запускает compose, ждёт Docker health и вступает в ladder;leave-and-stop.sh— выходит из ladder, ждёт завершения текущей партии и только потом останавливает container;status.sh— показывает безопасные container/capacity/delivery-метрики.
Статус
ssh dexus 'cd ~/dicechess-kcp-one-ply && ./status.sh'Дополнительная нагрузка:
ssh dexus 'docker stats --no-stream \
--format "{{.Name}} CPU={{.CPUPerc}} MEM={{.MemUsage}} NET={{.NetIO}}" \
dexus-kcp-one-ply-a dexus-kcp-one-ply-b dexus-kcp-one-ply-proxy; free -h; uptime'Запуск и ladder join
ssh dexus 'cd ~/dicechess-kcp-one-ply && ./start-and-join.sh'Скрипт защищён flock: параллельный start/stop не сможет изменить состояние в середине
операции.
Безопасная остановка
ssh dexus 'cd ~/dicechess-kcp-one-ply && ./leave-and-stop.sh'Порядок принципиален:
POST /bot/ladder/leaveзапрещает назначать новые игры;GET /bot/capacityопрашивается доactiveGames=0;- только затем выполняется
docker compose stop.
По умолчанию drain ждёт 1800 секунд. Если партия не завершилась, скрипт завершается с ошибкой, оставляет контейнер работающим, а бота — снятым с лестницы. Это безопаснее, чем выключить компьютер посреди рейтинговой партии.
Временной лимит можно изменить только для конкретного ручного запуска:
ssh dexus 'cd ~/dicechess-kcp-one-ply && \
DRAIN_TIMEOUT_SECONDS=3600 ./leave-and-stop.sh'10. Текущий круглосуточный режим
На RPi4 раньше был cron, который ночью будил Dexus, а затем отправлял его спать. Sleep-задача отключена, потому что новый бот пока должен играть 24×7. Wake остаётся: если Dexus уже включён, это безвредный no-op.
Текущее логическое состояние crontab:
45 23 * * * /home/jegors/dexus-wake-sleep/wake-dexus.sh
# disabled 2026-08-14: dexus kcp-one-ply bot runs 24x7restart: unless-stopped поднимет обе реплики и proxy после перезагрузки Dexus, но ladder-state
живёт в Play отдельно. Если хост долго был недоступен, перед возвратом нужно проверить,
что webhook healthy и только затем выполнить start-and-join.sh.
11. Будущий ночной режим
Ночной режим пока не включён. Целевая последовательность должна быть такой:
sequenceDiagram participant R as RPi4 scheduler participant D as Dexus participant P as Play R->>D: Wake-on-LAN R->>D: start-and-join.sh D->>P: join ladder Note over D,P: бот играет 2–3 часа R->>D: leave-and-stop.sh D->>P: leave ladder loop пока activeGames > 0 D->>P: GET /bot/capacity end D->>D: docker compose stop R->>D: shutdown only after successful drain
Критические правила:
- не назначать жёсткое выключение по времени без drain;
- если
leave-and-stop.shвернул ненулевой код, не выключать Dexus; - сначала накопить длительности реальных партий и выбрать окно с запасом;
- учитывать, что ladder может назначить игру непосредственно перед
leave; - backup и бот могут использовать разные ядра, но простой
cpus: 1.0не привязывает их к разным физическим ядрам; - backup-контейнеры сейчас не имеют подтверждённой отдельной квоты в один CPU, поэтому обещание «бот 1 CPU + backup 1 CPU и они не мешают» ещё надо проверить нагрузочным ночным замером.
Рекомендуемый первый эксперимент: оставить 30–60 минут запаса после ожидаемого окна игры, записать CPU/load/latency во время Borg/PBS и только после этого автоматизировать shutdown.
12. Первые реальные замеры
Снято 14.08.2026. Сначала измерена одна реплика, затем конфигурация расширена до двух реплик специально для одного ladder-слота и одного human-слота.
Контейнер и хост
| Состояние | CPU container | Memory container | Host available RAM |
|---|---|---|---|
| healthy, без хода | ~0,03% | ~91 MiB | ~2,1 GiB |
| активный ONNX-расчёт | 95–98% | 117–147 MiB | ~2,1 GiB |
В тот же момент:
bbsпоказывал около 951 MiB в Docker accounting и около 4% CPU;pbs— около 41 MiB и 0,03% CPU;- load average Dexus — примерно
0.71 / 0.84 / 0.62; - swap практически не использовался;
hunter-allбыл остановлен.
Это baseline первоначальной одноэкземплярной конфигурации. После запуска двух реплик в покое измерено: A ~79 MiB, B ~76 MiB, nginx ~5,8 MiB, host available ~2,0 GiB.
Вывод по памяти: лимит 640 MiB на реплику имеет большой запас, а -Xmx320m пока не является
узким местом. Рост с 91 до ~132 MiB похож на нормальную инициализацию/рабочий набор, а
не на доказанную утечку. Для вывода об утечке нужны сутки и сравнение нижних точек после
завершения партий.
Webhook и часы
После первых четырёх законченных рейтинговых партий:
{
"totalDeliveries": 53,
"outcomes": [{ "outcome": "applied", "count": 53 }],
"p50Ms": 2000,
"p90Ms": 30000,
"p99Ms": 120000,
"lastFailure": null
}Это bucket-аппроксимация Play, а не точные процентили, и выборка пока мала. Однако форма уже ясна:
- типичный ход укладывается примерно в 2 s;
- отдельные позиции занимают десятки секунд;
- сетевых, HMAC, HTTP и illegal-move ошибок нет;
- первая наблюдаемая партия после четырёх доставок ещё имела 250,5 s на часах чёрных, то есть флажок не был близок;
- первые четыре партии дали две победы и два поражения, но provisional rating после четырёх игр не является оценкой силы модели.
Главный риск — не RAM, а хвост CPU
Полное ядро Dexus заметно лучше 0,2 shared vCPU Northflank, но KCP One Ply всё равно встречает дорогие позиции. p90/p99 нужно наблюдать на сотнях доставок и отдельно во время backup.
maxConcurrentGames=2безопасен только потому, что теперь есть две независимые JVM-реплики. Выше 2 увеличивать нельзя: третьему webhook снова не будет отдельного worker и CPU.
13. Мониторинг в следующие сутки
Минимальный чек-лист:
ssh dexus 'cd ~/dicechess-kcp-one-ply && ./status.sh'
ssh dexus 'docker stats --no-stream \
dexus-kcp-one-ply-a dexus-kcp-one-ply-b dexus-kcp-one-ply-proxy'
curl -fsS https://play-api.jc.id.lv/bots/dexus/kcp-one-ply-1 | jqСледить за:
lastFailureдолжен оставатьсяnull;- outcome
appliedдолжен доминировать безtimed_out,http_5xx,garbledиrefused; - p90/p99 после 100+ доставок;
- нижней точкой памяти после партий, а не только пиком во время хода;
- OOM/restart count контейнера;
- часами в длинных партиях;
- поведением во время настоящего backup-окна.
Проверка restart count и OOM:
ssh dexus 'for c in dexus-kcp-one-ply-a dexus-kcp-one-ply-b dexus-kcp-one-ply-proxy; do
docker inspect "$c" --format \
"{{.Name}} restarts={{.RestartCount}} oom={{.State.OOMKilled}} status={{.State.Status}} health={{.State.Health.Status}}"
done'14. Обновление версии
Безопасный порядок:
leave-and-stop.shи дождаться нулевогоactiveGames;- передать новый private image с авторизованной машины;
- присвоить новый локальный тег, не перетирать работающий;
- проверить digest, архитектуру и наличие production assets;
- изменить только
image:в compose; docker compose up -dи дождатьсяhealthy;- выполнить публичный verification;
start-and-join.sh;- старый image удалить только после успешной партии на новом.
Если меняется алгоритм или модель, сначала решить, остаётся ли это та же рейтинговая идентичность. Значимое изменение требует нового имени Play-бота.
15. Быстрая диагностика
Container unhealthy
ssh dexus 'cd ~/dicechess-kcp-one-ply && docker compose ps && \
docker logs --tail 100 dexus-kcp-one-ply-a && \
docker logs --tail 100 dexus-kcp-one-ply-b && \
docker logs --tail 100 dexus-kcp-one-ply-proxy'Нормальный старт содержит:
opening book: /models/opening_book.tsv (147 entries)
ONNX model: /models/kcp_nodice_serving_20260809.onnx (mode=one-ply, features=kcp, timePolicy=empirical-v1, candidateLimit=8)
ONNX custom handler listening on :8080/api/webhookLocal работает, public нет
Проверять по цепочке:
docker compose psи127.0.0.1:8094на Dexus;192.168.10.4:8094с Aurora;- состояние replica tunnel
auroraв Cloudflare; - route и DNS для
dexus-kcp.jc.id.lv; - публичный verification POST.
Webhook verified, но ходы не приходят
Проверить:
ssh dexus 'cd ~/dicechess-kcp-one-ply && ./status.sh'onLadder— через публичный профиль;ladderAllowance=1;activeGames;totalDeliveriesиlastFailure;- нет ли уже текущей партии в
GET /bot/games.
Ошибки affinity ONNX
Не возвращать cpuset без отдельного теста настройки thread affinity. Рабочая
конфигурация использует quota cpus: 1.0 и ActiveProcessorCount=1.
Память растёт
Сравнивать одинаковые фазы:
ssh dexus 'for i in 1 2 3 4 5; do
date -Is
docker stats --no-stream --format "{{.Name}} {{.MemUsage}}" \
dexus-kcp-one-ply-a dexus-kcp-one-ply-b dexus-kcp-one-ply-proxy
sleep 60
done'Рост во время загрузки модели и первой партии нормален. Подозрение на leak возникает, если нижняя точка монотонно растёт между завершёнными партиями, а не просто меняется рабочий набор page cache/native allocator.
Итоговое решение на 14.08.2026
Текущую конфигурацию оставляем:
- Dexus работает 24×7;
- две KCP One Ply JVM-реплики за nginx
least_conn; - по 1 CPU и 640 MiB на реплику,
maxConcurrentGames=2; openToHumans=true,ladderAllowance=1: один слот лестнице, один человеку;- существующий Cloudflare Tunnel на Aurora;
- production model и book только в private image;
- безопасный
leave → drain → stopуже подготовлен для будущего ночного режима.
Следующее решение принимается после накопления статистики минимум за сутки и отдельного наблюдения во время backup: оставить бота always-on, перевести в ночное окно или вернуть KCP-слот в облако, а Dexus использовать только для экспериментальных сессий.