Перейти к основному содержанию
Robust.Cdn — это выделенный сервер для размещения и обслуживания файлов сборок игры для серверов Space Station 14. Он охватывает как управление сборками игровых серверов, так и дельта-загрузки клиентов, и рекомендуется для любого серьёзного постоянного хостинга серверов.
Эта страница предполагает наличие достаточного предварительного опыта работы с различными концепциями системного администрирования Linux. И не копируйте вслепую всё подряд. У меня нет времени или энергии делать эту инструкцию «для самых маленьких», так что вам нужно будет правильно прочитать и понять всё, что здесь перечислено.

Обзор

Стандартный рабочий процесс публикации для Space Station 14 выглядит так:
  1. Сборки периодически создаются системой CI, такой как GitHub Actions.
  2. Файлы сборок (клиентские и серверные zip-архивы) отправляются на центральный сервер. Серверные сборки индексируются, в них внедряется конфигурация для клиентского CDN, клиентские файлы обрабатываются, чтобы лаунчер мог их скачать.
  3. Игровые серверы (через SS14.Watchdog) получают уведомление об обновлении и автоматически загружают новые серверные сборки с центрального сервера.
  4. Внедрённая конфигурация игрового сервера используется для информирования клиентов о том, где они могут скачать файлы.
Robust.Cdn — это связующее звено, которое соединяет большую часть этого воедино:
  • Получение новых сборок напрямую от GitHub Actions.
  • Предоставление серверных сборок для доступа игровым серверам (watchdog).
  • Автоматическое уведомление watchdog’ов о новом обновлении.
  • Предоставление дельта-загрузок для клиентов.

Концепции

Robust.Cdn в настоящее время выполняет две основные функции:
  • Управление манифестами серверов
  • Управление дельта-загрузками клиентов
Манифест сервера — это, по сути, список доступных версий игрового сервера. Он используется watchdog’ом для загрузки новых обновлений сервера. Дельта-загрузки клиентов или «клиентский CDN» используются игровыми клиентами для загрузки новых файлов при обновлении, загружая только то, что необходимо.
Запускать Robust.Cdn можно и без использования поддержки манифеста сервера. Фактически, до версии 2.0 Robust.Cdn занимался только клиентским CDN. См. остальную документацию для подробностей.
Форк (fork) — это отдельный «поток разработки» игры. Например, основная ветка Space Station 14 (Wizard’s Den) и Rouny’s Marine Corps считаются двумя разными форками. Robust.Cdn может управлять несколькими форками одновременно, независимо друг от друга. Версия (version) или сборка (build) — это просто отдельная версия игры на форке. Всегда предполагается, что серверы используют самую последнюю версию, но Robust.Cdn также обслуживает и старые версии.

Установка

Мы предоставляем официальные образы контейнеров Robust.Cdn через GitHub Container Registry. Кроме того, у нас есть инструкции по самостоятельной публикации проекта с помощью .NET SDK.

Образ контейнера

Последний стабильный образ Robust.Cdn — ghcr.io/space-wizards-federation/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-federation/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-federation/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
Ради Мику и всего святого, не запускайте CDN напрямую из директории сборки. Пожалуйста.

Конфигурация

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-сервис, поэтому вы, вероятно, захотите запускать его за каким-либо обратным прокси. Есть несколько вещей, которые нужно проверить:
  • При использовании multi-request публикации установите максимальный размер тела клиента, чтобы он вмещал всю клиентскую загрузку целиком.
  • При использовании одноэтапной публикации установите достаточно большой тайм-аут запроса (обычно более минуты-двух).
Не размещайте его за Cloudflare
Если вы используете Cloudflare для управления вашим доменом, вы **не должны** размещать Robust.Cdn за обратным прокси Cloudflare. Это является нарушением их [Условий предоставления услуг](https://www.cloudflare.com/service-specific-terms-application-services/#content-delivery-network-terms) и, вероятно, приведёт к серьёзному ограничению пропускной способности.
Вот несколько примеров конфигураций для вашего обратного прокси:

Nginx

Этот пример предназначен для размещения в существующем блоке server вашей конфигурации (терминация TLS, имя сервера и т.д.)
# gzip JSON-ответы.
gzip on;
gzip_types application/json;

location / {
    # Увеличенный максимальный размер тела для multi-request публикаций. Не обязателен для одноэтапных публикаций.
    client_max_body_size 512m;

    # Не буферизировать тела запросов внутри nginx, особенно важно для multi-request публикаций.
    proxy_request_buffering off;
    # Отключить буферизацию исходящих ответов.
    proxy_buffering         off;
    # Обеспечить возможность потоковой передачи запросов и ответов через HTTP 1.1.
    proxy_http_version      1.1;
    # Увеличенный тайм-аут чтения для избежания тайм-аутов на API эндпоинте публикации.
    # Не является строго необходимым для multi-request публикаций, но не помешает.
    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. Если вы помещаете это в подпуть, вы можете просто поместить всё это в этот подпуть.
# Увеличенный максимальный размер тела для multi-request публикаций.
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.
  1. Отредактируйте Tools/publish_multi_request.py, чтобы изменить «параметры конфигурации» в верхней части скрипта:
  • ROBUST_CDN_URL должен быть URL, по которому доступен Robust.Cdn.
  • FORK_ID должен быть ID форка, который вы настроили в appsettings.json
  1. Создайте секрет Actions в вашем GitHub-репозитории с именем PUBLISH_TOKEN, содержащий UpdateToken, указанный для вашего форка в appsettings.json.
  2. Убедитесь, что рабочий процесс «Publish» запущен, или запустите его вручную.
Это всё, что вам нужно!

Собственная публикация

Для тех, кто хочет использовать собственные рабочие процессы публикации без GitHub Actions, вы также можете использовать скрипт Tools/publish_multi_request.py. Я рекомендую посмотреть на Actions workflow как на справочный материал для требуемых шагов.

Конфигурация 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 builds.

Пользовательский 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 authentication. Имена пользователей и пароли для этого можно настроить в конфигурации форка. Чтобы предоставить 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.

Ошибки подключения во время публикации (multi-request publish)

Убедитесь, что максимальный размер тела вашего обратного прокси установлен достаточно высоким.

Ошибка 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-контента так, чтобы вся информация о версиях была привязана к форку. Вы должны установить 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 указан в конфигурации форка.
  • Эндпоинты для доступа к серверным файлам требуют базовой аутентификации, если форк настроен как приватный.

Публикация

Существует два отдельных API для публикации: «одноэтапный» (one-shot) и «многозапросный» (multi-request). Одноэтапный API находится по адресу /fork/{fork}/publish, в то время как многозапросный — по адресу /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 скачает, содержащий файлы сборок (клиентские и серверные). Требует аутентификации.

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 для версии. См. Delta Updates для подробностей.

GET /fork/{fork}/version/{version}/file/{file}

Загружает zip-архив серверной или клиентской сборки из версии. File — имя файла.

GET /fork/{fork}/version/{version}/manifest

Эндпоинт манифеста клиентского CDN для версии. См. Delta Updates для подробностей.

POST & OPTIONS /version/{version}/download

Запасной эндпоинт, который перенаправляется на DefaultFork, если он настроен. Это для совместимости URL со старыми установками Robust.Cdn.

GET /version/{version}/manifest

Запасной эндпоинт, который перенаправляется на DefaultFork, если он настроен. Это для совместимости URL со старыми установками Robust.Cdn.
Последнее изменение 21 июня 2026 г.