Учебное руководство
Главы руководства
На этой странице

Пилот переезда на SourceCraft

Production продолжает работать на GitHub/GHCR. Целевой приватный репозиторий — zakharov-dev/gheilt, организация переименована из tapok-me; SSH-адрес теперь ssh://git@ssh.sourcecraft.dev/zakharov-dev/gheilt.git. ID репозитория сохранился. Основной checkout с неопубликованными коммитами не переносился.

Пилот доставки документации проверен 2026-10-05: PR №8 слит, автоматический registry-pilot №5 на SHA 6af691fd5a341b231090295d3fe0321dbde3286c выполнил проверки, собрал образ в CI и опубликовал его в YC. VPS получил образ по digest, сверил revision и HTTP 200; временные контейнер, Docker credentials и исходный token-file удалены. Это ещё не переключение production и не перенос остальных образов. Перевод CI на OIDC ниже требует отдельной проверки итогового SHA.

Реестр

Используем существующий Yandex Container Registry zakharov-dev (crpml6ifd12s8cbmbg3m), отдельный repository gheilt-docs. Это не встроенный registry SourceCraft: образ будет иметь адрес cr.yandex/crpml6ifd12s8cbmbg3m/gheilt-docs@sha256:…. Создавать новый платный реестр для пилота не требуется. Хранение новых слоёв расходует ресурсы существующего реестра; другие repositories не меняем.

Персональный SourceCraft PAT хранится локально в ~/.local/sourcecraft-token, права 600. Он нужен для PR, запуска CI и чтения артефактов, не для YC registry. Личный IAM-токен YC в SourceCraft или на VPS не передаём.

OCI-параметр scope недостаточен для ограничения прав: на проверке YC вернул bearer, который мог читать и другой repository. Доступ ограничивают IAM-роли. Для VPS используется отдельный gheilt-vps-registry-puller с ролью puller только на gheilt-docs; попытка публикации и чтения чужого repository вернула 403.

Для первоначального пилота создан gheilt-sourcecraft-ci с repository-only pusher. Новое сервисное подключение использует созданный владельцем аккаунт sourcecraft (ajeemhmv7ujda2j9cbmf). На проверке 2026-10-06 ему выданы роли Container Registry на весь каталог, включая editor; federated credential разрешает repo:zakharov-dev/*. Для scope: repo CI выдаёт subject repo:zakharov-dev/gheilt; он не совпадает со строкой repo:zakharov-dev/*. Поэтому добавлена отдельная federated credential с точным subject repo:zakharov-dev/gheilt на той же federation. Старую привязку организации не удаляли. identity-check выводит только iss, aud, sub, без JWT.

Права Container Registry на весь каталог шире, чем нужны Gheilt. Текущая конфигурация не утверждает, что этот аккаунт изолирован от других проектов; изменение выданных владельцем прав требует учитывать остальные подключения организации.

Первая инициализация repository

Container Registry создаёт repository при первой публикации образа. Чтобы не дать CI широкие права на весь общий registry, первый образ собирается в SourceCraft workflow artifact-pilot, без registry credentials. Workflow сначала выполняет те же проверки, затем существующий infra/docs/Dockerfile и docker save.

Артефакты sourcecraft-artifacts/docs.tar.gz и archive.json содержат готовый образ, commit SHA, номер запуска, Docker image ID и SHA-256 сжатого архива. Образ сжат gzip; размер проверяется до выгрузки, чтобы вместе с manifest не превысить лимит 100 МБ артефактов одного кубика. Перед загрузкой проверяем успешный запуск, точный SHA, хеш архива и image ID. Инициализация загружает готовый CI-образ и публикует его в выделенный repository; локальной сборки нет. Затем выдаём сервисным учёткам права только на уже созданный repository.

Артефакты CI — временный транспорт пилота, а не постоянное хранилище релизов. Основной путь остаётся CI → registry → VPS по digest.

Конфигурация и публикация

Настройка находится в .sourcecraft/ci.yaml, используется compute, без GitHub Actions и SSH-ключа VPS. PR запускает проверки, main — registry-pilot. Для bootstrap до слияния можно запустить workflow через API, указав проверенный commit одновременно в head.commit и config_revision.commit.

Сервисное подключение yandex-cloud связывает SourceCraft с аккаунтом sourcecraft. В YAML раздел tokens.registry с scope: repo запрашивает ID token. Кубик registry-iam использует официальный cr.yandex/sourcecraft/yc-iam, закреплённый по digest, и обменивает ID token на IAM token через federation. Кубики публикации и отдельной проверки подключения получают ${{ cubes.registry-iam.outputs.IAM_TOKEN }}.

Python получает IAM token из environment, удаляет его из environment до Docker build, после успешной сборки обменивает на OCI bearer через cr.yandex/v2/token/ и записывает bearer в временный Docker config с правами 600. В args команд, образе, артефакте pilot.json и логах credentials не сохраняются. Ошибки HTTP выдаются без тела ответа и заголовков. Постоянные ключи сервисного аккаунта не требуются.

Registry prefix — обычная настройка в YAML: cr.yandex/crpml6ifd12s8cbmbg3m/gheilt. Секреты PILOT_REGISTRY_BEARER и PILOT_REGISTRY_PREFIX больше не используются; старый bearer удаляем после успешной проверки новой публикации.

Manual workflow identity-check проверяет только получение IAM token, без сборки и публикации. При main-only доступе подключения его можно проверить до слияния:

src run trigger identity-check -R zakharov-dev/gheilt \
  --commit <main-SHA> --cfg-commit <проверенный-SHA-конфигурации>

Это подтверждает подключение, но не заменяет проверки нового кода на его SHA и публикацию после merge.

registry-pilot повторяет проверки, собирает образ документации в SourceCraft и публикует его. pilot.json содержит commit SHA, номер запуска и точный registry digest. Скрипт дополнительно проверяет событие, main ref и совпадение checkout SHA. Образы web, Strapi и Storybook переносятся после проверки этого первого пути.

SourceCraft merge-checks может показывать success при пустом списке CI. Агент обязан отдельно проверить успешный запуск на ожидаемом SHA и свежесть head/base; один общий зелёный статус недостаточен.

Проверка на VPS

После успешного CI скачиваем pilot.json именно из проверенного запуска. На VPS передаём временный bearer отдельной pull-only учётки в файл с правами 600.

python3 scripts/sourcecraft/pilot.py probe \
  --prefix cr.yandex/crpml6ifd12s8cbmbg3m/gheilt \
  --manifest /opt/gheilt/sourcecraft-pilot/pilot.json \
  --sha <полный-проверенный-SHA> \
  --token-file /opt/gheilt/sourcecraft-pilot/read-token

Скрипт скачивает образ по digest, сверяет digest и revision, открывает документацию на случайном порту только на loopback, проверяет HTTP и удаляет временный контейнер. Не принимает latest, чужой registry, другой SHA или дополнительный образ. Docker credentials живут во временном каталоге и удаляются после проверки. Исходный --token-file удаляет вызывающий контроллер в finally, в том числе при ошибке; сам probe его не удаляет. Production-сервисы, сети и volumes не меняются; образ остаётся в Docker cache. Публичный стенд на этом шаге не создаётся.

Критерии успеха

  • CI и публикация прошли на точном commit SHA.
  • VPS самостоятельно получил образ по исходящему HTTPS, проверил digest и HTTP 200.
  • Временный контейнер и Docker credentials удалены.
  • Права CI-учётки проверены и описаны явно; VPS не может публиковать.
  • PR-прогон не публикует образ и не подключает кубик получения IAM token.

Затем переносим остальные образы, снимок опубликованной CMS и манифесты с проверкой SHA и поколений стендов. Старый деплой выключаем только при проверенном переключении владельца окружений.

SourceCraft CI, сервисные подключения, права Container Registry.

Следующий этап: четыре образа и доставка

Workflow release-images на main выполняет проверки, затем отдельными кубиками собирает web, Strapi, Storybook и документацию существующими Dockerfile. registry-pilot сохранён как ручная диагностическая проверка документации. PR workflow получает только проверки, без IAM-кубика и CMS-секрета.

Web читает опубликованный контент текущей CMS https://cms.gheilt.mxsource.xyz. Репозиторный секрет STRAPI_READ_TOKEN передаётся через временный BuildKit secret, не через build-arg. Сам файл и Docker credentials удаляются при успехе и ошибке. Оригинальный внешний сайт не участвует. Аналитика в этом этапе не подключается; настройки production будут переноситься отдельно при переключении владельца деплоя.

После всех четырёх push проверяются commit, номер CI и digest каждого образа. Только полный набор публикуется как маленький OCI-образ gheilt-release:run-N с файлом /release.json. Этот образ не содержит ключей или исходного кода. Номера запусков заменяют общий изменяемый тег: приёмник должен выбирать максимальный run-N, а не последний записанный latest. Список тегов читается по страницам только внутри того же repository, с защитой от циклов и ограничением 64 страниц / 8 МБ. Образы приложений получаются по digest.

На этапе разработки этого изменения приёмник ещё не установлен. Публикация образов, получение их VPS и выкладка test — отдельные проверяемые состояния. Production по-прежнему обслуживается прежним pipeline до проверки нового пути.

Приёмник без входящего SSH

scripts/sourcecraft/receive.py обращается только к YC IAM и Container Registry по исходящему HTTPS. Он выбирает максимальный run-N, проверяет размер и SHA256 OCI layer, ограничивает распакованный tar до 256 КБ и читает только обычный файл release.json из памяти. Пути, ссылки и дополнительные файлы запрещены; распаковки на диск нет.

После скачивания всех четырёх образов проверяются registry digest и OCI revision. Только затем атомарно заменяется /opt/gheilt/sourcecraft/state/ready.json. Сбой оставляет предыдущий готовый манифест. Повторный опрос идемпотентен; запуск с меньшим номером не заменяет новый. Приёмник не запускает контейнеры приложения и не выполняет код из capsule. Manifest пока является входом для следующего этапа: выкладки main на test и управления окружениями.

Для VPS используется отдельная учётка gheilt-vps-registry-puller с pull-only правами на пять репозиториев gheilt. Её authorized key хранится только в /opt/gheilt/sourcecraft/pull-key.json с правами 600. Это постоянный закрытый ключ VPS; CI продолжает использовать OIDC без постоянного ключа. При каждом опросе Python и OpenSSL подписывают короткий PS256 JWT и получают временный IAM и registry bearer. JWT, ключи и токены не попадают в аргументы или логи.

Для установки администратор размещает pilot.py, release.py, receive.py в /opt/gheilt/sourcecraft, а unit-файлы из infra/deploy — в /etc/systemd/system. Каталог state принадлежит atmanki-deploy с правами 700; пользователь входит в уже существующую Docker-группу. Учётке дают puller именно на проектные repository, не на registry целиком. Затем:

sudo systemctl daemon-reload
sudo systemctl start sourcecraft-receive.service
sudo systemctl enable --now sourcecraft-receive.timer
sudo journalctl -u sourcecraft-receive.service -n 10 --no-pager

Остановить получение можно через systemctl disable --now sourcecraft-receive.timer. Текущие контейнеры, сети, volumes и production-маршруты при этом не меняются. Ключ отзывается отдельно в YC IAM после отключения приёмника.

Получение IAM по authorized key, переменные SourceCraft.