Настройка Robust.Cdn
Robust.Cdn — это выделенный сервер для хостинга и раздачи файлов игровых сборок для серверов Space Station 14. Он охватывает как управление сборками игрового сервера, так и дельта-загрузки клиента, и рекомендуется для любого серьёзного постоянного хостинга серверов.
Эта страница предполагает наличие изрядного опыта работы с различными концепциями Linux-администрирования. И не копируйте дерьмо вслепую. У меня нет времени и сил разжёвывать это руководство, так что вам нужно как следует прочитать и понять всё, что здесь изложено.
Обзор
Стандартный процесс публикации для Space Station 14 выглядит так:
- Сборки периодически создаются системой CI, такой как GitHub Actions.
- Файлы сборок (zip-архивы клиента и сервера) отправляются на центральный сервер. Серверные сборки индексируются и в них внедряется конфигурация для клиентского CDN, клиентские файлы принимаются, чтобы их мог скачать лаунчер.
- Игровые серверы (через SS14.Watchdog) получают уведомление об обновлении и автоматически скачивают новые серверные сборки с центрального сервера.
- Внедрённая конфигурация игрового сервера используется, чтобы сообщить клиентам, откуда они могут скачать файлы.
Robust.Cdn — это связующий элемент, который соединяет большую часть этого вместе:
- Приём новых сборок напрямую из GitHub Actions.
- Раздача серверных сборок игры для доступа игровыми серверами (watchdog).
- Автоматическое уведомление watchdog’ов о новом обновлении.
- Предоставление дельта-загрузок клиента.
Концепции
В настоящее время Robust.Cdn выполняет две основные функции:
- Управление серверными манифестами
- Управление дельта-загрузками клиента
Серверный манифест — это, по сути, список доступных версий игрового сервера. Он используется watchdog’ом для скачивания новых обновлений сервера.
Дельта-загрузки клиента или «клиентский CDN» используются игровыми клиентами для скачивания новых файлов при обновлении, загружая только необходимое.
Можно запускать Robust.Cdn без использования поддержки серверных манифестов. Фактически до 2.0 Robust.Cdn занимался только клиентским CDN. Подробности смотрите в остальной части документации.
Форк — это отдельный «поток разработки» игры. Например, апстрим Space Station 14 (Wizard’s Den) и Rouny’s Marine Corps будут считаться двумя разными форками. Robust.Cdn может управлять несколькими форками одновременно, независимо друг от друга.
Версия или сборка — это просто одна версия игры в форке. Всегда предполагается, что серверы используют самую свежую версию, но Robust.Cdn также раздаёт и более старые версии.
Установка
Мы предоставляем официальные образы контейнеров Robust.Cdn через GitHub Container Registry. Кроме того, у нас есть инструкции по ручной публикации проекта через .NET SDK.
Образ контейнера
Последний стабильный образ Robust.Cdn — ghcr.io/space-wizards/robust.cdn:2. Информация о контейнере:
- Слушает порт 8080
- UID/GID по умолчанию — 1654
- Важные тома для монтирования:
/app/appsettings.json: основной файл конфигурации./builds: содержит zip-файлы серверных/клиентских сборок./manifest: содержит базу данных SQLite для операций с серверным манифестом./database: содержит базу данных SQLite для загрузок клиентского контента.
Вот пример docker-compose.yml, измените его под свои нужды.
services:
robust_cdn:
image: ghcr.io/space-wizards/robust.cdn:2
container_name: robust_cdn
user: 1654:1654
volumes:
- ./appsettings.json:/app/appsettings.json
- ./builds:/builds
- ./manifest:/manifest
- ./database:/database
ports:
- 8080:8080
restart: unless-stopped
Возможно, вам придётся выполнить эти команды, чтобы задать правильного владельца и права для папок builds, manifest и database. Если вы получите ошибку unable to open database file, попробуйте команды ниже.
sudo chown -R 1654:1654 builds/ database/ manifest/
sudo chmod -R u+w,g+w builds/ database/ manifest/
Ручная компиляция
Если вы ненавидите контейнеры, вы можете вручную опубликовать Robust.Cdn и развернуть файлы самостоятельно. Для этого вам понадобятся Git и .NET 10 SDK. На сервере, который будет запускать сборку, должна быть установлена соответствующая ASP.NET Core Runtime, но сам SDK не нужен.
Клонируйте git-репозиторий, затем опубликуйте:
git clone https://github.com/space-wizards/Robust.Cdn.git
cd Robust.Cdn
dotnet publish -c Release -r linux-x64 --no-self-contained
Готовая сборка будет помещена в Robust.Cdn/bin/Release/net9.0/linux-x64/publish. Вы можете скопировать их в какое-нибудь случайное место по вкусу, например /opt, и запускать Robust.Cdn оттуда. Например:
/opt/robust_cdn/
├── appsettings.json
├── bin
│ ├── Robust.Cdn
│ ├── Robust.Cdn.dll
.
Сами файлы программы находятся во вложенной папке, а мы запускаем её из родительского каталога, чтобы вы не снесли файлы конфигурации обновлениями или вроде того.
Затем вы можете автоматически запускать Robust.Cdn с помощью следующего определения службы systemd:
# /etc/systemd/system/robust-cdn.service
[Unit]
Description=Robust.Cdn
[Service]
Type=notify
WorkingDirectory=/opt/robust_cdn/
ExecStart=/opt/robust_cdn/bin/Robust.Cdn
User=robust_cdn
[Install]
WantedBy=multi-user.target
Конфигурация
Robust.Cdn — это приложение ASP.NET Core, поэтому оно поддерживает конфигурацию как через файл конфигурации, так и через другие источники, например переменные окружения. Более подробный обзор можно найти в документации ASP.NET Core.
Большая часть конфигурации Robust.Cdn выполняется через файл конфигурации appsettings.json. Вот полный справочник по его содержимому:
{
// Настройка уровня логирования, можно оставить значения по умолчанию.
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Robust": "Information"
}
},
// Содержит конфигурацию в первую очередь для работы с манифестом,
// но также необходим для операций CDN, чтобы настроить доступные форки.
"Manifest": {
// Место на диске, где должны храниться сборки.
// Опустите это при использовании официальных образов контейнеров.
"FileDiskPath": "/var/robust-cdn/builds",
// Файл базы данных, содержащий информацию для функций серверного манифеста.
// Опустите это при использовании официальных образов контейнеров.
"DatabaseFileName": "/var/robust-cdn/manifest.db",
// Набор доступных форков, которые должен обслуживать Robust.Cdn.
"Forks": {
// Конфигурация для одного форка. Ключ здесь — это ID форка, который будет использоваться во многих местах.
// ВЫ ДОЛЖНЫ СДЕЛАТЬ ID ФОРКА АБСОЛЮТНО ГЛОБАЛЬНО УНИКАЛЬНЫМ, ЧТОБЫ ИЗБЕЖАТЬ ПРОБЛЕМ.
// НЕ ПИШИТЕ ЗДЕСЬ ПРОСТО "TEST".
"test": {
// Токен, используемый для публикации новых версий в этот форк.
// ***ВЫ ДОЛЖНЫ ИЗМЕНИТЬ ЕГО НА НОВОЕ УНИКАЛЬНОЕ ЗНАЧЕНИЕ***.
"UpdateToken": "foobar",
// Конфигурация для уведомления экземпляров SS14.Watchdog о новых обновлениях. Можно указать несколько.
"NotifyWatchdogs": [
{
// Базовый адрес watchdog.
"WatchdogUrl": "http://localhost:5000/",
// Конкретный экземпляр сервера на watchdog, который нужно уведомить.
"Instance": "syndicate_mothership",
// ApiToken, указанный в конфигурации watchdog для этого экземпляра.
"ApiToken": "Honk"
}
],
// Установите true, чтобы сделать этот форк «приватным».
// Приватные форки ограничивают доступ к серверным сборкам, что желательно для серверов с секретным контентом.
// Подробности ниже.
"Private": false,
// Комбинации имени пользователя и пароля для доступа к серверным файлам приватных форков.
// Игнорируется, если форк не приватный.
"PrivateUsers": {
"foobar": "baz"
},
// Сколько дней хранить старые файлы сборок.
"PruneBuildsDays": 90,
// Приятное человекочитаемое отображаемое имя этого форка.
// Оно отображается в таких местах, как HTML-страница сборок.
"DisplayName": "Test Fork",
// Назначение ссылки на HTML-странице сборок.
"BuildsPageLink": "https://example.com",
// Текст ссылки на HTML-странице сборок.
"BuildsPageLinkText": "Test Fork LINK"
}
}
},
// Конфигурация в первую очередь для клиентского CDN.
"Cdn": {
// Файл базы данных, содержащий информацию для функций серверного манифеста.
// Опустите это при использовании официальных образов контейнеров.
"DatabaseFileName": "/var/robust-cdn/content.db",
// Увеличьте это, чтобы снизить использование пропускной способности при больших загрузках. Большие значения требуют больше CPU.
"StreamCompressLevel": 5,
// «Резервный» форк для функции миграции из Robust.Cdn 1.x.
// Это можно опустить для новых установок.
"DefaultFork": "test"
},
// Корневой URL, по которому ваш сервер Robust.Cdn глобально доступен.
// Это необходимо для правильной генерации метаданных сборки.
"BaseUrl": "https://<robust-cdn-url>/",
// Базовый путь, по которому доступен Robust.Cdn.
// Его следует задать, когда Robust.Cdn проксируется через обратный прокси по подпути.
// См. также дополнительные примечания ниже.
"PathBase": "/",
// Допустимые имена хостов, которые клиенты могут использовать для подключения к Robust.Cdn.
// Можно просто оставить как есть.
"AllowedHosts": "*",
// Здесь можно изменить порт, к которому привязывается Robust.Cdn.
// Опустите это при использовании официальных образов контейнеров.
"Urls": "http://localhost:27690/",
}
Примеры конфигураций обратного прокси
Robust.Cdn — это HTTP-сервис, поэтому вам, вероятно, захочется запустить его за обратным прокси какого-либо рода. Есть несколько вещей, в которых нужно убедиться:
- При использовании публикации с несколькими запросами следует задать максимальный размер тела запроса клиента, достаточный, чтобы вместить всю клиентскую загрузку сразу.
- При использовании одноразовой публикации следует задать таймаут запроса достаточно высоким (обычно больше минуты или двух).
Если вы используете Cloudflare для управления своим доменом, вы не должны размещать Robust.Cdn за обратным прокси Cloudflare. Это нарушение их Условий обслуживания и, вероятно, приведёт к серьёзному ограничению вашей пропускной способности.
Вот несколько примеров конфигураций для вашего обратного прокси:
Nginx
Этот пример предназначен для вставки в существующий блок server вашей конфигурации (терминация TLS, имя сервера и т. д…)
# gzip-сжатие JSON-ответов.
gzip on;
gzip_types application/json;
location / {
# Увеличенный максимальный размер тела для публикаций с несколькими запросами. Не требуется для одноразовых публикаций.
client_max_body_size 512m;
# Не буферизировать тела запросов внутри nginx, особенно важно для публикаций с несколькими запросами.
proxy_request_buffering off;
# Отключить буферизацию исходящих ответов.
proxy_buffering off;
# Обеспечить возможность потоковой передачи запроса и ответа через HTTP 1.1.
proxy_http_version 1.1;
# Увеличенный таймаут чтения, чтобы избежать таймаутов на конечной точке API публикации.
# Строго говоря, не обязателен для публикаций с несколькими запросами, но не повредит.
proxy_read_timeout 120s;
# Шаблонная конфигурация обратного прокси.
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Измените порт здесь.
proxy_pass http://localhost:8080;
}
Caddy
Это пример Caddyfile для вставки в существующий блок для домена с CDN. Если вы помещаете это в путь, можете просто разместить всё это под этим путём.
# Увеличенный максимальный размер тела для публикаций с несколькими запросами.
request_body {
max_size 512MB
}
# Измените порт здесь.
reverse_proxy localhost:8080 {
flush_interval -1
}
# Сжатие JSON-ответов.
encode zstd gzip {
match header Content-Type application/json*
}
Настройка публикации
GitHub Actions
Если репозиторий вашего форка размещён на GitHub, самый простой способ автоматически публиковать новые сборки в Robust.Cdn — через конфигурацию GitHub Actions, доступную в кодовой базе. Именно так публикуются официальные сборки Wizard’s Den.
- Отредактируйте
Tools/publish_multi_request.py, чтобы изменить «параметры конфигурации» в начале скрипта:
ROBUST_CDN_URLдолжен быть URL, по которому доступен Robust.Cdn.FORK_IDдолжен быть ID форка, который вы настроили вappsettings.json
-
Создайте секрет Actions в вашем репозитории GitHub с именем
PUBLISH_TOKEN, содержащийUpdateToken, указанный для вашего форка вappsettings.json. -
Убедитесь, что рабочий процесс «Publish» запущен, или запустите его вручную.
Это всё, что вам нужно!
Собственный процесс
Для тех, кто хочет настроить собственные процессы публикации без GitHub Actions, также можно использовать скрипт Tools/publish_multi_request.py. Рекомендую посмотреть на рабочий процесс Actions как на справочник по необходимым шагам.
Конфигурация watchdog
Настроить SS14.Watchdog на использование вашего нового Robust.Cdn для получения сборок довольно просто. В конфигурации экземпляра введите что-то вроде этого:
UpdateType: "Manifest"
Updates:
# Замените на собственный URL Robust.Cdn и ID форка.
ManifestUrl: "https://<robust-cdn-url>/fork/<fork>/manifest"
Вероятно, вы также захотите настроить NotifyWatchdogs в конфигурации форка Robust.Cdn, чтобы он уведомлял SS14.Watchdog о появлении новой версии. Смотрите справочник выше.
HTML-страница сборок
Robust.Cdn создаёт простую HTML-веб-страницу, позволяющую людям вручную скачивать последние серверные сборки. Эта страница автоматически доступна по адресу /fork/<fork_id>.
Например: сборки Wizard’s Den.
Собственный PathBase
Если по какой-то причине у вас не может быть поддоменов (серьёзно, используйте поддомены, если можете), вы захотите разместить несколько сервисов за одним доменом с помощью обратного прокси, такого как nginx. В этом случае вам нужно задать PathBase в файле конфигурации, чтобы ссылки на HTML-странице сборок работали. На остальную функциональность API это не влияет.
Например, если вы хотите разместить CDN по адресу https://example.com/cdn/, настройте это так:
"BaseUrl": "https://example.com/cdn/",
"PathBase": "/cdn/",
Убедитесь, что ваш обратный прокси настроен правильно: он должен передавать полный путь к Robust.Cdn, то есть не отсекать сам префикс пути. При использовании nginx это достигается так:
# Обратите внимание на завершающий слэш!
# плохо
proxy_pass http://127.0.0.1:8080/;
# хорошо
proxy_pass http://127.0.0.1:8080;
Приватные форки
Форк можно пометить как «приватный». Это не позволяет Robust.Cdn предоставлять неавторизованным людям доступ к серверным сборкам, что желательно для форков с секретным контентом. Доступ ограничен с помощью HTTP Basic-аутентификации. Имена пользователей и пароли для этого можно настроить в конфигурации форка.
Чтобы предоставить watchdog доступ к этим сборкам, настройте его так в конфигурации обновления экземпляра:
UpdateType: "Manifest"
Updates:
# Замените на собственный URL Robust.Cdn и ID форка.
ManifestUrl: "https://<robust-cdn-url>/fork/<fork>/manifest"
Authentication:
Username: foobar
Password: baz
Структура файлов сборок
Robust.Cdn хранит и ожидает zip-файлы сборок в каталоге FileDiskPath (/build при использовании образа контейнера). Файлы в этом каталоге имеют довольно простую структуру <fork>/<version>/<file>.zip. Например:
/var/robust-cdn/builds
├── wizards
│ ├── 02030cfa0ed6511ec5527c5b7d1f8bcd46fe1435
│ │ ├── SS14.Client.zip
│ │ ├── SS14.Server_linux-arm64.zip
│ │ ├── SS14.Server_linux-x64.zip
│ │ ├── SS14.Server_osx-x64.zip
│ │ └── SS14.Server_win-x64.zip
│ ├── 021d39be2876f991c5fd6e663760a921d29ac694
│ │ ├── SS14.Client.zip
│ │ ├── SS14.Server_linux-arm64.zip
Устранение неполадок
504 gateway timeout во время публикации
Увеличьте таймаут ответа вашего обратного прокси. В nginx это управляется через proxy_read_timeout.
Ошибки соединения во время публикации (публикация с несколькими запросами)
Убедитесь, что максимальный размер тела запроса вашего обратного прокси задан достаточно высоким, чтобы позволить
Ошибка 404 not found в CDN API во время публикации
Убедитесь, что вы используете последнюю версию Robust.Cdn. В версии 2.2.0 добавлен новый механизм публикации, используемый инфраструктурой апстрима.
Миграция с Robust.Cdn 1.x
Если вы размещали существующую установку Robust.Cdn до добавления поддержки нескольких форков/серверного манифеста (1.0), эта часть руководства поможет вам выполнить миграцию.
В рамках поддержки нескольких форков и манифеста как минимум потребуется внести следующие изменения в вашу установку:
Cdn.UpdateTokenв конфигурации перемещён в конфигурацию форка.Cdn.VersionDiskPathфактически заменён наManifest.FileDiskPath. Обратите внимание, что структура файлов отличается, вам нужно вручную переместить сборки на одну папку вниз, чтобы они оказались внутри папки форка (см. выше).
Robust.Cdn автоматически перенесёт вашу существующую базу данных клиентского контента так, что всей хранящейся в ней информации о версиях будет назначен форк. Вы должны задать Cdn.DefaultFork в конфигурации, чтобы он знал, какому форку назначать эти версии. Существующие URL (для реплеев и т. п.) продолжат работать после этого, поскольку настройка Cdn.DefaultFork заставит CDN внутренне сопоставить старые URL /version/{version}/* с новыми под указанным форком.
После внесения вышеуказанных изменений новую версию Robust.Cdn всё ещё можно использовать со старым процессом публикации (с помощью gen_build_info.py и всех остальных скриптов). Просто учтите изменение структуры файлов и тому подобное. Очевидно, однако, мы рекомендуем как можно скорее перейти на новую встроенную систему публикации.
Импорт содержимого существующего серверного манифеста
Вы можете вручную импортировать существующий серверный манифест в базу данных манифеста с помощью следующего скрипта Python. Учтите, что это действительно необходимо, только если вам важно иметь возможность легко получать доступ к старым версиям сервера через HTML-страницу или вроде того, пропуск этого шага
Это очень коряво, и вам нужно будет хотя бы один раз опубликовать сборку обычным способом, чтобы JSON-манифест сервера перекэшировался и стал доступен. Но эй, это работает. Если скрипт не работает из-за отсутствия dateutil, на Python 3.10+ можно удалить код dateutil, заменив dateparser.parse на datetime.fromisoformat.
#!/usr/bin/env python3
import sqlite3
import json
import re
from datetime import datetime, timezone
from dateutil import parser as dateparser
JSON_FILE = "manifest.json"
DB_FILE = "manifest.db"
FORK_NAME = "wizards"
def main():
data = json.loads(open(JSON_FILE, "r").read())
db = sqlite3.connect(DB_FILE)
cur = db.cursor()
cur.execute("SELECT Id FROM Fork WHERE Name = ?", (FORK_NAME,))
fork_id = cur.fetchone()[0]
for name, build in data["builds"].items():
time = dateparser.parse(build["time"])
time = time.astimezone(timezone.utc)
cur.execute(
"INSERT INTO ForkVersion (Name, ForkId, PublishedTime, ClientFileName, ClientSha256, Available, EngineVersion) VALUES (?, ?, ?, ?, ?, TRUE, '')",
(name, fork_id, time, file_name(build["client"]["url"]), bytes.fromhex(build["client"]["sha256"])))
version_id = cur.lastrowid
for rid, server in build["server"].items():
cur.execute(
"INSERT INTO ForkVersionServerBuild (ForkVersionId, Platform, FileName, Sha256) VALUES (?, ?, ?, ?)",
(version_id, rid, file_name(server["url"]), bytes.fromhex(server["sha256"])))
db.commit()
def file_name(url: str) -> str:
return url.split("/")[-1]
main()
Справочник по API Robust.Cdn
Эта часть руководства объяснит все конечные точки API Robust.Cdn, которые вы можете использовать и с которыми можете взаимодействовать.
Аутентификация
Некоторые конечные точки API могут требовать аутентификации:
- Конечные точки управления форком, такие как публикация, требуют
Authorization: Bearer <updateToken>, сUpdateToken, указанным в конфигурации форка. - Конечные точки для доступа к серверным файлам требуют Basic-аутентификации, если форк настроен как приватный.
Публикация
Существует два отдельных API для публикации: «одноразовый» и «с несколькими запросами». Одноразовый API находится по адресу /fork/{fork}/publish, а API с несколькими запросами — по адресу /fork/{fork}/publish/{start,file,finish}. Мы рекомендуем API публикации с несколькими запросами, и именно его используют официальные скрипты публикации.
GET /fork/{fork}
Возвращает приятную человекочитаемую HTML-страницу о последних доступных сборках.
POST /fork/{fork}/control/update
Даёт клиентскому CDN указание пересканировать наличие новых файлов. Вы можете запустить это вручную, когда не используете поддержку серверного манифеста Robust.Cdn и применяете только клиентский CDN. Вам нужно будет разместить файлы в правильной структуре, как указано ниже.
Для этого требуется аутентификация.
GET /fork/{fork}/manifest
Возвращает JSON-список всех серверных сборок, доступных для форка.
POST /fork/{fork}/publish
Публикует новую версию в CDN за один запрос API. В отличие от API «с несколькими запросами», описанного ниже.
Он ожидает тело JSON со следующей информацией:
{
"version": "<version>",
"engineVersion": "<engine version>",
"archive": "<builds archive URL>"
}
version — это номер новой публикуемой версии. Это может быть что угодно. Engine version — это номер версии движка, который нужно использовать.
archive должен быть URL на zip-архив, который Robust.Cdn скачает и который содержит zip-файлы сборок (клиента и сервера).
Для этого требуется аутентификация.
POST /fork/{fork}/publish/start
Начинает новую операцию публикации, включающую несколько последующих запросов API. Начальное тело JSON-запроса выглядит так:
{
"version": "<version>",
"engineVersion": "<engine version>"
}
version — это номер новой публикуемой версии. Это может быть что угодно. Engine version — это номер версии движка, который нужно использовать.
Если публикация для указанного номера версии уже выполняется, она прерывается, и вы получаете чистый лист.
Для этого требуется аутентификация.
POST /fork/{fork}/publish/file
Добавляет дополнительный файл к публикации, выполняемой в данный момент.
Содержимое файла передаётся в теле запроса как application/octet-stream. Дополнительные метаданные следует передавать в следующих HTTP-заголовках:
Robust-Cdn-Publish-File: имя публикуемого файла. Обычно это что-то вродеSS14.Client.zipилиSS14.Server_win-x64.zip.Robust-Cdn-Publish-Version: номер версии, в которую публикуется файл (указанный ранее).
Для этого требуется аутентификация.
POST /fork/{fork}/publish/finish
Завершает публикацию новой версии, начатую ранее.
{
"version": "<version>"
}
version — это номер новой публикуемой версии.
Если публикация не проходит проверку (например, отсутствуют клиентские файлы), публикация прерывается, и её нужно начинать с нуля.
Для этого требуется аутентификация.
POST & OPTIONS /fork/{fork}/version/{version}/download
Конечная точка загрузки клиентского CDN для версии. Подробности см. в разделе Дельта-обновления.
GET /fork/{fork}/version/{version}/file/{file}
Скачивает zip-архив серверной или клиентской сборки из версии. File — это имя файла.
GET /fork/{fork}/version/{version}/manifest
Конечная точка манифеста клиентского CDN для версии. Подробности см. в разделе Дельта-обновления.
POST & OPTIONS /version/{version}/download
Резервная конечная точка, сопоставляемая с DefaultFork, если он настроен. Это для совместимости URL со старыми установками Robust.Cdn.
GET /version/{version}/manifest
Резервная конечная точка, сопоставляемая с DefaultFork, если он настроен. Это для совместимости URL со старыми установками Robust.Cdn.