Skip to content

0065. Git LFS с блокировками поверх артефактов ​

Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-04. Второй протокол рядом с /api/v1 после реестра образов (ADR 0063); хранение и права — прежние.

Контекст ​

Основные проекты — игры на Unity и Unreal. Их ассеты (текстуры, модели, уровни, .uasset) хранят в Git LFS, а хостинги git ограничивают объём и трафик LFS. Ассеты не сливаются построчно, поэтому команда блокирует файл на время правки (File Locking API). Arkvory уже умеет то, что нужно LFS-серверу: потоковую передачу больших объектов, проверку SHA-256, квоты, резервные копии и зеркала. Анализ форматов (ADR 0064) поставил LFS сразу после raw-файлов.

Решение ​

Адрес. Каждый репозиторий Arkvory — LFS-сервер по адресу https://<host>/lfs/<repository>, это lfs.url git-репозитория (в .lfsconfig или git config). Сам git-репозиторий остаётся на прежнем хостинге: Arkvory хранит только объекты LFS и блокировки.

Протокол — подмножество спецификации git-lfs:

  • POST …/objects/batch: операция upload или download, до 1000 объектов, только адаптер basic и sha256. Иначе 422, а для другого алгоритма — 409.
  • Для upload объекты, которые уже есть (тот же oid и размер), идут без действий: клиент их не передаёт. Для download отсутствующий объект получает ошибку 404 внутри ответа.
  • PUT …/objects/<oid>: тело объекта с Content-Length. Хранилище артефактов сверяет размер и SHA-256 с oid, при несовпадении — 422, ничего не сохраняется. Уже сохранённый объект не передаётся повторно, а соединение закрывается.
  • GET|HEAD …/objects/<oid>: скачивание с Range, через общую очередь и ограничение полосы.
  • Блокировки:
    • POST …/locks создаёт блокировку; путь уже заблокирован — 409 с этой блокировкой.
    • GET …/locks — список с фильтром по пути или id, постранично.
    • POST …/locks/verify — «наши» и «чужие» перед push.
    • POST …/locks/<id>/unlock; force снимает чужую.
  • Ошибки — документ {message, request_id} с типом application/vnd.git-lfs+json.
  • Маршруты /lfs следуют спецификации git-lfs и в OpenAPI /api/v1 не входят.

Вход. Как у реестра образов: ключ Arkvory (сервисный, персональный токен или файловый) — пароль Basic, имя не проверяется; Bearer тоже принимается. На 401 сервер отвечает LFS-Authenticate: Basic realm="Arkvory", и git спрашивает учётные данные у своего хранилища (Git Credential Manager и т. п.). В действиях передачи (actions.*.header) клиенту возвращается его же Authorization: git-lfs отправляет с ним объект на тот же сервер по тому же TLS. Нужен HTTPS.

Права:

  • скачивание и batch download — content.read;
  • загрузка и batch upload — upload.create (файловые ключи — write);
  • создать, проверить и снять свою блокировку — upload.create; список — artifact.list;
  • снять чужую блокировку (force) — artifact.delete, как удаление артефактов;
  • зеркало и токен только для чтения загрузки и блокировки не принимают;
  • batch — POST и для скачивания, поэтому общий запрет POST для токена только для чтения его пропускает. Операцию upload по-прежнему отклоняет проверка прав: у такого токена их нет.

Хранение.

  • Объект — обычный артефакт: имя и SHA-256 — oid, метка lfs. Идемпотентность загрузки — по oid, поэтому повтор после обрыва продолжает тот же артефакт.
  • Миграция 31 (только расширение схемы): arkvory_lfs_objects(repository, oid → artifact) и arkvory_lfs_locks(id, repository, path, owner_id, owner_name, locked_at), один замок на путь в репозитории.
  • Имя владельца в блокировке — имя пользователя или сервисного аккаунта на момент блокировки, у файлового ключа — его ID.
  • Обе таблицы входят в резервные копии.
  • Объекты LFS защищены от retention причиной reference: сервер не видит историю git, поэтому объект хранится, пока его не удалит администратор.

Зеркала. Запись объекта пишет в ленту действие lfs.object (деталь — oid) в своей транзакции, под замком каталога репозитория. Зеркало после копии артефакта добавляет строку в свой индекс, а после засева проигрывает эти записи, как записи реестра. Блокировки не зеркалируются: в зеркале нечего блокировать.

Последствия ​

  • Удаления объектов и сборки неиспользуемых объектов нет. Это осознанно: на неверное удаление LFS отвечает сломанной историей.
  • Адаптер tus (докачка внутри объекта) не поддержан: git-lfs повторяет объект целиком. Отдельный объект больше сотен ГБ лучше держать в raw-файлах или UPack.
  • Шлюзы чтения (ADR 0009) LFS не обслуживают: batch — это POST, поэтому lfs.url указывает на пишущий шлюз.
  • Нет: SSH-входа (git-lfs-authenticate), прокси к чужому LFS-серверу, блокировок по веткам — они общие на репозиторий.

Проверки ​

  • tests/integration/git-lfs.test.mjs с настоящими git и git-lfs в изолированной конфигурации:
    • push двух больших файлов и clone в другой каталог со сверкой SHA-256;
    • повторный push без новых объектов;
    • lock, locks, повторный lock отклонён, unlock;
    • через HTTP: 401 с LFS-Authenticate, отсутствующий объект, неверный oid, sha1, тело не по oid, нехватка прав.
  • tests/integration/mirror-images.test.mjs: объекты LFS до и после засева скачиваются с зеркала, загрузка в зеркало отклоняется. tests/mirror-sync.test.mjs: проигрыш lfs.object.
  • В образ локального CI добавлен git-lfs; на раннерах GitHub он уже есть.

Дополнение: проверка перед 0.4.0 ​

  • Тела объектов принимаются при любом Content-Type. git-lfs определяет тип по содержимому, и текстовые ассеты (Unity Force Text: .prefab, .unity, .asset) приходят как text/plain. Раньше такие загрузки отклонялись.
  • Если загрузка под ключом lfs-<oid> истекла или отменена, объект загружается заново под новым ключом. Раньше одна оборванная попытка блокировала объект для владельца навсегда.
  • Наличие объекта проверяется по индексу без content.read: ключи только на запись видят, что объект уже есть.
  • Ссылки действий строятся из адреса, по которому пришёл клиент. X-Forwarded-Proto: https может только повысить схему. Authorization клиента возвращается лишь в ссылку https или loopback и никогда не уходит в открытый HTTP к другому хосту.
  • Страницы блокировок сравнивают курсор в порядке байтов. ID блокировки — строго UUID, иначе она просто неизвестна.

Arkvory · ProAnimaStudio