Обычные файлы
Путь файла в репозитории работает как файл на веб-сервере. PUT сохраняет тело запроса как следующую версию пути. GET возвращает текущую версию. Используйте это в сценариях сборки и заданиях CI, где есть curl или PowerShell и больше ничего.
Адрес один и тот же для всех трёх методов:
https://<хост>/api/v1/repositories/<репозиторий>/raw/<путь>Например: https://arkvory.example/api/v1/repositories/releases/raw/builds/game/1.4/GameSetup.exe.
Сохранение файла
Вам нужны репозиторий и ключ с доступом на запись. См. Учётные записи и ключи. Передавайте ключ как Authorization: Bearer <ключ>. Другие виды аутентификации для обычных файлов не принимаются.
export ARKVORY_KEY="$(cat ~/.arkvory/key)"
URL="https://arkvory.example/api/v1/repositories/releases/raw/builds/game/1.4/GameSetup.exe"
curl --fail-with-body -sS -T ./GameSetup.exe \
-H "Authorization: Bearer $ARKVORY_KEY" \
"$URL"$headers = @{ Authorization = "Bearer $env:ARKVORY_KEY" }
$url = 'https://arkvory.example/api/v1/repositories/releases/raw/builds/game/1.4/GameSetup.exe'
Invoke-RestMethod -Method Put -InFile .\GameSetup.exe -Headers $headers -Uri $urlwget -qO- --method=PUT --body-file=GameSetup.exe \
--header="Authorization: Bearer $ARKVORY_KEY" "$URL"Дайте curl -T полный адрес файла, а не папки. Кодируйте в пути символы, которые не допускаются в URL: пробел записывайте как %20, # — как %23, а ? — как %3F.
Ответ — JSON. Новый файл или новые байты возвращают 201:
{
"path": "builds/game/1.4/GameSetup.exe",
"revision": 1,
"created": true,
"artifact": { "id": "00000000-0000-4000-8000-000000000001", "size": "1048576", "sha256": "…" }
}Если по пути уже лежат именно эти байты, ответ — 200 с "created": false и той же версией. Ничего не сохраняется. Шаг задания CI можно запустить снова, не создавая новую версию. size — строка из десятичных цифр.
Отправка контрольной суммы
Отправьте SHA-256 файла в заголовке X-Checksum-Sha256, а длину — в Content-Length. curl -T и PowerShell отправляют длину для файла. Тогда сервер записывает байты прямо в хранилище за один проход и проверяет их там. Неверная контрольная сумма возвращает 422 с кодом integrity_mismatch, ничего не сохраняет и оставляет путь прежним.
curl --fail-with-body -sS -T ./GameSetup.exe \
-H "Authorization: Bearer $ARKVORY_KEY" \
-H "X-Checksum-Sha256: $(sha256sum GameSetup.exe | cut -d' ' -f1)" \
"$URL"$headers['X-Checksum-Sha256'] = (Get-FileHash .\GameSetup.exe -Algorithm SHA256).Hash.ToLower()
Invoke-RestMethod -Method Put -InFile .\GameSetup.exe -Headers $headers -Uri $urlБез контрольной суммы или с chunked-телом без Content-Length сервер сначала записывает тело во временный файл и вычисляет его хеш. Затем он сохраняет файл. На короткое время для этого нужно до удвоенного размера файла на диске сервера и второй проход по байтам. Временные файлы, оставшиеся после сбоя, сервер удаляет через сутки.
Если вы отправляете контрольную сумму и длину, а по пути уже лежат эти байты, сервер отвечает 200, не читая тело, и закрывает соединение.
Только создание
PUT учитывает только одно условие — If-None-Match: *. С ним сервер сохраняет файл, только если пути не существует. Иначе он отвечает 409 с причиной already_exists, даже если байты те же. Любое другое значение If-None-Match в PUT возвращает 400.
curl --fail-with-body -sS -T ./notes.txt \
-H "Authorization: Bearer $ARKVORY_KEY" -H "If-None-Match: *" \
"https://arkvory.example/api/v1/repositories/releases/raw/docs/notes.txt"Две одновременные записи
Когда два запроса одновременно меняют один путь, побеждает первый. Второй получает 409 с причиной revision_mismatch, а путь сохраняет содержимое победителя. Запустите запрос снова, чтобы создать новую версию. Загруженные байты проигравшего остаются артефактом без пути, пока их не удалят правила хранения.
Чтение файла
GET возвращает текущую версию пути. HEAD возвращает только заголовки.
curl --fail-with-body -sS -H "Authorization: Bearer $ARKVORY_KEY" -o GameSetup.exe "$URL"$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Headers $headers -Uri $url -OutFile .\GameSetup.exewget --header="Authorization: Bearer $ARKVORY_KEY" -O GameSetup.exe "$URL"Заголовки ответа:
| Заголовок | Значение |
|---|---|
ETag | "sha256:<дайджест>": SHA-256 содержимого в кавычках |
Content-Length | Размер файла |
Accept-Ranges | bytes |
Content-Type | Всегда application/octet-stream |
Content-Disposition | attachment с последним сегментом пути в качестве имени файла |
X-Arkvory-Artifact-Id | ID артефакта, в котором хранится эта версия |
Неизвестный путь возвращает 404.
Диапазоны и условные запросы
| Заголовок запроса | Действие |
|---|---|
Range: bytes=0-1023 | 206 с запрошенной частью и Content-Range. Начало за концом файла возвращает 416 с Content-Range: bytes */<размер>. |
Range: bytes=-1024 | Последние 1024 байта |
Range: bytes=1048576- | От смещения до конца |
If-Range: "sha256:…" | Применяет Range, только если ETag в точности такой. Если у пути новая версия, вы получаете весь новый файл. |
If-None-Match: "sha256:…" | 304 без тела, если ETag тот же. Работает и с HEAD. |
В одном запросе поддерживается только один диапазон. Запрос с несколькими диапазонами возвращает весь файл.
У пути в любой момент может появиться новая версия, а GET каждый раз разрешает путь заново. Чтобы безопасно продолжить скачивание, запомните ETag первого ответа и отправьте его в If-Range:
etag=$(curl -sSI -H "Authorization: Bearer $ARKVORY_KEY" "$URL" | awk 'tolower($1)=="etag:" {print $2}' | tr -d '\r')
curl -sS -H "Authorization: Bearer $ARKVORY_KEY" -H "If-Range: $etag" \
-H "Range: bytes=1048576-" "$URL" >> GameSetup.partЧтобы пропустить скачивание, если файл не изменился, отправьте ETag, который вы сохранили в прошлый раз:
curl -sS -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $ARKVORY_KEY" \
-H 'If-None-Match: "sha256:<дайджест>"' "$URL"Для больших файлов arkvoryctl get скачивает с возможностью продолжения и сам проверяет SHA-256.
Пути и версии
Обычный файл — это файл по пути. Каждый PUT с новыми байтами добавляет версию пути: версия 1, 2, 3 и так далее. Прежние версии остаются. Байты никогда не заменяются, потому что каждая версия указывает на собственный неизменяемый артефакт, названный по последнему сегменту пути.
GETпо обычному адресу всегда отдаёт текущую версию.- Чтобы увидеть все версии пути, прочитайте его историю:
getAssetHistoryили История файлов в консоли. - Чтобы прочитать более старую версию,
getAssetRevisionвозвращает её артефакт. Скачайте его по адресу содержимого артефакта. - Чтобы вернуться к старой версии, используйте
restoreAsset. Она добавляет новую версию, которая указывает на старые байты. - Чтобы перечислить пути репозитория по префиксу, используйте
listAssetPage. - Путь нельзя удалить. История остаётся. Правила хранения не удаляют артефакты, которые использует версия пути.
Те же операции есть в SDK (client.raw.putRawFile, downloadRawFile) и в arkvoryctl (put, get).
Правила путей
| Правило | Значение |
|---|---|
| Длина | От 1 до 1024 символов |
| Папки | Сегменты, разделённые / |
| Не допускаются | Пустой сегмент (a//b), . или .., обратная косая черта, двоеточие и управляющие символы |
curl и браузеры убирают . и .. из URL перед отправкой, поэтому такой путь никогда не доходит до сервера. Путь, нарушающий правила, возвращает 400.
Права
Персональные токены и файловые ключи получают доступ на чтение или запись к репозиторию. Ключи сервиса получают точные действия.
| Операция | Действия ключа сервиса | Персональный токен или файловый ключ |
|---|---|---|
GET, HEAD | content.read | Доступ на чтение |
PUT | upload.create, upload.write, upload.complete, asset.read, asset.write, artifact.read | Доступ на запись, область токена read-write |
Агенту развёртывания, который только скачивает, нужно действие content.read.
Шлюз чтения принимает только GET и HEAD. Зеркало обслуживает чтение и отклоняет PUT с 409 и причиной mirror_read_only.
Ограничения
| Ограничение | Значение |
|---|---|
| Размер файла | Максимальный размер объекта в установке, ARKVORY_MAX_OBJECT_BYTES (по умолчанию около 10 ТиБ) |
Один запрос PUT | Должен завершиться в течение 30 минут и не должен простаивать дольше 30 секунд (ARKVORY_UPLOAD_DEADLINE_MS, ARKVORY_UPLOAD_IDLE_TIMEOUT_MS). Это же наибольшие допустимые значения. |
| Одновременные загрузки | По умолчанию 2 на сервер и 1 на ключ (ARKVORY_MAX_UPLOADS, ARKVORY_MAX_UPLOADS_PER_PRINCIPAL). Ожидающий запрос сдаётся через 20 секунд с ответом 503. |
| Одновременные скачивания | По умолчанию 16 на сервер и 4 на ключ |
| Квота | Файл учитывается в квоте репозитория и в ёмкости установки |
У одиночного PUT нет продолжения: после сбоя он начинается с первого байта. Используйте обычные файлы для небольших и средних файлов и для скриптов. Для больших файлов или медленных сетей используйте arkvoryctl put или SDK. Они загружают по частям, продолжают после сбоя и проверяют SHA-256. Они также сохраняют файл как версию пути. Переменные описаны в разделе Переменные окружения.
Unity Addressables
Addressables загружают каталог и бандлы обычными запросами GET, поэтому папка сборки может лежать по raw-пути. Это подходит для внутренних сборок, QA и инструментов. Это не подходит для игроков в открытом интернете: чтение raw всегда требует ключа, ссылка на скачивание живёт не дольше 24 часов, а ключ внутри выпущенного клиента не секрет.
Загрузите папку командой arkvoryctl put. Файлы с неизменившимися байтами повторно не отправляются. Используйте отдельную папку на каждую сборку: путь всегда отдаёт новейшую версию, а старый каталог не должен встречаться с новыми бандлами:
cd ServerData/StandaloneWindows64
find . -type f | while read -r file; do
arkvoryctl put "$file" "addressables/game/$BUILD/StandaloneWindows64/${file#./}" || exit 1
done$root = (Resolve-Path .\ServerData\StandaloneWindows64).Path
Get-ChildItem $root -Recurse -File | ForEach-Object {
$relative = $_.FullName.Substring($root.Length + 1) -replace '\\', '/'
arkvoryctl put $_.FullName "addressables/game/$env:BUILD/StandaloneWindows64/$relative"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}Укажите в профиле Addressables удалённый путь загрузки — raw-адрес этой папки, например https://arkvory.example/api/v1/repositories/releases/raw/addressables/game/<build>/[BuildTarget]. Дайте клиенту ключ только на чтение: персональный токен доступа с чтением или ключ сервиса с действием content.read. Raw принимает ключ только в заголовке Authorization, поэтому задайте его в каждом запросе:
Addressables.WebRequestOverride = request =>
{
if (request.url.StartsWith("https://arkvory.example/"))
request.SetRequestHeader("Authorization", "Bearer " + readKey);
};В Addressables 1.x это свойство Addressables.WebRequestOverride; проверьте имя в своей версии. Это шаблон, а не проверенная интеграция.
Устранение неполадок
Ошибки — это JSON-документы с code, reason, message и requestId. См. Ошибки. Передайте requestId администратору, чтобы он нашёл запрос в журнале сервера.
| Статус | Reason | Причина | Что делать |
|---|---|---|---|
400 | validation | Путь, Content-Length, X-Checksum-Sha256 или If-None-Match недопустимы | Проверьте правила путей и закодируйте URL |
401 | credential_missing, credential_invalid, token_expired | Нет ключа, неверный ключ или токен истёк | Отправляйте Authorization: Bearer <ключ>. Basic-аутентификация для обычных файлов не работает. |
403 | permission_missing, read_only_token | Ключ не может записывать, или это токен только для чтения | Используйте ключ с действиями из раздела Права |
404 | Пути не существует, или ключ не видит репозиторий | Проверьте имя репозитория и путь | |
409 | already_exists | If-None-Match: *, а путь существует | Уберите заголовок, чтобы добавить версию |
409 | revision_mismatch | Другой запрос изменил путь раньше | Повторите запрос |
409 | mirror_read_only | Репозиторий — зеркало | Записывайте на основной сервер |
416 | range_not_satisfiable | Диапазон начинается после конца файла | Проверьте размер через HEAD |
422 | integrity_mismatch | Тело не соответствует X-Checksum-Sha256 или Content-Length | Вычислите контрольную сумму заново; проверьте прокси-сервер |
503 | busy | Слишком много одновременных передач | Подождите время из Retry-After и повторите |
507 | storage_quota | Достигнута квота репозитория или ёмкость установки | Освободите место или запросите большую квоту |
| Не статус | curl: (55) или (56) при отправке | Сервер закрыл соединение. Если по пути уже лежат эти байты, он отвечает 200 и закрывает соединение. | Запустите curl -i и прочитайте ответ |
| Не статус | Соединение закрывается через 30 минут | Предел времени загрузки | Используйте arkvoryctl put |