Приветствую всех. Когда-то давно я писал мануал
про сборку пакета. Описанный там способ страшноват, хоть и работает.
Сейчас у меня появилось время вернуться к этой теме и написать, как более или менее правильно собирать свои deb-пакеты — с подписями пакетов, своими репозиториями и всем подобным. Заодно я затрону тему того, что такое deb-пакеты изнутри и расскажу про механику использования dpkg/apt.
Первая статья, само собой — сборка своего deb-пакета из ничего. Такие пакеты будут очень полезны для того, чтобы деплоить свою конфигурацию — вы из коробки получаете контроль версий установленной конфигурации на машине. Штатными средствами системы можно будет быстро откатиться на старую версию пакета. Легче обновляться, легче деплоить на несколько машин. И так далее. Бла-бла-бла.
На одном из своих выступлений я рассказывал о том, что именно deb-пакетами деплоится конфигурация машин и самописный софт (в том числе и сервисы) в Яндексе. Конечно, не во всех отделах, но все стараются двигаться к этому.
Несмотря на то, что давно уже создана куча инструментов декларативной конфигурации серверов (chef, puppet, Salt, ansible) — пока ещё ничего лучше родной пакетной системы дистрибутива не сделали. Конечно, у пакетов есть некоторые ограничения, поэтому есть смысл использовать пакетирование совместно с системами конфигурации, но большую часть конфигурации имеет смысл держать именно в пакетах (а каким-нибудь паппетом смотреть за актуальностью/установленностью нужных пакетов на нужных хостах). Вооот.
Сразу оговорюсь, что я не ставлю прямо сейчас цели описать то, как собрать пакет в соответствии с debian policy (они развесистые и соблюдать их внутри своей инфраструктуры тяжело). Прямо сейчас я описываю понятный способ сборки. Примерно поэтому я не буду использовать dh_install, например (но по debian policy мне бы за это надо голову оторвать).
Ну поехали собирать.
Мы соберем пакет, который кладет наш ключик руту, кладет пачку конфигов для nginx’a в conf.d, sites-available, добавляет пару задач в крон, притаскивает, собственно, скрипты для этих задач, тащит за собой нужный софт.
Для начала установим все нужные инструменты для сборки пакета описанным ниже способом:
root@server:~# apt-get -qq update; apt-get install dh-make devscripts
Создаём каталог, из которого мы будем собирать пакет. Относительно этого каталога мы дальше будем работать (и относительно него будем собирать пакет). Каталог нужно назвать в формате «packagename-version»
В качестве названия пакета я буду использовать «example-package»:
user@server:~$ mkdir example-package-0.1; cd example-package-0.1
Генерируем пустой «скелет» для сборки:
user@server:~$ dh_make --copyright bsd --createorig
В --copyright
мы указываем «лицензию» для нашего пакета (кому какая разница=)? ), --createorig
— создаёт tar-ник с исходниками каталогом выше. Можно использовать опцию --native
, но он не работает в текущей версии dh_make в Wheezy, так что нужно быть к этому готовым.
Удаляем лишние файлы, которые нагенерил нам dh_make:
user@server:~$ rm -f debian/*.EX; rm -f debian/*.ex; rm -f debian/README*
Если вся эта эпопея происходит внутри каталога системы контроля версий (git, svn?), то самое время убрать номер версии из названия каталога:
user@server:~$ cd ../ ; mv example-package-0.1/ example-package; cd example-package
Иначе при сборке пакета каждый раз будет меняться название каталога (в итоге у вас будет куча каталогов внутри VCS — переименование будет делаться через обычный mv, а не через git mv, например). Можно всё это контролировать руками, но лень.
Теперь быстренько нагадим файлами, которые будут «исходниками» пакета. Здесь всё очень условно, воспринимайте это как очень простой пример. Вся основная магия будет происходить в файле debian/rules, поэтому неважно как сейчас вы расположите файлы (главное, чтобы они были внутри каталога, из которого будет собираться пакет).
Создадим каталог для исходников, чтобы отделить мух от котлет:
user@server:~$ mkdir sources
В итоге у нас должно быть так:
user@server:~$ ls
debian sources
Создадим файлы и каталоги внутри sources:
user@server:~$ mkdir -p sources/nginx/sites-available/
user@server:~$ mkdir -p sources/nginx/conf.d/
user@server:~$ mkdir -p sources/bin/
user@server:~$ mkdir -p sources/cron/
user@server:~$ touch sources/nginx/sites-available/somesite.conf
user@server:~$ touch sources/nginx/conf.d/someconfig.conf
user@server:~$ touch sources/bin/script.sh
user@server:~$ touch sources/bin/script2.py
user@server:~$ touch sources/authorized_keys
user@server:~$ touch sources/cron/example-package
Ещё раз — всё это условности. Располагайте файлы так, как вам в дальнейшем будет удобно. Само собой, в эти файлы нужно что-то записать, если хотите притащить не пустые файлы на машину из пакета ;)
Теперь напишем файл debian/rules.
Основная задача этого файла с нашей точки зрения — подготовить каталог debian/packagename (в нашем случае это будет debian/example-package), из которого потом будет сгенерирован архив внутри пакета, который будет распакован поверх корневой файловой системы там, куда этот пакет мы будем ставить.
То есть, в нашем случае, файл, который мы положим в debian/example-package/root/.ssh/authorized_keys после установки собранного пакета станет файлом /root/.ssh/authorized_keys
Это, в целом, самый-самый главный момент при сборке пакета, который нужно понимать. Остальное за нас сделают разные debhelper’ы (и мы можем не понимать, что они там делают, но им-то на это наплевать).
В нашем примере я бы использовал такой rules (напомню, что это нифига не по debian policy, зато понятно и работает):
#!/usr/bin/make -f
# строчка выше - это не комментарий, не протеряйте её.
# отказываемся от использования dh_install, который в данном примере больше будет мешать, чем помогать принцип сборки пакетов:
override_dh_install:
# дальше мы создаём дерево каталогов внутри сборочного дерева, из которого соберется архив с файлами:
# для начала убедимся, что там не осталось хлама от предыдущих сборок (получим пустой каталог debian/example-package):
mkdir -p debian/example-package/
# если не создавать пустой файл - то команда rm упадёт со словами "нечего удалять" и сборка пакета не случится
touch debian/example-package/dummy
rm -f debian/example-package/*
# теперь создаём нужные каталоги:
mkdir -p debian/example-package/root/.ssh
mkdir -p debian/example-package/usr/bin/
mkdir -p debian/example-package/etc/cron.d/
mkdir -p debian/example-package/etc/nginx/conf.d/
mkdir -p debian/example-package/etc/nginx/sites-enabled/
mkdir -p debian/example-package/etc/nginx/sites-available/
# копируем наши файлики:
cp sources/authorized_keys debian/example-package/root/.ssh/authorized_keys
cp -r sources/nginx/* debian/example-package/etc/nginx/
cp -r sources/cron/* debian/example-package/etc/cron.d/
cp sources/bin/* debian/example-package/usr/bin/
# выставим флаг +x на скрипты внутри пакета, чтобы они могли запускаться:
chmod +x debian/example-package/usr/bin/*
# запускаем все остальные deb-helper'ы "по умолчанию" (про которые мы как раз ничего не знаем):
%:
dh $@
Если вы (зачем-то) скопировали текст-примера debian/rules, то придется ввести эту команду:
user@server:~$ sed -i 's/ /\t/g' debian/rules
В rules-файле (он же - и Makefile) обязательно должны быть символы табуляции, а не 4 пробела. Вообще же debian/rules вам придется писать самим каждый раз.
Следующий на очереди - файл debian/control. В этом файле описывается мета-информация о наших исходниках, мета-информация о пакетах, которые мы собираем, то, как наш пакет будет взаимодействовать с другими пакетами (с какими конфликтует, от каких зависит).
В нашем примере подойдет такой control:
# название нашего исходника и файла changes - в данном случае, оно будет совпадать с названием пакета - он у нас один.
Source: example-package
# Section и Priority - в целом, нам без разницы что там - оставляем по умолчанию или пишем misc+extra
Section: misc
Priority: extra
# Мэйнтейнер пакета (в официальном дебиане это человек, который будет отвечать за его целостность и совместимость с другими пакетами. В нашем случае имеет смысл написать сюда фамилию, имя и почту (свои) - в следующих статьях эти данные будут использоваться для подписывания пакетов.
Maintainer: Zhivotnev Vladislav <root@vlad.pro>
# Здесь описываются зависимости, которые нужны для сборки пакетов. Это не зависимости будущего пакета. Например, для сборки пакета может требоваться bash, но внутри пакета может не быть файлов, которым нужен bash. Аналогично с devscripts - он используется только для сборки пакета, а для установки пакета они не нужны.
Build-Depends: debhelper (>= 8.0.0), bash, devscripts
# версия стандартов. Если пакет собираем на старом дистрибутиве и будем ставить только на такой же или новее - не паримся и оставляем по-умолчанию.
Standards-Version: 3.9.3
# Домашняя страница с информацией о содержимом пакета (ссылка на документацию обычно):
Homepage: http://somesite.tld/somepage
# ссылка на git-репу с исходниками пакета. Есть аналогичные поля Vcs-Svn, Vcs-Cvs, емнип. Необязательное поле.
#Vcs-Git: git://git.yourgit.tld/git/somerepo.git
# ссылка на просмотр репозитория из браузера (не на сам репозиторий по http). Например, ссылка на страницу репозитория на гитхабе. Не обязательное поле.
#Vcs-Browser: https://github.com/youraccount/somerepo/
# Дальше мы описываем пакеты, которые будут собираться из нашего исходника. Из одного исходника можно собирать несколько пакетов разом, но в нашем примере будет только один.
# Package - название самого deb-пакета:
Package: example-package
# Архитектура, под которую мы собираем пакет. Например, amd64, i386. Существует так же 2 "псевдо-архитектуры" - all и any.
# Архитектура any означает то, что собранный пакет может быть установлен на ту же архитектуру, под которой пакет собирался. То есть, если собрать пакет в amd64-системе, то и поставить его потом можно будет только на amd64.
# Архитектура all обозначает то, что пакет может быть установлен на дистрибутивы любой архитектуры независимо от того, где он собирался. Именно она и нужна нам в данном случае.
Architecture: all
# пакеты необходимые для корректной установки и конфигурирования нашего пакета. То есть без этих пакетов наш пакет не сможет установиться вообще. Сюда нужно вписывать те пакеты, которые, например, используются в preinstall файле. У меня в postinstall будет рестартиться nginx, поэтому я впишу его сюда (иначе postinstall-скрипт может упасть с ошибкой).
Pre-depends: nginx
# пакеты, нужные для работы нашего пакета.
# Разница с Pre-depends в том, что dpkg не будет гарантировать то, что эти пакеты будут сконфигурированы к моменту запуска установки нашего пакета (и в этом случае могут не сработать preinstall или postinstall скрипты). Нам нужны bassh и python для запуска наших скриптов. Ещё нужны openssh-server, cron и nginx (иначе зачем мы тащим конфигурацию для них?).
Depends: nginx, cron, openssh-server, python, bash
# Description - описание пакета.
# первая строчка (которая сразу после директивы Description) будет отображаться, например, в dpkg -l.
# следующий строки (обязательно должны начинаться с пробела или таба) - полное описание пакета, будет отображаться, например, в apt-cache show package.
Description: short descrition for dpkg -l
First line of full description.
Second line of full description.
Etc lines of full description
Это минимальное содержимое файла debian/control. Его хватит для сборки нашего пакета.
Сейчас самое время обратить на то, что в нашем пакете мы кладем какие-то файлы в /etc/nginx/sites-available. Само собой, в этом случае, nginx не будет использовать наши файлы. Нам нужно создать симлинк файла из /etc/nginx/sites-available в /etc/nginx/sites-enabled. Для этого есть 2 способа.
Первый - варварский, но понятный - использовать уже заведенный нами debian/rules и вписать в нём в секцию override_dh_install команду создания симлинка:
ln -s debian/example-package/etc/nginx/sites-available/somesite.conf debian/example-package/etc/nginx/sites-enabled/somesite.conf
Либо использовать файл debian/links и вписать в него такое:
etc/nginx/sites-available/somesite.conf etc/nginx/sites-enabled/somesite.conf
Этот файл будет прочитан deb-helper'ом dh_link, который и создаст симлинку внутри каталога, из которого будет собран архив. Это как раз про строчку "запустим все debhelperы по умолчанию".
Остаётся последний штрих - postinstall (скрипт, который будет запускаться после установки нашего пакета).
В примере я буду рестартить nginx, чтобы применилась конфигурация nginx'a из пакета.
Создадим файл и выставим на него флаг +x:
user@server:~$ touch debian/postinst ; chmod +x debian/postinst
Внутрь запишем примерно такое содержимое:
#!/bin/bash
# сначала проверяем корректность конфигурации, если всё хорошо - рестартим nginx. Если нет - пишем соответствующий текст.
/etc/init.d/nginx configtest && /etc/init.d/nginx restart || echo "There are incorrect configuration of nginx, i will not restart it right now".
# учтите, что postinstall должен возвращать код возврата 0, иначе пакет не получится поставить. Именно для этого нам и нужен здесь echo.
# Если вам удобнее, чтобы пакет не ставился, если конфиг неправилен - то удалите про echo.
Теперь всё готово к сборке пакета. Сообщим собиралке пакетов, что у нас новая версия (и заодно напишем правильно поля в changelog'e):
user@server:~$ dch --vendor=debian --distribution=unstable -i
У нас откроется файл changelog в редакторе. В него будут добавлены примерно такие строки:
example-package (0.1-1.1) unstable; urgency=low
* Non-maintainer upload.
*
-- <inkvizitor68sl@vds113> Sun, 27 Oct 2013 17:37:26 +0400
Сотрем всё это и напишем то, что нам нужно:
example-package (0.2) unstable; urgency=low
* changes, we are made in this version
* another changes in this version
-- inkvizitor68sl <root@vlad.pro> Sun, 27 Oct 2013 17:37:26 +0400
В скобочках - версия. Последняя строка (начинающаяся с " --") - кто и когда собирал пакет. "Кто" должно совпадать с нашей подписью, но сейчас её у нас нет, поэтому можете написать туда что угодно.
Собираем пакет (находясь в каталоге example-package или example-package-0.2, если не переименовывали):
user@server:~$ debuild --no-lintian -b
После сборки у нас должно получиться несколько файлов каталогом выше:
user@server:~$ ls ../example-package_0.2_*
../example-package_0.2_amd64.build ../example-package_0.2_amd64.changes ../example-package_0.2_amd64.deb
.build - лог сборки
.changes - файл с метаинформацией о пакете (нужен для того, чтобы потом залить пакет в репозиторий).
.deb - сам deb-пакет.
Проверим, что в пакет попали все нужные файлы нужного размера с нужными правами:
user@server:~$ dpkg -c ../example-package_0.2_amd64.deb
drwxr-xr-x root/root 0 2013-10-27 17:54 ./
drwxr-xr-x root/root 0 2013-10-27 17:54 ./root/
drwxr-xr-x root/root 0 2013-10-27 17:54 ./root/.ssh/
-rw-r--r-- root/root 4 2013-10-27 17:54 ./root/.ssh/authorized_keys
...
Пока что мы можем поставить deb-пакет ручками. Для этого копируем его на нужную машину, ставим руками все зависимости и запускаем установку (от рута или с sudo):
root@server:~# dpkg -i example-package_0.2_amd64.deb
В общем-то всё.
В следующих статьях я напишу как подписывать наши пакеты, и как сделать репозиторий с нашими собственными пакетами - тогда их уже можно будет действительно использовать.