Debian.pro/

Про Debian


Собираем свой debian-пакет с файлами.

Приветствую всех. Когда-то давно я писал мануал про сборку пакета. Описанный там способ страшноват, хоть и работает.
Сейчас у меня появилось время вернуться к этой теме и написать, как более или менее правильно собирать свои 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

В общем-то всё.

В следующих статьях я напишу как подписывать наши пакеты, и как сделать репозиторий с нашими собственными пакетами — тогда их уже можно будет действительно использовать.


Комментарии (12):

  1. Сергей Орлов :

    В rules, видимо должно быть
    rm -f debian/example-package/*
    вместо
    rm -f debian/example-package/dummy

  2. Да, спасибо. Поправил.

  3. А мы тут пилим для себя опенсурсную приблуду-репозиторий (ONE2DEB),
    пример можно глянуть http://deb.one2team.ru (там же есть и пакет).

    Надо будет добавить ваш сценарий для сборки, на удивление простая схема.

  4. лучше добавьте что-то в духе
    cp -r sources/* debian/example-package
    =)

    А пользователям расскажите, что файлы нужно складывать в sources/ так, как будто это корень.

  5. Jeka :

    Большое спасибо за статью, давно хочу автоматизировать процесс конфигурирования серверов. Но использование исключительно puppet для подобного кажется слишком трудоемкой задачей. Думаю использование пакетов упростит задачу.

  6. Puppet + пакеты вас спасут, да.
    Вместо паппета можно ansible попробовать.
    я на днях опубликую про репозитории свои — там статью сверстать осталось и проверить.

  7. А почему принципиально puppet, salt не пробовали?
    Мы вот собираемся перелезать с puppet.

  8. ну почему ж, puppet я использую (и ansible местами).
    Только вот пакеты дают большую консистентность и восстанавливаемость, если ими правильно пользоваться.

  9. Amber :

    1) В debian/rules для корректного повторного запуска debuild нужно вбить стандартное
    if [ -d debian/ex-pkg/ ]; then rm -rf debian/ex-pkg/; fi
    mkdir debian/ex-pkg/
    (вместо touch la-la-la -> rm -f)
    Иначе повторный запуск заканчивается на сообщении от mkdir «директория уже создана».

    2) При создании пакета валимся с воплем о несуществующем ключе (Now signing changes and any dsc files… secret key not available debsign: gpg error occurred! Aborting…). Либо надо детально объяснять, как правильно создавать подпись и где брать ключ, либо рассказать про опции debuild’a -us и -uc, которые
    указывают не подписывать исходный код и файлы изменений.

    3) А теперь про эти самые ‘sources’. Если собирать как указано в статье с опцией ‘-b’, то debuild сообщит, что пакет создан и исходники в него не включены. Пакет получается пустым. Классно. То есть мы под «исходниками» понимаем исходные бинарные/текстовые файлы для пакета, а он их считает реально исходниками для сборки компилятором. При попытке собрать без флага ‘-b’ dpkg-source закономерно поднимает вопли о каких-то левых файлах вместо сорцов на Си (dpkg-source: error: unwanted binary file),
    а в конце появляется рекомендация добавить все бинарники в директорию
    debian/source/include-binaries (что логично и требует пояснений от автора статьи)

  10. > нужно вбить стандартное
    Стандартное это всё же dh_install. Но он плохо подходит для демонстрации того, что на самом деле происходит при сборке пакета.
    И мой способ, и ваш, одинаково плохи с точки зрения debian policy =)

    > Иначе повторный запуск заканчивается на сообщении от mkdir «директория уже создана».
    Опцию -p потеряли у mkdir.

    > Либо надо детально объяснять, как правильно создавать подпись и где брать ключ
    https://debian.pro/1424

    > либо рассказать про опции debuild’a -us и -uc, которые указывают не подписывать исходный код и файлы изменений.
    Опция называются —no-lintian и в статье про неё написано.

    > что логично и требует пояснений от автора статьи
    да нет, не логично =)
    Я сотни две пакетов собрал примерно по этому мануалу (более того, они и без -b всегда собирались, но .tar.gz обычно никому не нужны, когда есть git/svn и внятная ссылка на ревизию, из которой пакет собран).

    Показывайте файлы из debian/ и лог сборки. Где-то вы ошиблись.

  11. Андрей :

    Скажите пожалуйста а куда нужно класть файл исходник, например файл ICQ скачанный с офф сайта?

  12. Эта статья про деплой готовых статических файлов пакетами, а не про сборку пакета из исходников программы.

Написать комментарий