wraithbound-launcher-server работает на серверной инфраструктуре. Игрок его не скачивает: в дистрибутив входят Electron и клиентские native binaries, но не control-plane и не приватные ключи.

Зона ответственности

Control-plane отдаёт bootstrap, список доступных сборок, подписанные manifests, maintenance и минимальную версию лаунчера. Сами JAR, assets и Java проходят напрямую через CDN, поэтому API не становится файловым прокси. Сайт остаётся владельцем аккаунтов, OAuth, групп, банов и платежей. Административный UI обращается только к tRPC сайта. Закрытый bearer token остаётся в Nest-процессе, а browser никогда не вызывает admin gRPC напрямую. Публичные сайт/tRPC маршруты не смешиваются с протоколом лаунчера.

Модули

Новая бизнес-логика добавляется в сервисы и repository/gateway, а не в main.rs или transport handlers. Рабочая композиция создаёт каталог и maintenance через CatalogStorage, поэтому выбор filesystem или S3 остаётся на границе конфигурации. Упрощённые filesystem-конструкторы существуют только в тестовой сборке и не попадают в production binary.

Каталог релизов

Control-plane работает с неизменяемыми manifest-envelope и отдельно хранит указатель на активный релиз:
Перед выдачей или активацией manifest сервер проверяет Ed25519-подпись, build_id, release_id и одинаковое поколение envelope/payload. При первой активации PublishBuild создаёт запись сборки из подписанных полей manifest; для существующей сборки обновляет её метаданные и activeReleaseId. RollbackBuild тем же способом выбирает ранее установленный подписанный release. Игровые файлы и manifest при этом не переписываются. InstallBuildRelease является отдельной authenticated операцией: она принимает envelope от publisher/CI, проверяет его и атомарно сохраняет, но не делает релиз активным. Повтор тех же bytes идемпотентен; другое содержимое под уже существующим releaseId отклоняется. Благодаря этому publisher может работать на отдельной машине без прямого доступа к Docker volume каталога.

Локальный запуск

Production

Production Compose находится в deploy/docker/compose.yml; hardened unit для запуска без Docker — в deploy/systemd, а проверенный маршрут публичного gRPC-Web edge — в deploy/nginx. Публичный hostname рекомендуется закрепить как launcher-api.wraithbound.com; контейнер слушает loopback, а TLS завершается в Nginx Proxy Manager или другом reverse proxy. Перед первым запуском заполните deploy/docker/.env, смонтируйте secret-файлы, trust registry и каталог publisher-конфигураций, затем выполните:
Проверка не обращается к S3 и не показывает значения credentials. Она ловит placeholder-настройки, публичный bind без reverse proxy, небезопасные URL и prefix, повреждённые deployment-файлы и отсутствие build-конфигураций. После запуска фактическую доступность S3 проверяет /health/ready. Read-only проверку AWS credentials и прав на выбранный prefix можно выполнить без запуска listener и без private signing key:
Команда читает catalog.json, выполняет полный paginated inventory и выводит только backend, namespace, число объектов и общий размер. Запись и удаление S3 objects не выполняются. Через публичный edge доступен только wraithbound.launcher.v1.LauncherService. Маршрут wraithbound.admin.v1.LauncherAdminService возвращает 404: publisher и CI обращаются к нему через приватную сеть или SSH tunnel. Это не позволяет случайно превратить admin bearer token в браузерный credential. Текущая модель хранения рассчитана на один активный экземпляр control-plane: изменения catalog.json, operations.json и maintenance сериализуются внутри процесса, но не используют распределённую блокировку S3. До добавления lease или транзакционного metadata-store нельзя запускать несколько пишущих replicas для одного WRAITHBOUND_S3_PREFIX.
WRAITHBOUND_SIGNING_KEY указывает на read-only secret внутри контейнера. WRAITHBOUND_TRUSTED_KEYS_FILE указывает на отдельный read-only JSON с публичными ключами и их состояниями. Выбранный private key обязан находиться в реестре со статусом active, иначе сервис останавливается до открытия портов. Тот же ключ должен находиться внутри своего validity window; expired или ещё не активный ключ не может подписывать maintenance и принимать releases. Admin credential аналогично передаётся через WRAITHBOUND_ADMIN_TOKEN_FILE. Содержимое ключей нельзя помещать в environment, Docker image, S3, Portainer stack text или backup каталога manifests.

Текущий этап

Bootstrap, S3-каталог, проверка signed manifests, maintenance, trust bundle, атомарная активация, rollback, постоянный журнал операций и health endpoints работают. PrepareBuild ставит allowlisted buildId в ограниченную очередь и запускает publisher по серверному <buildId>.toml. Пути из браузера не входят в RPC-контракт. Пока job находится в очереди или worker, повторный PrepareBuild для того же build id отклоняется, поэтому два publisher-процесса не меняют одну сборку одновременно. История операции сохраняется в том же storage backend и восстанавливается после рестарта. Операции, чей последний state был queued или running, получают отдельное terminal-событие failed/interrupted: UI не показывает погибший вместе с процессом worker как бесконечно работающий. Worker читает JSONL-события publisher по мере выполнения и сохраняет только разрешённые этапы подготовки профиля, assets и Java runtime. Служебные детали publisher, включая локальные пути, в журнал операций не переносятся. stderr полностью вычитывается, чтобы дочерний процесс не заблокировался, но в памяти и серверном warning сохраняются не более 16 КиБ диагностики. Каждый worker ограничен WRAITHBOUND_PUBLISHER_TIMEOUT_SECONDS (по умолчанию 1800 секунд). При зависании launcher-server завершает publisher, сохраняет terminal-событие failed/timeout и освобождает reservation сборки. Production preflight принимает значения от 60 секунд до 24 часов. In-process contract tests проверяют admin bearer middleware, отбрасывание небезопасных buildId/releaseId и полный путь установки подписанного envelope до активации и записи истории. Они не требуют запуска listener или S3. Сетевой smoke-test поднимает launcher-server на свободных loopback-портах и проверяет health, отказ без admin credential, установку/активацию подписанного release и его публичное получение. После Rust-клиента тот же процесс опрашивает Node-only TypeScript admin SDK: storage, maintenance, releases, операции, блокировка/разблокировка сборки и реестр ключей должны совпасть с состоянием сервера.
Скрипт совместим с Windows PowerShell 5.1 и современным pwsh, не использует системный proxy для loopback и удаляет временный каталог после проверки.