Как стать автором
Обновить

Стать мэинтейнером — обновляем репозиторий

Время на прочтение7 мин
Количество просмотров2.4K
Данная статья, не направлена на решение каких-либо фундаментальных проблем, но помогает, прилагая минимум усилий, автоматизировать сборку пакетов для вашего публичного репозитория.
Допустим, вы собираете для ваших любимых пользователей десять пакетов, да каждый под два дистрибутива (допустим, Debian unstable и Ubuntu jaunty), да еще и каждый под две архитектуры (amd64 и i386). Помните, мы с вами узнали про замечательный инструмент для создания репозиториев — reprepro. Так вот с тех пор он так и не научился включать в репозиторий пакеты пачками — только по одному. А это значит, что вам придётся вводить вашу gpg-подпись 10*2*2=40 раз. А как эффективно собирать эти 10 пакетов, особенно, если они обновляются ежедневно?

На самом деле, у меня ситуация была ещё хуже. В общей сложности при обновлении репозитория qutIM мне приходилось вводить свой отнюдь не короткий gpg-ключ аж 176 (сто семьдесят шесть) раз. Надо сказать, что к тому моменту, когда мне это окончательно надоело и перебороло мою лень, я выполнял эту процедуру всего за 3 минуты — по секунде на ввод. Но не будем вас истязать, сразу исправим ситуацию. Итак, устанавливаем пакет gnupg-agent и редактируем свой файл ~/.bashrc, добавляя в конце следующие строки:
  1. #
  2. #   запоминалка паролей для GnuPG
  3. #
  4. gpg-agent --daemon --enable-ssh-support --write-env-file "${HOME}/.gpg-agent-info"
  5. if [ -f "${HOME}/.gpg-agent-info" ]; then
  6. . "${HOME}/.gpg-agent-info"
  7. export GPG_AGENT_INFO
  8. export SSH_AUTH_SOCK
  9. export SSH_AGENT_PID
  10. fi

Теперь при старте локальной консоли у вас будет запускаться gpg-агент, «запоминающий» введенный вами gpg-ключ на некоторое время. Вуаля, мы избавились от одной большой проблемы.

Что дальше? Дальше сборка пакетов. Я расскажу про нее на примере всё того же моего любимого qutIM'а: в числе собираемых мною пакетов есть те, которые чуть ли не ежедневно обновляются из svn, а есть те, которые были скачаны один раз, и с тех пор просто пересобираются при обновлении ядра qutIM.
Для начала я расскажу немножко о том, как организована структура каталогов и именования пакетов у меня, чтобы было понятнее.
В корне сборочного каталога у меня изначально лежит только скрипт для сборки и каталоги тех пакетов, которые не обновляются из svn, например, qutim-plugin-floaties. В корень сборочного же каталога выкачиваются все каталоги исходников из svn и кладутся собранные пакеты. Еще в этом каталоге у меня есть замечательный подкаталог controls, в котором лежат debian-папки для пакетов. Например, чтобы найти debian-папку для пакета qutim-protocol-jabber, я обращаюсь по адресу controls/qutim-protocol-jabber/debian/
Ну а в каталоге rep/ у меня лежат каталоги репозиториев для разных дистрибутивов — rep/jaunty/, rep/unstable/ и т.д.
От этого и будем отталкиваться.

Итак, начнём с шапки нашего шелл-скрипта, который будет заниматься сборкой:
  1. #!/bin/bash
  2. BUILDERRORS="" # переменная, куда пишутся ошибки сборки
  3. MAJORVERSION="0.2a" # главная версия каждого пакета
  4. DISTRIBUTIONS="unstable testing stable jaunty" # названия репозиториев

Теперь давайте напишем две маленькие простые функции, одна из которых будет просто собирать пакет из текущего каталога для всех дистрибутивов и архитектур, а вторая будет обновлять версии пакетов во всех наших pbuilder-оболочках.
  1. build_it () {
  2. OLDDIR=`pwd`
  3. cd $1
  4. for i in $DISTRIBUTIONS; do
  5.         for j in i386 amd64; do
  6.                 DIST=$i ARCH=$j pdebuild -- --basetgz /var/cache/pbuilder/$i-$j-base.tgz
  7.                 if [[ $? != "0" ]]
  8.                 then
  9.                         BUILDERRORS="$BUILDERRORS
  10.        $1 - $i - $j"
  11.                 fi
  12.         done
  13. done
  14. cd $OLDDIR
  15. }
  16.  
  17. update_it () {
  18. echo "Updating pbuilder environments..."
  19. for i in $DISTRIBUTIONS; do
  20.         for j in i386 amd64; do
  21.                 DIST=$i ARCH=$j pbuilder --update --basetgz /var/cache/pbuilder/$i-$j-base.tgz
  22.                 if [[ $? != "0" ]]
  23.                 then
  24.                         BUILDERRORS="$BUILDERRORS
  25.        Updating - $i - $j"
  26.                 fi
  27.         done
  28. done
  29. }


Всё просто, не правда ли? Первая функция будет получать на вход имя каталога, заходить туда и пытаться собрать пакет под все архитектуры. Второй не надо вообще ничего, она просто обновляет все pbuilder-оболочки. Заметим, что обе команды в случае ошибок ничего не делают, просто дописывают сообщение в лог ошибок.
Теперь нам надо сделать универсальную функцию, которая будет собирать наши пакетики. Все пакеты отличаются друг от друга названием, версией и местом, откуда их брать. Это и будем использовать. Во-первых, в тех файлах debian/changelog, которые у меня хранятся в папке controls, я вместо версии везде прописал "--VERSION--". А во-вторых, написал вот такую замечательную маленькую функцию:
  1. make_package () {
  2.         _REV=$1
  3.         _URL=$2
  4.         _NAME=$3
  5.         _DIR=$4
  6.         _BREV=$5
  7.         DIRNAME=""
  8.         PACKAGENAME=""
  9.         if [[ $_URL != "" ]]
  10.         then
  11.                 rm -rf "$_DIR"
  12.                 export $_REV=`LANG=C svn export $_URL $_DIR | awk '$1=="Exported" && $2=="revision" {print $3}' | sed 's/.$//'`
  13.                 if [[ ${!_REV} = "" ]]
  14.                 then
  15.                         echo "Error! Can't get $_NAME revision!" && exit 1
  16.                 fi
  17.                 echo "Received $_NAME revision: ${!_REV}"
  18.         else
  19.                 export $_REV="1"
  20.         fi
  21.         export REVISION="${!_REV}"
  22.         DIRNAME="${_DIR}-${_BREV}.${!_REV}"
  23.         PACKAGENAME="${_DIR}_${_BREV}.${!_REV}"
  24.         echo "$_NAME directory is: $DIRNAME"
  25.         rm -rf "${PACKAGENAME}.orig.tar.gz" "$DIRNAME"
  26.         cp -r "$_DIR" "$DIRNAME"
  27.         tar czf "${PACKAGENAME}.orig.tar.gz" "$DIRNAME"
  28.         echo "$_NAME source archive is: ${PACKAGENAME}.orig.tar.gz"
  29.         cp -r "controls/${_DIR}/debian" "$DIRNAME/"
  30.         sed -i "s#--VERSION--#${_BREV}.${!_REV}-1#" "$DIRNAME/debian/changelog"
  31.         echo "Building $_NAME..."
  32.         build_it "$DIRNAME"
  33. }


Давайте поподробнее посмотрим, что делает эта функция. На вход мы передаем последовательно пять параметров:
  • имя переменной, куда положить номер скачанной ревизии;
  • адрес, откуда выкачивать исходники;
  • имя пакета, как его обзывать в лог-сообщениях;
  • имя debian-пакета;
  • «старший» номер версии

Функция универсальна — если передан URL, то из него выкачиваются исходники и автоматически создается архив .orig.tar.gz. Если не передан — то он создаётся из уже имеющегося каталога.
Теперь пакет можно собрать одной просто строчкой:
  1. make_package "QREV" "qutim.org/svn/qutim" "qutIM" "qutim" "$MAJORVERSION"

Для чего нужен самый первый передаваемый параметр QREV? При сборке пакета мы передаем qutIM «старшую» версию «0.2a» и получаем версию самого пакета, например, «0.2a.294-1». Если я собираю пакет протокола, то я хочу, чтобы в его версию входила и версия ядра:
  1. make_package "MRIM_REV" "qutim.org/svn/mrim" "MRIM" "qutim-protocol-mrim" "$MAJORVERSION.$QREV"

На выходе получим версию «0.2a.294.357-1». Не правда ли, удобно? Таким образом мы всегда знаем, какую версию исходников мы получили.
Если пакет у нас есть локально и я его просто пересобираю, без выкачивания из svn — не беда, просто передадим в качестве URL пустой параметр:
  1. make_package "FLOATIES_REV" "" "Floaties" "qutim-plugin-floaties" "$MAJORVERSION.$QREV"

Нам осталась самая малость — обновить пакеты в репозитории, выложить его с билд-машины на сервер и сообщить, при сборке каких пакетов у нас были ошибки:
  1. echo "Copying packages to local repository..."
  2. for j in $DISTRIBUTIONS; do
  3.         for i in `ls /var/cache/pbuilder/$j/result/*amd64.changes`; do
  4.                 reprepro -b rep/$j/ --ignore=wrongdistribution -C main include $j $i
  5.         done
  6.         for i in `ls /var/cache/pbuilder/$j/result/*_i386.deb`; do
  7.                 reprepro -b rep/$j/ --ignore=wrongdistribution -C main includedeb $j $i
  8.         done
  9. done
  10.  
  11. echo "Updating main repository..."
  12. scp -r rep/* remotehost:/path/to/repo/
  13.  
  14. echo "Done."
  15. if [ "$BUILDERRORS" ];
  16. then
  17.         echo "There were errors in: "
  18.         echo "$BUILDERRORS"
  19. fi

Заметьте, поскольку мы «прикрутили» к нашей консоли gpg-agent, нам больше не нужен ключ --ask-passphrase для reprepro.

Все эти замечательные скрипты не только позволяют мне теперь добавлять новые пакеты в репозиторий простой подготовкой каталога debian/ для пакета и одной строчкой в скрипте, но и совершенно не требуют моего внимания. Для сборки я теперь запускаю свой скрипт, и ухожу по своим делам. Вернувшись через несколько часов я ввожу один раз ключ gpg и один раз — пароль от удаленного сервера репозитория. Просто и удобно.

P.s. Информация для любителей экспериментировать: помимо удобного reprepro есть еще официальная утилита dak, которую используют для репозиториев как минимум в Debian. Я ей никогда не пользовался, поскольку для работы она использует PostgreSQL и неправоверный язык python (холиварить по этому поводу не буду, и не просите). Но желающие могут попробовать.

P.p.s. Для тех, кто захочет применять скрипты у себя, предлагаю полный используемый у меня скрипт. Редактируйте для себя на здоровье: pastebin.com/f3289286a
Теги:
Хабы:
Всего голосов 28: ↑24 и ↓4+20
Комментарии14

Публикации