Manpages

НАЗВАНИЕ

debconf - руководство разработчика

ОПИСАНИЕ

Данное руководство помогает при разработке пакетов, использующих debconf.

Предполагается, что вы знакомы с debconf как пользователь, а также знакомы с основами создания пакетов debian.

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

СЦЕНАРИЙ НАСТРОЙКИ

Debconf добавляет дополнительный сценарий сопровождения, сценарий config, который служит для настройки сопровождающих сценариев, имеющихся в пакетах debian (postinst, preinst, postrm и prerm). Сценарий config отвечает за выдачу всех вопросов настройки пакета.

Замечание: часто смущает тот факт, что dpkg ссылается на запущенный сценарий пакета postinst как на «настроечный», хотя пакет, который использует debconf часто полностью настроен своим сценарием config ещё до того как postinst даже будет запущен. Такие дела.

Как и postinst, сценарию config передаётся два параметра при запуске. В первом задаётся действие, которое нужно выполнить, а во втором — версия установленного в данный момент пакета. Как и в postinst, вы можете применить dpkg --compare-versions к $2, чтобы выполнить данную команду только при обновлении с определённой версии пакета и тому подобных операций.

Сценарий config может быть запущен в трёх случаях:

1

Во время предварительной настройки пакета из dpkg-preconfigure, который запускает сценарий config с параметрами «configure» и номером установленной версии.

2

При запуске postinst из пакета, debconf также попытается запустить сценарий config и передать те же параметры, что и в случае с предварительной настройкой пакета. Это необходимо, так как пакет может не быть предварительно настроен, и сценарию config даётся ещё одна попытка отработать. Подробней смотрите в разделе ХАКИ.

3

При повторной настройке пакета с помощью dpkg-reconfigure, где сценарий config запускается с параметрами «reconfigure» и номером установленной версии.

Заметим, что так как типичная установка или обновление с помощью apt попадают под случаи 1 и 2, сценарий config обычно запускается дважды. Он ничего не должен делать во второй раз (отвечать на вопросы второй раз подряд раздражает), и определённо должен быть идемпотентен. К счастью, debconf избегает повторения вопросов по умолчанию, поэтому это легко достигается.

Заметим, что сценарий config запускается перед распаковкой пакета. В нём должны использоваться только команды из пакетов первой необходимости. Единственная зависимость пакета, которая будет удовлетворена до запуска сценария config — это сам debconf (возможно указать версию).

Сценарию config ничего не нужно изменять в файловой системе. Он должен просто проверить состояние системы и задать вопросы, а debconf сохранит ответы, которые требуют действий позже из сценария postinst. И наоборот, сценарий postinst не должен использовать debconf для задания вопросов, а вместо этого должен полагаться на ответы, полученные при работе сценария config.

ФАЙЛ TEMPLATES

Пакету, использующему debconf, вероятно требуется задать некоторые вопросы. Эти вопросы хранятся в форме шаблона в файле templates.

Как и сценарий config, файл templates находится в секции control.tar.gz deb файла. Его формат похож на формат debian файла control; секции строк, разделённые символами новой строки, каждая строка задана в RFC822-подобной форме:

Template: foo/bar
Type: string
Default: foo
Description: Это пример строки вопроса.
Это её дополнительное описание.
.
Заметим что:
- как и в описании пакета debian точка
в отдельной строке начинает новый параграф.
- для большого текста выполняется перенос на новую строку, но текст
с двойным отступом выводится как есть,
вы можете использовать это для показа списков
как сделано здесь. Осторожно, так как
перенос не выполняется, это очень плохо сказывается на слишком
длинных строках. Используйте это для коротких элементов
(поэтому это плохой пример).

Template: foo/baz
Type: boolean
Description: Вроде всё просто?
Это другой вопрос, логического типа.

Несколько рабочих примеров для файлов templates можно найти в /var/lib/dpkg/info/debconf.templates и других файлах .templates в этом каталоге.

Давайте рассмотрим каждое из полей по очереди..
Template

Название шаблона в поле «Template» обычно начинается с названия пакета. Далее можно использовать что угодно; вы можете использовать простую схему как в предыдущем примере или настроить «подкаталоги», содержащие похожие вопросы.

Type

Тип шаблона, который определяет какого вида будет показан элемент. Поддерживаются:

string

Поле ввода произвольных данных, в которое пользователь может ввести любую строку.

password

Предлагает пользователю ввести пароль. Используйте с осторожностью; этот пароль будет сохранён в базу данных debconf. Вероятно, вы должны удалить это значение из базы как можно быстрее.

boolean

Выбор true/false.

select

Выбор одного из указанных значений. Значения должны быть заданы в поле «Choices». Разделителями значений могут быть запятые и пробелы:

Choices: да, нет, может_быть

multiselect

Как и в типе данных select, выбор из списка, кроме того, что пользователь может выбрать сразу несколько значений из списка (или ни одного из них).

note

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

error

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

title

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

text

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

Default

Поле «Default» указывает debconf, какое должно быть значение по умолчанию. Для multiselect это может быть список значений, разделённых запятыми или пробелами, как в поле «Choices». Для select — это одно значение из списка. Для boolean — это «true» или «false», хотя может использоваться любая строка, и она игнорируется для паролей.

Не подумайте, что поле значения по умолчанию содержит «ответ» на вопрос, или что он может быть использован для замены ответа на вопрос. Это не так, и не может быть так, он только предоставляет ответ по умолчанию при первом показе вопроса. Чтобы изменить значение по умолчанию на лету, используйте команду SET.

Description

Поле «Description» подобно полю описания в пакете Debian, имеет две части: короткое описание и расширенное описание. Заметим, что некоторые интерфейсы к debconf не отображают длинное описание, или могут показать его, только если пользователь попросит показать помощь. Поэтому короткое описание должно отображать всю суть вопроса.

Если вы не можете придумать длинного описания, то сначала подумайте ещё немного. Напишите в debian-devel. Попросите помочь. Возьмите урок по написанию! Длинное описание очень важно. Если после всего этого ничего не приходит в голову, оставьте поле пустым. Нет смысла дублировать то, что уже написано в коротком описании.

Текст в расширенном описании будет переноситься по словам, если он не начинается с дополнительного пробельного символа (после первого обязательного пробела). Вы можете разбить его на параграфы поместив « .» в пустой строке между параграфами.

ВОПРОСЫ

Вопрос — это проиллюстрированный шаблон. Прося debconf показать вопрос, таким образом ваш сценарий config может взаимодействовать с пользователем. Когда debconf загружает файл templates (это происходит при запуске сценария config или postinst), он автоматически показывает вопрос каждого шаблона. На самом деле можно показать несколько независимых вопросов из одного шаблона (с помощью команды REGISTER), но это требуется редко. Шаблоны — это статические данные из файла templates, а вопросы используются для хранения динамических данных, таких как имеющийся ответ на вопрос, видел ли пользователь уже этот вопрос и тому подобное. Помните об этом различии между шаблоном и вопросом, но не стоит сильно забивать этим голову.

ОБЩИЕ ШАБЛОНЫ

Фактически, есть возможность иметь один шаблон и вопрос сразу для нескольких пакетов. Все пакеты могут предоставлять идентичную копию шаблона в своих файлах templates. Это может быть полезно, если набору пакетов требуется задать один и тот же вопрос, а вы хотите побеспокоить пользователя только один раз. Общие шаблоны обычно помещаются в псевдокаталог shared/ в пространстве имён шаблонов debconf.

ПРОТОКОЛ DEBCONF

Сценарии config взаимодействуют с debconf по протоколу debconf. Это простой однострочный протокол, похожий на основные протоколы интернета, типа SMTP. Сценарий config посылает debconf команду, записывая эту команду в стандартный вывод. Затем, он может прочитать ответ debconf со стандартного ввода.

Ответы debconf можно разделить на две части: числовой код результата (первое слово в ответе) и необязательный расширенный код результата (оставшаяся часть ответа). В числовом коде значение 0 соответствует успешному выполнению, а другие числа указывают на различного вида неудачи. Подробней смотрите таблицу в политике Debian, раздел спецификации debconf.

Расширенный код результата имеет любой вид и никак не стандартизован, поэтому нужно игнорировать его и не пытаться использовать в своей программе для выяснения что делает debconf. Исключение составляют команды типа GET, которая создаёт значение для возврата в расширенном коде результата.

Обычно вы будете использовать библиотеку, которая работает на низком уровне для установления связи с debconf и взаимодействия с ним.

А теперь обсудим команды протокола. Это не полное описание, за ним обращайтесь к документу о политике Debian, раздел спецификации debconf.
VERSION номер

Обычно не нужно использовать эту команду. Она позволяет договориться с debconf о том, какая версия протокола должна быть использована. Текущая версия протокола — 2.0, а версии ветки 2.x обратно совместимы. Вы можете указать номер версии протокола и debconf вернёт версию протокола в расширенном коде результата. Если указанная вами версия слишком стара, debconf вернёт числовой код 30.

CAPB возможности

Обычно не нужно использовать эту команду. Она позволяет обменяться с debconf списком поддерживаемых возможностей (разделяются пробелами). Будут использованы возможности, которые поддерживаются вами и debconf, а debconf вернёт список всех поддерживаемых им возможностей.

Если среди ваших возможностей будет «escape», то debconf будет ожидать, что в посылаемых вами командах символы обратный слеш и новой строки экранированы (\\ и \n соответственно), а в своих ответах будет также это делать. Это может быть использовано, например, для подстановки многострочных строк в шаблонах, или для получения многострочных расширенных описаний, использующихся в METAGET. В этом режиме вы должны сами экранировать входной текст (для этого вы можете использовать команду debconf-escape(1), если хотите), а библиотеки confmodule будут снимать экранирования в ответах за вас.

SETTITLE вопрос

Устанавливает заголовок, который debconf показывает пользователю, используя короткое описание шаблона указанного вопроса. Шаблон должен быть типа title. Обычно редко используется, так как debconf может автоматически генерировать заголовок на основе имени пакета.

Установка заголовка из шаблона означает, что они хранятся в том же месте что и все вопросы debconf, и их можно переводить на другие языки.

TITLE строка

Устанавливает заголовок, который debconf показывает пользователю, используя заданную строку. Использование команды SETTITLE предпочтительно, так как позволяет переводить заголовок на другие языки.

INPUT приоритет вопрос

Просит debconf подготовить к показу вопрос пользователю. Вопрос не показывается до тех пор пока не будет дана команда GO; это позволяет последовательно задать несколько команд INPUT для накопления набора вопросов, которые могут быть заданы все разом на одном экране.

Поле приоритета говорит debconf насколько важен вопрос, который будет показан пользователю. Значения приоритета:

low

Очень простые элементы со значениями по умолчанию, которые работают в подавляющем большинстве случаев; только параноики увидят их.

medium

Обычные элементы с корректными значениями по умолчанию.

high

Элементы, у которых нет приемлемых значений по умолчанию.

critical

Элементы, которые вероятно сломают систему, если не будет вмешательства пользователя.

Debconf сам решает нужно ли вообще показывать вопрос, основываясь на этом приоритете и том, видел ли уже пользователь этот вопрос и какой интерфейс используется для показа. Если вопрос не будет показан, debconf возвращает код 30.

GO

Указывает debconf показать накопленные вопросы (переданные ранее командами INPUT) пользователю.

Если поддерживается возможность backup и пользователь потребовал вернуться на шаг назад, то debconf возвратит код 30.

CLEAR

Очистить накопленные вопросы (из команд INPUT) и не показывать их.

BEGINBLOCK
ENDBLOCK

Некоторые интерфейсы debconf могут показывать пользователю сразу несколько вопросов одновременно. Может быть в будущем интерфейсы даже смогут группировать такие вопросы по блокам на экране. BEGINBLOCK и ENDBLOCK могут указываться вокруг набора команд INPUT, чтобы обозначить блоки вопросов (и блоки могут быть вложенными). Так как пока ни один интерфейс к debconf не поддерживает их, в данный момент эти команды игнорируются.

STOP

Эта команда указывает debconf на завершения взаимодействия с ним. Часто debconf может сам обнаружить завершение вашей программы и эта команда не обязательна.

GET вопрос

После отработки INPUT и GO для показа вопроса, вы можете использовать эту команду для получения значения введённого пользователем. Значение возвращается в виде расширенного кода результата.

SET вопрос ответ

Задаёт ответ на вопрос; это можно использовать для замены ответа по умолчанию чем-то, что ваша программа вычислила во время работы.

RESET вопрос

Сбрасывает ответ на вопрос в значение по умолчанию (как указано в поле «Default» в шаблоне).

SUBST вопрос ключ значение

Вопросы могут содержать подстановки в полях «Description» и «Choices» (хотя использование подстановок в полях «Choices» считается хаком; со временем будет разработан лучший способ). Эти подстановки выглядят как "${ключ}". Когда вопрос отображается, подстановки заменяются на нужные значения. Эта команда может быть использована для задания значения подстановки. Это полезно, если вам требуется показать какое-то сообщение пользователю, которое нельзя однозначно записать в файл templates.

Не пытайтесь использовать SUBST для замены ответа по умолчанию на вопрос; это не сработает, так как для этого служит команда SET.

FGET вопрос флаг

Вопросы могут иметь связанные с ними флаги. Флаги могут иметь значение «true» или «false». Эта команда возвращает значение флага.

FSET вопрос флаг значение

Устанавливает значение флага вопроса. Значение должно быть или «true» или «false».

Распространённым флагом является флаг «seen». Обычно он устанавливается только если пользователь уже видел вопрос. Debconf обычно показывает только те вопросы пользователю, у которых флаг seen установлен в «false» (или если происходит процесс перенастройки пакета). Иногда вам нужно чтобы пользователь увидел вопрос снова — в этом случае вы можете установить флаг seen в значение «false», чтобы заставить debconf показать вопрос ещё раз.

METAGET вопрос поле

Возвращает значение любого поля вопроса из соответствующего шаблона (например, Description).

REGISTER шаблон вопрос

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

UNREGISTER вопрос

Удаляет вопрос из базы данных.

PURGE

Вызывайте эту команду из сценария postrm при вычистке пакета. Она удалит все вопросы пакета из базы данных debconf.

X_LOADTEMPLATEFILE /путь/к/templates [владелец]

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

Вот простой пример протокола debconf в действии.

INPUT medium debconf/frontend
30 question skipped
FSET debconf/frontend seen false
0 false
INPUT high debconf/frontend
0 question will be asked
GO
[ Здесь debconf показывает вопрос пользователю. ]
0 ok
GET no/such/question
10 no/such/question doesn’t exist
GET debconf/frontend
0 Dialog

БИБЛИОТЕКИ

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

Для программирования на языке оболочки командной строки существует библиотека /usr/share/debconf/confmodule, которую вы можете указать в начале сценария оболочки, и общаться с debconf в довольно естественной манере, используя команды debconf протокола записанные строчными буквами, которые начинаются с «db_» (то есть, «db_input» и «db_go»). Подробней смотрите в confmodule(3).

Программисты на perl могут использовать модуль Debconf::Client::ConfModule(3pm), а программисты на python могут использовать модуль debconf.

В последующих примерах сценариев оболочки руководства будет использована библиотека /usr/share/debconf/confmodule. Вот пример сценария config, который задаёт вопрос с помощью этой библиотеки:

#!/bin/sh
set -e
. /usr/share/debconf/confmodule
db_set mypackage/reboot-now false
db_input high mypackage/reboot-now || true
db_go || true

Заметим, что использование "|| true" предотвращает сценарий от завершения работы, если debconf решит, что не может показать вопрос, или когда пользователь решит вернуться к предыдущему вопросу. В таких ситуациях debconf возвращает ненулевой код возврата, и так как в сценарии оболочки указана set -e, неотловленный код выхода прервёт его работу.

А вот соответствующий сценарий postinst, который использует пользовательский ответ на вопрос, должна ли система быть перезагружена (слегка абсурдный пример..):

#!/bin/sh
set -e
. /usr/share/debconf/confmodule
db_get mypackage/reboot-now
if [ "$RET" = true ]; then
shutdown -r now
fi

Заметим, что используемая переменная $RET получает расширенный код возврата от команды GET, который содержит ответ пользователя на вопрос.

СЦЕНАРИЙ POSTINST

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

*

Не задавайте вопросов из сценария postinst. Вопросы должен задавать сценарий config с помощью debconf, для того чтобы работал механизм предварительной настройки.

*

Всегда включайте /usr/share/debconf/confmodule в самом начале сценария postinst, даже если не будет использоваться никаких команд db_*. Это нужно для того, чтобы дать шанс запуститься сценарию config (подробней смотрите в разделе ХАКИ).

*

Избегайте вывода чего-либо в stdout из сценария postinst, так как это debconf может неправильно понять и вообще, сценарий postinst не должен быть слишком разговорчивым. Если очень нужно — выводите в stderr.

*

Если сценарий postinst запускается в режиме демона, убедитесь, что вы послали debconf команду STOP в конце работы, так как debconf не сможет отловить момент когда postinst закончил с ним работать.

*

Пишите сценарий postinst так, чтобы он принимал первым параметром «reconfigure». Он может трактовать его как «configure». Это будет использовано в следующих версиях debconf, чтобы указать сценариям postinst когда они должны выполнять перенастройку.

ДРУГИЕ СЦЕНАРИИ

Кроме сценариев config и postinst, вы можете использовать debconf в любом сценарии сопровождения пакета. Наиболее часто вы будете использовать debconf в сценарии postrm, чтобы вызвать команду PURGE при удалении пакета для удаления всех записей о пакете из базы данных debconf (кстати, это настраивается автоматически с помощью dh_installdebconf(1)).

Также debconf может быть использован в сценарии postrm при вычистке пакета чтобы задать вопрос об удалении чего-либо. Или может быть по какой-то причине вам может потребоваться использовать его в preinst или prerm. Везде он будет работать, хотя это вероятно приведёт к выдаче одинаковых вопросов и реакции на ответы в программе, а не к двум разным действиям, как это происходит со сценариями config и postinst.

Заметим, что если ваш пакет использует debconf только в сценарии postrm, вы должны добавить вызов /usr/share/debconf/confmodule в начало сценария postinst, чтобы дать debconf шанс загрузить файл templates в свою базу данных. Затем templates будет доступен во время вычистке пакета.

Также вы можете использовать debconf просто в работе программ. Но не забывайте, что debconf не предназначен для этого, и не должен использоваться как своеобразный реестр. Это всё-таки unix, и настройки программ хранятся в файлах в каталоге /etc, а не в какой-то мутной базе данных debconf (которая в конечном счёте просто кэш и может сломаться). Так что тридцать раз подумайте перед тем как использовать debconf в простых программах.

Есть ситуации когда это имеет смысл, например в программе apt-setup, которая использует debconf для того чтобы пользователь завершил процесс установки debian в похожем интерфейсе, и сразу же применяет эти ответы для настройки файла sources.list программы apt.

ЛОКАЛИЗАЦИЯ

Debconf поддерживает локализацию файлов templates. Это достигается добавлением дополнительных полей с переведённым текстом. Любые поля можно переводить. Например, есть желание перевести описание на испанский. Просто создайте поле с именем «Description-es», в котором есть перевод. Если переведённое поле недоступно, debconf использует обычный английский вариант.

Помимо поля «Description», вы можете перевести поле «Choices» из шаблона select или multiselect. Убедитесь, что переведённые значения в списке расположены в том же порядке что и в основном поле «Choices». Вам не нужно переводить поле «Default» из вопроса select или multiselect, ответ на вопрос будет автоматически возвращён на английский.

Вы обнаружите, что для облегчения управления переводами лучше хранить их в отдельных файлах; один файл на перевод. Раньше для управления файлами debian/template.ll использовались программы debconf-getlang(1) и debconf-mergetemplate(1). Теперь эту функцию выполняет пакет po-debconf(7), который позволяет работать с переводами debconf в .po файлах как с любыми другими переводами. Ваши переводчики будут благодарны вам за использование этого нового усовершенствованного механизма.

Подробней о po-debconf смотрите в его справочной странице. Если вы используете debhelper, переведите его в po-debconf просто запустив один раз команду debconf-gettextize(1) и добавьте зависимость в Build-Dependency от po-debconf и от debhelper (>= 4.1.13).

СОБЕРЁМ ВСЁ ВМЕСТЕ

Итак, у вас есть сценарий config, файл templates, сценарий postinst, использующие debconf и так далее. Поместить все эти части в пакет debian несложно. Вы можете сделать это вручную или с помощью dh_installdebconf(1), которая объединяет переведённые шаблоны, копирует файлы в нужные места, и даже генерирует вызов PURGE, который должен быть в сценарии postrm. Убедитесь, что ваш пакет зависит от debconf (>= 0.5), так как более ранние версии несовместимы с всем описанным в руководстве. Всё.

Да, за исключением тестирования, отладки и реального использования debconf для более интересных вещей чем простое задание вопросов. Об этом читайте далее..

ОТЛАДКА

Итак, у вас есть пакет, который предположительно использует debconf, но он не совсем работает. Может быть debconf не только задаёт вопросы при настройке. Или может быть что-то странное случается; он крутится в каком-то вечном цикле или хуже. К счастью, debconf обладает множеством средств отладки.
DEBCONF_DEBUG

Наипервейшая вещь — это переменная окружения DEBCONF_DEBUG. Если вы сделаете export DEBCONF_DEBUG=developer, то debconf будет выводить в stderr дамп протокола debconf при работе. Это выглядит как-то так(сразу видно опечатку):

debconf (developer): <-- input high debconf/frontand
debconf (developer): --> 10 "debconf/frontand" doesn’t exist
debconf (developer): <-- go
debconf (developer): --> 0 ok

Для отладки довольно полезно использовать интерфейс debconf readline(по мнению автора), поскольку вопросы не мешают и весь отладочный вывод легко сохранить.

DEBCONF_C_VALUES

Если значение данной переменной окружения равно «true», то интерфейсная программа будет показывать значения шаблонов Choices-C (если есть) выбора и множественного выбора, вместо описательных значений.

debconf-communicate

Другим полезным инструментом является программа debconf-communicate(1). Просто запустите её и вы сможете интерактивно вводить команды протокола debconf непосредственно в debconf. Это отличный способ попробовать команды на лету.

debconf-show

Если пользователь сообщил о проблеме, можно использовать debconf-show(1), чтобы скопировать все вопросы пакета, показать их ответы и узнать какие из них видел пользователь.

.debconfrc

Чтобы избежать утомительного цикла сборка/установка/отладка, можно загрузить файл templates с помощью debconf-loadtemplate(1) и запустить сценарий config вручную с помощью команды debconf(1). Однако, вам по прежнему требуются права суперпользователя? Не очень здорово. И в идеале вы хотели бы увидеть как проходит новая установка, с чистой базой данных debconf.

Оказывается, если вы настроите файл ~/.debconfrc для обычного пользователя, указав в нём личный config.dat и template.dat, то сможете загружать файлы templates и запускать сценарий config как хочется, без полномочий суперпользователя. Если нужно ещё раз запуститься с чистой базой данных, просто удалите файлы *.dat.

Подробности настройки смотрите в debconf.conf(5), и заметим, что файл /etc/debconf.conf отлично подойдёт как шаблон для личного файла ~/.debconfrc.

УГЛУБЛЁННОЕ ПРОГРАММИРОВАНИЕ С DEBCONF

Работа с файлами настройки
Многие из вас хотели бы использовать debconf для управления файлами настройки пакета. Возможно в файлах настройки недостаточно настроек по умолчанию и поэтому вы бы хотели использовать debconf для опроса пользователя и основываясь на его ответах создавать файл настройки. Кажется, что этого легко достичь, но когда вы задумаетесь об обновлении, или когда кто-то ещё изменит файл настройки, созданный вами и про dpkg-reconfigure и ...

Есть много способов сделать это и большинство из них неправильные, а вы будете получать раздражающие отчёты об ошибках. Есть один правильный путь достижения этого. В нём предполагается, что ваш файл настройки состоит из простого набора устанавливаемых переменных оболочки с комментариями, и поэтому вы можете просто выполнить файл для его «загрузки». Если у вас более сложный формат, чтение (и запись) становятся более сложными.

Ваш сценарий config будет выглядеть так:

#!/bin/sh
CONFIGFILE=/etc/foo.conf
set -e
. /usr/share/debconf/confmodule

# Загрузить файл настройки, если он существует.
if [ -e $CONFIGFILE ]; then
. $CONFIGFILE || true

# Сохранить значения из файла настройки в
# базу данных debconf.
db_set mypackage/foo "$FOO"
db_set mypackage/bar "$BAR"
fi

# Задать вопросы.
db_input medium mypackage/foo || true
db_input medium mypackage/bar || true
db_go || true

А сценарий postinst будет выглядеть так:

#!/bin/sh
CONFIGFILE=/etc/foo.conf
set -e
. /usr/share/debconf/confmodule

# Сгенерировать файл настройки, если он не существует.
# Или скопировать из файла шаблона
# откуда-то ещё.
if [ ! -e $CONFIGFILE ]; then
echo "# Config file for my package" > $CONFIGFILE
echo "FOO=" >> $CONFIGFILE
echo "BAR=" >> $CONFIGFILE
fi

# Выполнить подстановку значений из базы данных debconf.
# Здесь явно возможна оптимизация.
# Команда cp перед sed проверяет, что мы не испортили
# права доступа и владельца файла настройки.
db_get mypackage/foo
FOO="$RET"
db_get mypackage/bar
BAR="$RET"
cp -a -f $CONFIGFILE $CONFIGFILE.tmp

# Если администратор удалил или закомментировал какие-то переменные, но
# после задал их через debconf, добавим(пересоздадим) их в файле
# настройки.
test -z "$FOO" || grep -Eq ’^ *FOO=’ $CONFIGFILE || \
echo "FOO=" >> $CONFIGFILE
test -z "$BAR" || grep -Eq ’^ *BAR=’ $CONFIGFILE || \
echo "BAR=" >> $CONFIGFILE

sed -e "s/^ *FOO=.*/FOO=\"$FOO\"/" \
-e "s/^ *BAR=.*/BAR=\"$BAR\"/" \
< $CONFIGFILE > $CONFIGFILE.tmp
mv -f $CONFIGFILE.tmp $CONFIGFILE

Рассмотрим как эти два сценария обрабатывают все возможные ситуации. При начальной установке сценарий config задаёт вопросы и новый файл настройки генерируется сценарием postinst. При обновлении или перенастройке файл настройки читается и его значения используются для изменения значений в базе данных debconf, поэтому значения, изменённые администратором, не теряются. Вопросы задаются снова (могут показываться, а могут и нет). Затем сценарий postinst выполняет подстановку значений обратно в файл настройки, не изменяя всего остального.

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

Так как debconf управляется вашим сценарием config, он не может прыгнуть обратно на предыдущий вопрос сам, но с небольшой вашей помощью, он сможет выполнить этот подвиг. Первый шаг — в сценарии config дать понять debconf, что сценарий config способен обрабатывать нажатие кнопки назад пользователем. Для этого используйте команду CAPB, передав backup в качестве параметра.

Затем, после каждой команды GO вы должны выполнять тест, чтобы увидеть просил ли пользователь вернуться назад (debconf возвратит код 30), и, если это произошло, возвратиться к предыдущему вопросу.

Есть несколько способов написания управляющих структур в вашей программе для возвращения к предыдущему вопросу при необходимости. Вы можете написать код, использующий кучу команд перехода goto. Или вы можете создать несколько функций и использовать рекурсию. Но возможно самым понятным и простым является создание конечного автомата. Вот скелет конечного автомата, который можно заполнить и расширить.

#!/bin/sh
set -e
. /usr/share/debconf/confmodule
db_capb backup

STATE=1
while true; do
case "$STATE" in
1)
# Два несвязных вопроса.
db_input medium my/question || true
db_input medium my/other_question || true
;;
2)
# Задавать этот вопрос, только если
# на первый был получен
# положительный ответ.
db_get my/question
if [ "$RET" = "true" ]; then
db_input medium my/dep_question || true
fi
;;
*)
# Действие по умолчанию возникает, когда $STATE больше чем последнее
# введённое состояние и прерывает цикл. Для
# этого требуется, чтобы состояния были пронумерованы последовательно с 1
# без перерывов, так как в этом случае возникает событие по умолчанию
break # выход из обёртывающего цикла "while"
;;
esac

if db_go; then
STATE=$(($STATE + 1))
else
STATE=$(($STATE - 1))
fi
done

if [ $STATE -eq 0 ]; then
# Пользователь хочет выйти из первого вопроса.
# Это проблематичный случай. Обычная установка
# пакетов с помощью dpkg и apt не даёт
# вернуться к вопросам между пакетами как здесь написано,
# поэтому завершить работу, оставив пакет ненастроенным,
# вероятно, лучший путь обработки этой ситуации.
exit 10
fi

Заметим, что если ваш сценарий config задаёт несколько несвязных между собой вопросов, то конечный автомат не нужен. Просто задайте их все и GO; debconf покажет их все на одном экране, и пользователю не нужно возвращаться назад.

Предотвращение бесконечных циклов
Это может произойти с debconf, если в сценарии config есть цикл. Предположим, вы задаёте вопрос и проверяете ответ, и всё это в цикле пока ответ не станет правильным:

ok=”
do while [ ! "$ok" ];
db_input low foo/bar || true
db_go || true
db_get foo/bar
if [ "$RET" ]; then
ok=1
fi
done

На первый взгляд всё выглядит хорошо. Но посмотрим что произойдёт, если значение foo/bar будет "" в начале цикла, а пользователь установил приоритет на высокий, или использует не интерактивный интерфейс, и поэтому вопрос на самом деле задан не будет. Значение foo/bar не изменяется в db_input, и тест не проходит и повторяется. И всё зацикливается …

Одним вариантом исправления является задание значения foo/bar перед входом в цикл. Например, если значение по умолчанию foo/bar равно «1», то вы можете выполнить RESET foo/bar перед входом в цикл.

В другом варианте можно проверить код завершения команды INPUT. Если он равен 30, то пользователю вопрос показан не был, и вы должны прервать цикл.

Выбор из похожих пакетов
Иногда может выполняться установка похожих по функциональности пакетов, и вы хотели бы предложить пользователю выбрать пакет, который должен использоваться по умолчанию. Примером таких наборов могут быть оконные менеджеры или файлы словарей ispell.

Хотя можно для каждого пакета задавать простой вопрос «Использовать этот пакет по умолчанию?», но это приведёт к большому числу повторяющихся вопросов, если устанавливается несколько пакетов. С помощью debconf возможно создать список всех пакетов и позволить пользователю сделать из него выбор. Посмотрим как это делается.

Сделайте так, чтобы все пакеты в наборе использовали общий шаблон. Например:

Template: shared/window-manager
Type: select
Choices: ${choices}
Description: Выбор оконного менеджера по умолчанию.
Определяет выбранный оконный менеджер, который запускается
по умолчанию при запуске X.

Каждый пакет должен содержать копию этого шаблона. Также нужно включить код в свой сценарий config, примерно такой:

db_metaget shared/window-manager owners
OWNERS=$RET
db_metaget shared/window-manager choices
CHOICES=$RET

if [ "$OWNERS" != "$CHOICES" ]; then
db_subst shared/window-manager choices $OWNERS
db_fset shared/window-manager seen false
fi

db_input medium shared/window-manager || true
db_go || true

Объяснение. Во время запуска сценария config, debconf уже прочитал все шаблоны для устанавливаемых пакетов. Так как эти пакеты используют общий вопрос, debconf записывает этот факт в поле владельцев. По странному совпадению формат поля владельцев такой же как и у поля выбора (запятая и пробел являются разделителями в списке значений).

Команда METAGET может использоваться для получения списка владельцев и списка выбора. Если они различаются, то это значит, что был установлен новый пакет. Поэтому используется команда SUBST для замены списка выбора на список владельцев и задаётся вопрос.

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

Это можно выполнить добавив следующее в сценарии prerm всех похожих пакетов (заменив <package> именем пакета):

if [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
# больше не задавать этот вопрос.
db_unregister shared/window-manager

# Проверить, что общий вопрос ещё существует.
if db_get shared/window-manager; then
db_metaget shared/window-manager owners
db_subst shared/window-manager choices $RET
db_metaget shared/window-manager value
if [ "<package>" = "$RET" ] ; then
db_fset shared/window-manager seen false
db_input high shared/window-manager || true
db_go || true
fi

# Теперь сделать тоже что делает сценарий postinst
# для обновления символической ссылки оконного менеджера.
fi
fi

ХАКИ

В настоящее время debconf не полностью интегрирован с dpkg (но я хочу изменить это в будущем), и поэтому сейчас вызывается для некоторых неприятных хаков.

Худшие из них требуют запуска сценария config. Для этого в настоящий момент приходится запускать сценарий config во время предварительной настройки пакета. Также, при запуске сценария postinst, снова запускается debconf. Debconf понимает, что запускается из сценария postinst и поэтому останавливается и запускает сценарий config. Это работает, только если ваш postinst загружает хотя бы одну из библиотек debconf, поэтому postinst всегда делает это. Мы надеемся вернуться к этому позже добавив явную поддержку в dpkg для debconf. Программа debconf(1) — это шаг в данном направлении.

Похожий хак — запуск debconf когда сценарии config, postinst или другие программы используют его при запуске. Не смотря ни на что, они ожидают что будут сразу общаться с debconf. Сейчас это выполняется, так что когда такой сценарий загружает библиотеку debconf (типа /usr/share/debconf/confmodule), а debconf ещё не запущен, он запускается, и новая копия сценария выполняется заново. Заметный результат в том, что вам нужно указывать строку загрузки библиотеки debconf как можно ближе к началу сценария, или могут случиться непонятные вещи. Мы надеемся вернуться к этой проблеме позже изменив вызов debconf, и превратить его в что-то подобное отдельного демона.

Сравнимо с хаком и то, как debconf выясняет какие файл шаблонов загружены и когда их загружать. Когда сценарии config, preinst и postinst вызывают debconf, он автоматически выясняет где файл templates и загружает его. Отдельные программы, использующие debconf, заставляют debconf искать файл шаблонов в /usr/share/debconf/templates/имя_программы.templates. И если postrm хочет использовать debconf при вычистке, шаблоны будут недоступны если только у debconf не будет шанса загрузить их в postinst. Не неприятно, но неизбежно. Хотя в будущем некоторые из таких программ смогут использовать debconf-loadtemplate вручную.

Исторически сложившееся поведение /usr/share/debconf/confmodule играть с файловыми дескрипторами и устанавливать fd #3 через который происходит общение с debconf, может вызывать весь набор проблем при запуске из postinst демона, так как демон завершает общение с debconf, а debconf не может определить когда завершился сценарий. Команда STOP помогает обойти это. В будущем, мы рассмотрим создание связи с debconf через сокет или другой механизм отличный от stdio.

Debconf устанавливает DEBCONF_RECONFIGURE=1 перед запуском сценариев postinst, поэтому сценарий postinst, которому требуется избежать некоторой затратной операции перед перенастройкой, может проверить наличие этой переменной. Это хак, так как правильно сделать это — передать $1 = «reconfigure», но этого делать нельзя, так как будут работать неправильно все сценарии postinst, использующие debconf. Разработан план перехода на приём сценариями postinst значения «reconfigure», и как только все сделают это, начнётся передача этого параметра.

СМОТРИТЕ ТАКЖЕ

В debconf(7) содержится руководство пользователя debconf.

Описание debconf в политике debian является спецификацией протокола debconf. /usr/share/doc/debian-policy/debconf_specification.txt.gz

debconf.conf(5) содержит много полезной информации, включая описание среды хранения базы данных.

АВТОР

Joey Hess <joeyh [AT] debian.org>