Допустим, вы собираете для ваших любимых пользователей десять пакетов, да каждый под два дистрибутива (допустим, Debian unstable и Ubuntu jaunty), да еще и каждый под две архитектуры (amd64 и i386). Помните, мы с вами узнали про замечательный инструмент для создания репозиториев — reprepro. Так вот с тех пор он так и не научился включать в репозиторий пакеты пачками — только по одному. А это значит, что вам придётся вводить вашу gpg-подпись 10*2*2=40 раз. А как эффективно собирать эти 10 пакетов, особенно, если они обновляются ежедневно?
На самом деле, у меня ситуация была ещё хуже. В общей сложности при обновлении репозитория qutIM мне приходилось вводить свой отнюдь не короткий gpg-ключ аж 176 (сто семьдесят шесть) раз. Надо сказать, что к тому моменту, когда мне это окончательно надоело и перебороло мою лень, я выполнял эту процедуру всего за 3 минуты — по секунде на ввод. Но не будем вас истязать, сразу исправим ситуацию. Итак, устанавливаем пакет gnupg-agent и редактируем свой файл ~/.bashrc, добавляя в конце следующие строки:
- #
- # запоминалка паролей для GnuPG
- #
- gpg-agent --daemon --enable-ssh-support --write-env-file "${HOME}/.gpg-agent-info"
- if [ -f "${HOME}/.gpg-agent-info" ]; then
- . "${HOME}/.gpg-agent-info"
- export GPG_AGENT_INFO
- export SSH_AUTH_SOCK
- export SSH_AGENT_PID
- 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/ и т.д.
От этого и будем отталкиваться.
Итак, начнём с шапки нашего шелл-скрипта, который будет заниматься сборкой:
- #!/bin/bash
- BUILDERRORS="" # переменная, куда пишутся ошибки сборки
- MAJORVERSION="0.2a" # главная версия каждого пакета
- DISTRIBUTIONS="unstable testing stable jaunty" # названия репозиториев
Теперь давайте напишем две маленькие простые функции, одна из которых будет просто собирать пакет из текущего каталога для всех дистрибутивов и архитектур, а вторая будет обновлять версии пакетов во всех наших pbuilder-оболочках.
- build_it () {
- OLDDIR=`pwd`
- cd $1
- for i in $DISTRIBUTIONS; do
- for j in i386 amd64; do
- DIST=$i ARCH=$j pdebuild -- --basetgz /var/cache/pbuilder/$i-$j-base.tgz
- if [[ $? != "0" ]]
- then
- BUILDERRORS="$BUILDERRORS
- $1 - $i - $j"
- fi
- done
- done
- cd $OLDDIR
- }
-
- update_it () {
- echo "Updating pbuilder environments..."
- for i in $DISTRIBUTIONS; do
- for j in i386 amd64; do
- DIST=$i ARCH=$j pbuilder --update --basetgz /var/cache/pbuilder/$i-$j-base.tgz
- if [[ $? != "0" ]]
- then
- BUILDERRORS="$BUILDERRORS
- Updating - $i - $j"
- fi
- done
- done
- }
Всё просто, не правда ли? Первая функция будет получать на вход имя каталога, заходить туда и пытаться собрать пакет под все архитектуры. Второй не надо вообще ничего, она просто обновляет все pbuilder-оболочки. Заметим, что обе команды в случае ошибок ничего не делают, просто дописывают сообщение в лог ошибок.
Теперь нам надо сделать универсальную функцию, которая будет собирать наши пакетики. Все пакеты отличаются друг от друга названием, версией и местом, откуда их брать. Это и будем использовать. Во-первых, в тех файлах debian/changelog, которые у меня хранятся в папке controls, я вместо версии везде прописал "--VERSION--". А во-вторых, написал вот такую замечательную маленькую функцию:
- make_package () {
- _REV=$1
- _URL=$2
- _NAME=$3
- _DIR=$4
- _BREV=$5
- DIRNAME=""
- PACKAGENAME=""
- if [[ $_URL != "" ]]
- then
- rm -rf "$_DIR"
- export $_REV=`LANG=C svn export $_URL $_DIR | awk '$1=="Exported" && $2=="revision" {print $3}' | sed 's/.$//'`
- if [[ ${!_REV} = "" ]]
- then
- echo "Error! Can't get $_NAME revision!" && exit 1
- fi
- echo "Received $_NAME revision: ${!_REV}"
- else
- export $_REV="1"
- fi
- export REVISION="${!_REV}"
- DIRNAME="${_DIR}-${_BREV}.${!_REV}"
- PACKAGENAME="${_DIR}_${_BREV}.${!_REV}"
- echo "$_NAME directory is: $DIRNAME"
- rm -rf "${PACKAGENAME}.orig.tar.gz" "$DIRNAME"
- cp -r "$_DIR" "$DIRNAME"
- tar czf "${PACKAGENAME}.orig.tar.gz" "$DIRNAME"
- echo "$_NAME source archive is: ${PACKAGENAME}.orig.tar.gz"
- cp -r "controls/${_DIR}/debian" "$DIRNAME/"
- sed -i "s#--VERSION--#${_BREV}.${!_REV}-1#" "$DIRNAME/debian/changelog"
- echo "Building $_NAME..."
- build_it "$DIRNAME"
- }
Давайте поподробнее посмотрим, что делает эта функция. На вход мы передаем последовательно пять параметров:
- имя переменной, куда положить номер скачанной ревизии;
- адрес, откуда выкачивать исходники;
- имя пакета, как его обзывать в лог-сообщениях;
- имя debian-пакета;
- «старший» номер версии
Функция универсальна — если передан URL, то из него выкачиваются исходники и автоматически создается архив .orig.tar.gz. Если не передан — то он создаётся из уже имеющегося каталога.
Теперь пакет можно собрать одной просто строчкой:
- make_package "QREV" "qutim.org/svn/qutim" "qutIM" "qutim" "$MAJORVERSION"
Для чего нужен самый первый передаваемый параметр QREV? При сборке пакета мы передаем qutIM «старшую» версию «0.2a» и получаем версию самого пакета, например, «0.2a.294-1». Если я собираю пакет протокола, то я хочу, чтобы в его версию входила и версия ядра:
- make_package "MRIM_REV" "qutim.org/svn/mrim" "MRIM" "qutim-protocol-mrim" "$MAJORVERSION.$QREV"
На выходе получим версию «0.2a.294.357-1». Не правда ли, удобно? Таким образом мы всегда знаем, какую версию исходников мы получили.
Если пакет у нас есть локально и я его просто пересобираю, без выкачивания из svn — не беда, просто передадим в качестве URL пустой параметр:
- make_package "FLOATIES_REV" "" "Floaties" "qutim-plugin-floaties" "$MAJORVERSION.$QREV"
Нам осталась самая малость — обновить пакеты в репозитории, выложить его с билд-машины на сервер и сообщить, при сборке каких пакетов у нас были ошибки:
- echo "Copying packages to local repository..."
- for j in $DISTRIBUTIONS; do
- for i in `ls /var/cache/pbuilder/$j/result/*amd64.changes`; do
- reprepro -b rep/$j/ --ignore=wrongdistribution -C main include $j $i
- done
- for i in `ls /var/cache/pbuilder/$j/result/*_i386.deb`; do
- reprepro -b rep/$j/ --ignore=wrongdistribution -C main includedeb $j $i
- done
- done
-
- echo "Updating main repository..."
- scp -r rep/* remotehost:/path/to/repo/
-
- echo "Done."
- if [ "$BUILDERRORS" ];
- then
- echo "There were errors in: "
- echo "$BUILDERRORS"
- fi
Заметьте, поскольку мы «прикрутили» к нашей консоли gpg-agent, нам больше не нужен ключ --ask-passphrase для reprepro.
Все эти замечательные скрипты не только позволяют мне теперь добавлять новые пакеты в репозиторий простой подготовкой каталога debian/ для пакета и одной строчкой в скрипте, но и совершенно не требуют моего внимания. Для сборки я теперь запускаю свой скрипт, и ухожу по своим делам. Вернувшись через несколько часов я ввожу один раз ключ gpg и один раз — пароль от удаленного сервера репозитория. Просто и удобно.
P.s. Информация для любителей экспериментировать: помимо удобного reprepro есть еще официальная утилита dak, которую используют для репозиториев как минимум в Debian. Я ей никогда не пользовался, поскольку для работы она использует PostgreSQL и неправоверный язык python (холиварить по этому поводу не буду, и не просите). Но желающие могут попробовать.
P.p.s. Для тех, кто захочет применять скрипты у себя, предлагаю полный используемый у меня скрипт. Редактируйте для себя на здоровье: pastebin.com/f3289286a

