> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ss14.art/llms.txt
> Use this file to discover all available pages before exploring further.

# Настройка Robust.Cdn

[Robust.Cdn](https://github.com/space-wizards-federation/Robust.Cdn/) — это выделенный сервер для размещения и обслуживания файлов сборок игры для серверов Space Station 14. Он охватывает как управление сборками игровых серверов, так и дельта-загрузки клиентов, и рекомендуется для любого серьёзного постоянного хостинга серверов.

<Warning>
  Эта страница предполагает наличие достаточного предварительного опыта работы с различными концепциями системного администрирования Linux. И не копируйте вслепую всё подряд. У меня нет времени или энергии делать эту инструкцию «для самых маленьких», так что вам нужно будет правильно прочитать и понять всё, что здесь перечислено.
</Warning>

## Обзор

Стандартный рабочий процесс публикации для Space Station 14 выглядит так:

1. Сборки периодически создаются системой CI, такой как GitHub Actions.
2. Файлы сборок (клиентские и серверные zip-архивы) отправляются на центральный сервер. Серверные сборки индексируются, в них внедряется конфигурация для клиентского CDN, клиентские файлы обрабатываются, чтобы лаунчер мог их скачать.
3. Игровые серверы (через SS14.Watchdog) получают уведомление об обновлении и автоматически загружают новые серверные сборки с центрального сервера.
4. Внедрённая конфигурация игрового сервера используется для информирования клиентов о том, где они могут скачать файлы.

Robust.Cdn — это связующее звено, которое соединяет большую часть этого воедино:

* Получение новых сборок напрямую от GitHub Actions.
* Предоставление серверных сборок для доступа игровым серверам (watchdog).
* Автоматическое уведомление watchdog'ов о новом обновлении.
* Предоставление дельта-загрузок для клиентов.

## Концепции

Robust.Cdn в настоящее время выполняет две основные функции:

* Управление манифестами серверов
* Управление дельта-загрузками клиентов

**Манифест сервера** — это, по сути, список доступных версий игрового сервера. Он используется watchdog'ом для загрузки новых обновлений сервера.

**Дельта-загрузки клиентов** или «**клиентский CDN**» используются игровыми клиентами для загрузки новых файлов при обновлении, загружая только то, что необходимо.

<Info>
  Запускать Robust.Cdn можно и без использования поддержки манифеста сервера. Фактически, до версии 2.0 Robust.Cdn занимался только клиентским CDN. См. остальную документацию для подробностей.
</Info>

**Форк (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-сервиса:

```ini theme={null}
# /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
```

<Warning>
  Ради Мику и всего святого, не запускайте CDN напрямую из директории сборки. Пожалуйста.
</Warning>

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

Robust.Cdn — это ASP.NET Core-приложение, поэтому оно поддерживает настройку как через конфигурационный файл, так и через другие источники, например переменные окружения. Вы можете обратиться к [документации ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) для более подробного обзора.

Большая часть конфигурации Robust.Cdn осуществляется через конфигурационный файл `appsettings.json`. Вот полная справка по его содержимому:

<Danger>
  Вам следует полностью изучить этот конфигурационный файл, чтобы понять, что вы настраиваете.
</Danger>

```json theme={null}
{
  // Настройка уровня логирования, можете оставить по умолчанию.
  "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 публикации установите максимальный размер тела клиента, чтобы он вмещал всю клиентскую загрузку целиком.
* При использовании одноэтапной публикации установите достаточно большой тайм-аут запроса (обычно более минуты-двух).

```admonish failure title="Не размещайте его за Cloudflare" theme={null}
Если вы используете Cloudflare для управления вашим доменом, вы **не должны** размещать Robust.Cdn за обратным прокси Cloudflare. Это является нарушением их [Условий предоставления услуг](https://www.cloudflare.com/service-specific-terms-application-services/#content-delivery-network-terms) и, вероятно, приведёт к серьёзному ограничению пропускной способности.
```

Вот несколько примеров конфигураций для вашего обратного прокси:

### Nginx

Этот пример предназначен для размещения в существующем блоке `server` вашей конфигурации (терминация TLS, имя сервера и т.д.)

```nginx theme={null}
# 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`

2. Создайте секрет Actions в вашем GitHub-репозитории с именем `PUBLISH_TOKEN`, содержащий `UpdateToken`, указанный для вашего форка в `appsettings.json`.

3. Убедитесь, что рабочий процесс «Publish» запущен, или запустите его вручную.

Это всё, что вам нужно!

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

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

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

Настройка SS14.Watchdog для использования вашего нового Robust.Cdn для получения сборок довольно проста. В конфигурации экземпляра укажите примерно следующее:

```yml theme={null}
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](https://wizards.cdn.spacestation14.com/fork/wizards/).

### Пользовательский PathBase

Если по какой-то причине у вас не может быть поддоменов (серьёзно, используйте поддомены, если можете), вы можете захотеть разместить несколько сервисов за одним доменом с помощью обратного прокси, такого как nginx. Когда вы это делаете, вам нужно установить `PathBase` в конфигурационном файле, чтобы ссылки на HTML-странице сборок работали. На другие функции API это не влияет.

Например, если вы хотите разместить CDN по адресу `https://example.com/cdn/`, настройте это так:

```json theme={null}
"BaseUrl": "https://example.com/cdn/",
"PathBase": "/cdn/",
```

**Убедитесь, что ваш обратный прокси настроен правильно**: он должен передавать полный путь в Robust.Cdn, то есть не отсекать префикс пути самостоятельно. При использовании **nginx** это делается так:

```nginx theme={null}
# Обратите внимание на завершающий слеш!
# плохо
proxy_pass http://127.0.0.1:8080/;
# хорошо
proxy_pass http://127.0.0.1:8080;
```

## Приватные форки

Форк может быть помечен как «приватный». Это не позволяет Robust.Cdn предоставлять неавторизованным лицам доступ к серверным сборкам, что желательно для форков с секретным контентом. Доступ ограничен с помощью HTTP Basic authentication. Имена пользователей и пароли для этого можно настроить в конфигурации форка.

Чтобы предоставить watchdog'у доступ к этим сборкам, вы можете настроить его в конфигурации обновления экземпляра следующим образом:

```yml theme={null}
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`.

```py theme={null}
#!/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 со следующей информацией:

```json theme={null}
{
  "version": "<version>",
  "engineVersion": "<engine version>",
  "archive": "<builds archive URL>"
}
```

Version — это номер новой версии, которую вы публикуете. Это может быть что угодно. Engine version — это номер версии движка для использования.

Archive должен быть URL на zip-архив, который Robust.Cdn скачает, содержащий файлы сборок (клиентские и серверные).

Требует аутентификации.

### POST `/fork/{fork}/publish/start`

Начинает новую операцию публикации, включающую несколько последующих API-запросов. Начальное тело JSON-запроса выглядит так:

```json theme={null}
{
  "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`

Завершает публикацию новой версии, начатую ранее.

```json theme={null}
{
  "version": "<version>"
}
```

Version — это номер новой версии, которую вы публикуете.

Если публикация не проходит проверку (например, отсутствуют клиентские файлы), она будет прервана, и её придётся начинать заново.

Требует аутентификации.

### POST & OPTIONS `/fork/{fork}/version/{version}/download`

Эндпоинт загрузки клиентского CDN для версии. См. [Delta Updates](../other-projects/launcher/delta-updates-and-manifests) для подробностей.

### GET `/fork/{fork}/version/{version}/file/{file}`

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

### GET `/fork/{fork}/version/{version}/manifest`

Эндпоинт манифеста клиентского CDN для версии. См. [Delta Updates](../other-projects/launcher/delta-updates-and-manifests) для подробностей.

### POST & OPTIONS `/version/{version}/download`

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

### GET `/version/{version}/manifest`

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