main

О билд-системах и собственном репозитории

Зачем оно вообще нужно? В процессе использования любого дистрибутива накапливается некий список улучшений, который ты воссоздаёшь каждый раз на новых машинах и виртуалках. Это могут быть:

  • конфиги
  • пакеты, которых нет в дистрибутиве
  • пакеты, сборка которых не устраивает по разным причинам
  • собственные проекты

Для меня актуальны последние три пункта. В четвёртом, дополнительно хочется ставить их как белый человек, а не методом слаквари.

Теория

Примерное движение пакетов и исходников показано на схеме. Обозначения:

  • .deb -- бинарный пакет
  • .dsc -- исходники ([d]ebian [s]our[c]es), комплект из 3х файлов, оригинальный тарбол, тарбол со служебными файлами из debian/ и собственно .dsc -- манифест с описанием, чексуммами 2х предыдущих архивов, gpg-подписями и т.д. (итого: .dsc, .debian.tar.gz, .orig.tar.gz)
  • .changes -- то же самое что и .dsc, но включает в себя ещё и итоговый .deb-пакет

Программы:

  • dput, dupload -- загрузка скомпиленнных пакетов на другой сервер по списку в .changes
  • reprepro -- управление репозиторием (метаданные, их подпись, файлы в пуле, acl'ы)
  • rebuildd, wanna-build -- софт для поддержания базы данных, в которой отслеживается состояние пакетов. Также управляет заданиями на сборку.
  • pbuilder, sbuild -- непосредственно сборка пакетов (для билдсервера)
  • debuild, dpkg-buildpackage -- ручная сборка пакетов (для dev-машины)
  • cowbuilder, cowdancer -- вспомогательные утилиты, ускоряют создание сборочной среды
  • gpg -- управление цифровой подписью
  • build-essential -- виртуальный пакет с минимально необходимыми зависимостями для сборки
  • devscripts -- дополнительные утилиты для сборки
  • mini-dinstall -- автоматическое обновление репозитория при загрузке

Реализация

Выглядит это так:

  • мы сидим на dev-машине, пишем код
  • закончили с кодом - пробуем собрать то что получилось через debuild/<...>
  • не получилось - см п. 1,
  • делаем dch -i, пишем список изменений, при необходимости - заново собираем .dsc через debuild -S
  • загружаем .dsc через dput/<...> на сервер, в incoming/ репозитория
  • включаем .dsc в репозиторий через reprepro includedsc <release> incoming/<package>_<ver>.dsc
  • добавляем задание на сборку или руками через sql-клиент или через rebuildd-job add на билд-машине

Пункты 6-7 можно автоматизировать через mini-dinstall. TODO: Однако, тут "есть ньюансы".

Затем в дело вступает наша хитрая машинерия:

  • rebuildd лезет в базу за новыми заданиями, и видит там что-то новое и лочит его
  • затем, обычным apt-get source пытается получить всё необходимое для сборки
    • вот здесь-то нас и поджидает первая свинья - если пакет был залит 5 минут назад, apt хост-системы про него ничего не знает
  • всё падает с ошибкой SOURCE_FAILED

  • после успешной выкачки source, rebuildd пытается построить chroot для сборки, поставив туда необходимые зависимости

    • здесь нас поджидает вторая свинья - одинаковый набор репозиториев+ключей должен присутствовать как на хосте, так и в чруте
  • иначе всё падает на dpkg-satisfy-depends

  • запускается непосредственно сборка пакета, в результате получится .changes

  • вот этот самый .changes при помощи dput/<...> переправляется на сервер с репозиторием, в упомянутый выше incoming/
    • третья свинья: в .changes входит deb и dsc. Внимание, вопрос: что будет, если 2 бид-сервера соберут пакет для разных архитектур и зальют его на один и тот же сервер? deb - разный, а вот dsc - один и тот же. Тут есть 2 варианта - или всё обломается на стадии загрузки: "не могу переписать уже залитый файл", или reprepro выдаст ошибку на этапе импорта: "у меня уже есть source с этим именем и версией, чего вы мне тут пытаетесь втулить?".
  • и да, кстати, насчёт "одинакового dsc/deb". Нифига он не одинаковый. В первом случае - меняется время перепакованных файлов, во втором - gcc-build-id. В результате, чексуммы разные -> кровь, кишки, распидорасило.

  • через reprepro includedeb <release> incoming/<package>_<ver>.deb в репу добавляется уже бинарный пакет

Недостатки есть и существенные. Первый и главный - использование средств хост-системы для разрешения зависимостей. Грабли пунктов 2-3 можно обойти, но этом используется изначально неправильный подход. Чруты должны быть полностью независимы от хоста на котором всё собирается.

А причина - в том, что многие забывают, что означает первая буква в pbuilder, который используется в подавляющем большинстве мануалов "как настроить свою репу". Я не говорю уже о том, что 90% из подобных мануалов - это инструкция вида

  • соберите пакеты
  • свалите всё в кучу
  • натравите reprepro
  • ...
  • профит

Второй главный недостаток, неустраняемый впринципе -- это многовековые слои отложений легаси в формате пакетов и сборочных средствах. Проще надо быть, блжад. Пусть даже ценой частичной потери совместимости. rpm-based, арч, гента, даже бсд как-то обходятся максимум парой файлов. Здесь их минимум 6. МИНИМУМ.

Третий недостаток, теоретически решаем, но с очередной порцией костылей: используется свой собственный формат в пакетах. Похожий на yaml, но другой. Это ограничивает возможности работы с метаданными до официально поддерживаемых утилит и порождает десятки велосипедов для его разбора и ренерации.

Посмотрите, например, в исходники debmirror'а. Или на мой велосипед в debian-repo-listing/, написать который заново оказалось быстрее по времени, чем продраться через лапшу в debmirror'е, который я честно смотрел перед этим.

Ну и ещё на закуску: для создания chroot'ов используется старый, древний и проверенный debootstrap. У меня была мысль понастроить lxc-контейнеров, заюзать снапшоты и исключить раз и навсегда debootstrap/cowdancer из процесса сборки. Зачем? Управлять проще, поддерживать проще, и заодно можно ресурсы резать. Потом посмотрел, сколько придётся перепахать инструментов (а ведь потом ещё это поддерживать) - и не стал, время можно потратить и с большей пользой.

В довершение скажу, что я не один с такими мыслями: вот здесь один из DD высказывает похожую мыслю в ключе "похороните уже это говно мамонта".