Пакеты
На самом деле, у нас сегодня довольно важная тема, без которой в принципе мы этот курс закончить не можем, потому что мы худо-бедно рассмотрели, потыкали палочкой, я бы сказал, как функционирует ваша линукс система с точки зрения ее как системы, с точки зрения ее как окружения для вашей работы, с точки зрения ее как просто какого-то набора программ, установленных на компьютер, в общем, с разных сторон мы потыкали палочкой нашу операционную систему, даже как ее настраивать частично посмотрели, не посмотрели только главное, собственно, почему операционная система… операционная система - это что вообще такое? Напоминаю, что у нас был разговор в самом начале про то, что операционная система - это такой программный комплекс, то есть, это штука, которая внезапно оказывается на вашем компьютере и решает там 3 класса задач: унификация доступа к ресурсам компьютеров, разделение доступа к ресурсам компьютеров и частично протоколирование, журналирование - не знаю, как правильно сказать, потому что… учет! потому что на самом деле вот это вот протоколирование/журналирование/учет нужно для того, чтобы потом эффективнее организовывать доступ и разделение этого доступа, чтобы там отдельные субъекты системы не перебрали себе ресурсы. Но, тем не менее, это очень важные компоненты.
Вот. Значит, потом мы нарисовали такую условную картинку, что у нас есть ядро операционной системы, которое обеспечивает всю базовую функциональность, связанную с этим доступом к ресурсам - с разделением, учетом и унификацией, а вокруг этого ядра наверчено очень много дополнительного софта, который, собственно, певращает вот эту базовую функциональность, которой могут пользоваться только программисты, причем системные, в функциональность прикладную, которой могут пользоваться не системные, а прикладные, которой могут пользоваться пользователи, запускающие отдельные программы, ну и так дальше. Получается такая архитектура, состоящая из ядра, каких-то утилит для работы с внутренностями операционной системы, каких-то приложений, работающих с прикладными задачами, ну и еще где-то там в промежутке будут болтаться библиотеки, оформляющие способ доступа, оформляющие то, что называется API. Очень бы хотелось рассмотреть вопрос, откуда они вообще взялись - все эти библиотеки, все это ядро, все эти компоненты вашей системы, все эти программы, которые мы запускаем - помните, у нас было занятие, когда мы посмотрели, сколько программ лежит в каталоге /usr/bin?
На этом компьютере сейчас их 2500 штук.
Первое число - количество строк, второе - количество слов, третье - количество символов. Поскольку ls, видя, что его стандартный вывод не является экраном, выдает все в одну колонку, и поскольку, не смотря на то, что пробел может входить в имя файла, так обычно не делают, по крайней мере, с запускаемыми программами, у нас количество строк совпадает с количеством слов, вот. Вот если мы насильственно скажем выводить в несколько колонок, то у нас эти числа будут не равны.
Я вангую, что там было две колонки - и правда две.
Ну, вот. Тут, значит, тема, в которую мы вступаем, она такая, двойственная, и я думаю, что мы как раз две последние лекции ей посвятим. С одной стороны, нам интересно, как вообще оказываются вот эти программы на нашем компьютере и что с ними можно делать, а с другой стороны было бы очень недурственно узнать, почему вообще возможна такая ситуация, что у вас установлена операционная система, в которой многие тысячи программ установлены, и при этом, вроде как, никто не просит за нее денег. Дело тут, конечно, не в деньгах, или, вернее скажем, деньги тут не играют решающую роль, но, в принципе, подумать о том, как компенсируются очевидные многие затраты ресурсов при разработке операционной системе было бы интересно, потому что, на самом деле, и я об этом, видимо, скажу в следующий раз, вся идея очень простая. Любой человек, который может потратить какую-то долю своих ресурсов на улучшение операционной системы, это делает, и поэтому она улучшается. Так что вот эту вот неразделимую проблему мы начнем рассматривать с одной стороны, со стороны технологической, а именно - откуда все это берется, как оно сопровождается, я имею в виду, программное наполнение операционной системы, и что с этим вообще делать.
Сайчас, попробую подумать, с какой стороны к этому подойти. Наверное, стоит рассказать вот что. Представим себе, что мы задались целью собрать операционную систему (собрать в буквальном смысле, что называется, с миру по нитке) из отдельно разрабатываемых продуктов, каждый из которых имеет собственную цель, свое комьюнити, каких-то ключевых разработчиков и так далее. Собственно говоря, идея создания операционной системы полностью свободной овладела массами еще очень давно, проект GNU, которому мы благодарны за свободную лицензию, когда-то очень давно надумал свободную операционную систему, которая называется Hurd, она до сих пор существует, но развивается не очень сильно. Как будет устроен такой программный комплект? Со всей очевидностью, он должен состоять из многих компонентов, которые по желанию можно туда надстроить над тем, что уже есть, или удалить. Первая мысль, которая приходит в голову: давайте все соберем сами, соберем - в смысле скомпилируем. У нас есть ядро операционной системы, которое мы берем из одного места, у нас есть необходимое для сборки программное окружение, которое мы берем из другого места, и мы это ядро собираем и вот у нас первый компонент системы готов, скомпилировано ядро сообразно профилю, сообразно тому, как мы его, может быть, перенастроили, ну и так дальше. Дальше мы подгребаем исходные тексты необходимых утилит, и так же их компилируем, собираем из них бинарники. Дальше мы подгребаем еще что-то, еще что-то, ну и худо-бедно таким способом мы можем через несколько компьютеромесяцев, через несколько месяцев непосредственной компиляции получить результат. Может быть даже быстрее. Через неделю непосредственной компиляции исходных текстов получить программный комплект, состоящий из ядра, утилит, нужных нам приложений, и использовать это на компьютере в свое удовольствие.
По крайней мере, так представлялось себе понятие операционной системы где-то лет 30 назад, может быть, даже 40. Дело в том, что в те поры развитие программного обеспечения было достаточно медленным, то есть, если, там, компьютер создавался пять лет, то и программное обеспечение для него создавалось лет 5, вот за эти лет 5 мы вполне могли написать качественную операционную систему, ее хорошенько задокументировать, все такое. Однако, вот эта идея о том, что все собирается из исходных текстов, она имеет 3 неприятные стороны, две из них мы будем рассматривать в следующий раз, а третья состоит в том, что вся эта сборка может у вас просто не пойти, или она идет долго, ну, долго - то ладно, мы можем потерпеть, но она может просто не пойти: что-то случилось не так, что-то пошло не так, вы - недостаточно компетентный программист в области всего, чтобы собрать всю операционную систему от начала до конца со всеми приложеньками, ваше железо немножко не такое, как железо, где все это хорошо собиралось, и поэтому оно может не собраться, ну и так дальше. То есть, если про программу вообще неизвестно, насколько она хорошо работает в разных случаях и ее требуется допиливать-дотачивать до конкретной ситуации, то действительно лучше ее собирать из исходных текстов всякий раз, когда вы ее собираетесь использовать, а вот если про программу уже известно довольно все, то было бы, конечно, удобнее где-то ее собрать, получить бинарник, этот бинарник потом радостно скопировать себе на компьютер и запускать. Это требует, разумеется, существенно большей дисциплины программирования, особенно в случае, если у вас достаточно произвольная конфигурация операционной системы, не монолитная, но зато мы можем пропустить все стадии сборки и начать прямо со стадии инсталляции.
Этому очень способствует структурирование файловой системы юникс и, вслед за ним, линукс, очень четко иерархическая, то что называется Filesystem Hierarchy Standard, где всегда заранее известно, в какое место надо закладывать наши бинарники, библиотеки, документацию ну и так дальше и так дальше.То есть, мы, в принципе, можем организовать дистрибуцию наших программных продуктов таким образом, что мы делаем из них архив, этот архив называем, ну, скажем, пакетом, и эти пакеты распространяем. О том, почему это возможно и какие там есть интересности - будем говорить в следующий раз, а сейчас сосредоточимся на понятии «пакет».
Итак, что такое понятие «пакет»: у пакета должна быть возможность его получить откуда-то, установить на нашу систему, никому при этом не повредив, и удалить ее из системы, никому при этом не повредив. Вот это «никому при этом не повредив» - собственно, ключевой момент, когда мы принимаем решение, как будет устроена система пакетирования. Вариантов здесь, в общем, два. Ну, то есть, вариантов три, есть еще вариант - мы поставили монолитную операционную систему и ничего там не меняем, а обновление ее идет как обновление прошивки: берем, сносим предыдущую всю операционную систему целиком и ставим новую. Что там при этом изменилось - об этом пускай голова заботится у тех .кто эту операционную систему изготовил, и если она небольшая в действительности прошивка, то так, наверное, даже удобнее: люди собрали новую прошивку, ее хорошенько оттестировали и давай ее распространять вместо старой. Если же мы имеем дело с большой операционной системой общего назначения - такая картина, конечно, нам не годится и нам надо принимать два других решения: либо то, что мы называем пакетом - это такая немножко монада спинозовская, то есть, она содержит все необходимое для работы в себе и про окружающий мир ничего не знает, ну, за исключением того, что она работает на ядре линукс и, возможно, пользуется библиотекой libc. Все остальное эта монада с собой таскает, тем самым обеспечивая нам такой достаточно примитивный механизм установки и удаления. Он будет заключаться в том, что мы берем этот архив, целиком его куда-то распаковываем, возможно, сообщаем нашей программному продукту, который мы в этом архиве распространяем, в какое место его распаковали, если его это интересует, после чего он запускается и работает - все! Проблем, в общем, никаких, за двумя исключениями: исключение первое - это, как ни странно, объем: представьте себе ,что у нас есть программа, которая пользуется, ну, там, не знаю, графическим сервером и набором графических библиотек, которые рисуют кнопочки, менюшки, вот это все. На самом деле, довольно нехилый набор графических библиотек, ну, не знаю:
Если вам кажется, что 37 мб - очень маленькая библиотека - ну, вспоминаем, сколько весит дистрибутив какого-нибудь обычного хрома под обычную винду. На самом деле, линуксоид, глядя на вот этот размер, скажет «ууу, какая здоровая!». Вот этот размер - 37 мб - это действительно очень большой компонент. А если мы придерживаемся вот этого принципа монолитности нашего пакета, то такие размеры будут минимум в 10 раз больше. Но это даже, на самом деле, не главная проблема. Главная проблема состоит в том, что вот эти привносимые нами с пакетами библиотеки, например, библиотека libqt, согласно нашему принципу изолирования, каждое приложение будет работать со своей копией, со своей версией, и загружать свою копию в память, то есть, фиг бы с ним, с диском. У нас наступит толчея одинаковых загружаемых библиотек прямо в памяти, и еще не факт, что это хорошо будет работать, потому что приложение загружается, оно спрашивает… На самом деле, главная проблема - не только проблема объема, но и проблема разноверсицы. Проблема разноверсицы, когда у нас есть одна и та же функциональность, предоставляемая внутри каждого нашего монолитного пакета отдельной сущностью, может быть решена, как мы только что обсуждали, самыми различными способами.
Способ 1: мы делаем полностью ограниченное окружение, как это сделано, скажем, в макосе, когда у нас есть небольшой объем базовых библиотек и функций ядра, которые предоставляются всем и все остальное четко изолированное в рамках нашего приложения, в рамках нашего пакета и подгрузка соответствующих библиотек ни на что не влияет, только память пожирает. Два раза по 37 мб - будет два раза по 37, 10 раз по 37 - будет 370 мб, вот это все. А второй вариант - кто первый встал - того и тапки: мы подгрузили библиотеку, у которой есть конкретное версионирование, и если какое-то другое приложение попросит такую же - оно получит ее, а не ту, с которой она приехала. Тут возможны удивительно совершенные сайд-эффекты, потому что если эти две библиотеки не идентичны, собраны в разное время на разных хостах с разными опциями, возможно даже с разных исходников, то, конечно, функционирование будет довольно странное вплоть до того, что это просто не будет работать, потому что у нас внезапно окажутся разные размеры объектов в памяти - мы будем дереференсить память, которая на самом деле не принадлежит нашему объекту и все радости программирования на С нас встретят в полный рост.
Другой подход: если мы не пользуемся монолитным пакетированием, состоит в том, чтобы пакетирование было разделенное: в каждый пакет входит только та функциональность, которую он предоставляет, а все остальные программы, которые мы будем запускать, этой функциональностью пользуются. Тем самым мы убиваем несколько зайцев: первое - если у нас возникла ошибка в исходном коде какого-то из компонентов - мы его правим, обновляем и одновременно эта ошибка перестает проявляться везде во всех программах, которые этим компонентом пользуются. Если, конечно, при этом не поменялся программный интерфейс библиотеки: размеры объектов остались те же, описания функций остались те же, ABI тоже не поменялось - в этом случае ошибка исправляется просто прозрачно для всех приложений.
В случае, если в процессе обновления и изменения одного компонента меняется его ABI, то нам нужно поменять так же и все компоненты, которые этим ABI пользуются. Таким образом у нас возникают понятия совместимости и зависимости. В принципе, понятие совместимости оно и в первом случае возникает: если мы внезапно подсовываем одну библиотеку вместо другой, то может ничего и не заработать, только у нас нет средств контроля этой совместимости. В случае, когда у нас раздельное пакетирование, мы без средства контроля совместимости вообще ничего сделать не можем, и поэтому на сегодня давайте предположим, что она есть: то есть, что у вас есть некий инструментарий, который позволяет для каждого вашего компонента проверить в действительности, что он совместим со всеми остальными - звучит, конечно, немного сказочно, но, поверьте, так оно и происходит, и вы как пользователь пользуетесь результатами работы этого инструментария, то есть те пакеты, которые вы ставите - заранее известно, что библиотеки подходят к тем приложениям, которыми пользуется, что они правильной версии, что обновления влекут за собой исправление ошибок и обновление тех компонентов, для которых оно потребовалось и так дальше.
Второй бонус - разумеется, размер, я уже говорил, что это одна из самых крупных библиотек, третий бонус - отсутствие разноверсицы как таковой: если в вашей системе присутствует несколько версий одной и той же библиотеки, то это скорее исключение, чем правило - такое бывает, потому что, например, какие-то приложения просто органически не могут быть собраны с более новой версии, поэтому все еще присутствует более старая, но это, скорее, исключение. Главный при этом недостаток, который внезапно возникает - понятие «зависимость». Смотрите: теперь мы не можем просто поставить библиотеку WebKit и просто поставить какую-то программу, которая будет этим вебкитом пользоваться. А если мы поставим программу, а библиотеку нет - программу даже запустить будет нельзя. Ну вот смотрите: вот вам пожалуйста некий бинарник, который называется qtbrowser - справочник. Некая программа, просмотрщик внутренней документации зависит вот от таких библиотек, соответственно, если у нас не установлен пакет с этой библиотекой - эта программа работать просто не будет.
Таким образом, наша система пакетирования, которую мы сейчас изобретаем должна в себе содержать понятие зависимости: утверждение, что данный пакет не может быть поставлен без данного второго - получается такое дерево - в случае, если вам хорошо повезло, ориентированный граф без циклов - если вам просто повезло, и ориентированный граф с циклами - если вам не повезло. Понятно, чем плох ориентированный граф с циклами: если у вас есть зависимости, то вы можете поставить только все пакеты вместе, потому что один требует другой, другой требует третий, а третий требует первый. Помимо самого понятия зависимости, там где-то в бекграунде еще лежит необходимость эти зависимости как-то генерировать, прописывать, короче говоря, повышается ваша ответственность за происходящее: если вы сделали изолированную какую-то прогу, то вы внутри этой программы все как надо сделали, а снаружи там трава не расти, лишь бы ядро было. Если вы сделали пакет в рамках нашего дистрибутива и в рамках нашего хранилища, то на вас ложится ответственность: либо сделать его таким, чтобы он был совместим с той частью хранилища, которое ему требуется, либо модифицировать и доработать ту часть хранилища, которая изменится с изменением вашего пакета.
Мы с вами останавливаемся на втором способе, потому что способ первый - он такой немножко не линуксовый, сейчас идут какие-то брожения умов на предмет того, не придумать ли нам какой-то способ изолированного пакетирования и не делать для дистрибутива из него, но пока что дальше придумок дело не пошло: я не видел работы с системой, которая таким образом устроена, за исключением MacOS, но MacOS - не Linux, там, собственно, разработка закрытая и в этом весь смысл. Мы в следующий раз поговорим, как открытая разработка помогает способу раздельного пакетирования. Итак, тогда вернемся к нашему понятию пакет.
Пакет - список файлов, архив, в котором совершенно четко известно, где какой компонент лежит.
Свойства:
- архив
Сразу возникает понятие конфликтов: было бы здорово, если бы при развертывании этого архива ни одного содержимого других пакетов не пострадало. Кто-то должен задуматься о том, чтобы хотя бы по файлам не было таких файлов, которые один и тот же файл принадлежит разным пакетам, и при этом они еще там разные. Что касается удаления пакета - тут возникает вопрос: а какие файлы удалять? Помимо того, что пакет - это архив, понятие «установка пакета» включает в себя прописывание в какую-то базу данных простейшую, какие файлы с этим пакетом приехали:
- регистрация в системе (список файлов, контрольные суммы, версия, идентификатор пакета (имя), тип файла).
Что такое регистрация в системе? Список файлов, версия, идентификатор пакета. Если мы такую штуку придумаем - это будет означать, что мы можем потом этот пакет спокойно удалить. Установка, удаление.
Чего нам еще хотелось бы от пакетов, кроме того, что мы можем устанавливать и удалять? Проверять цельность.
- целостность
Это не просто архив, это архив, про который написана, грубо говоря, контрольная сумма каждого файла, из которого он состоит. Потому что если контрольная сумма изменилась - файл изменился и с этой ситуацией надо что-то делать. Допустим, если это какой-то критически важный для системы компонент, тот факт, что у него изменилась контрольная сумма говорит о либо порче носителя, либо успешно проведенной атаке, либо еще о чем-нибудь таком.
Все ли файлы, входящие в состав пакета при проверке целостности должны вызывать такой триггер «ааа, у нас файл изменился, все плохо, давайте чинить»? Очевидно, нет, потому что у нас есть целая категория файлов, которые, собственно, предназначены для того, чтобы мы меняли их контрольную сумму - это настройки. Вот мы пакет установили, у него есть настроечный файл какой-нибудь по умолчанию, было бы неплохо этот настроечный файл по умолчанию под себя настроить. При этом, для файлов, которые менять не надо, у нас несовпадение контрольной суммы будет означать порчу. Для файлов, которые менять надо, для конфигов, несовпадение контрольной суммы будет говорить о том, что мы этот файл поменяли и с ним надо как-то по-другому поступать в случае обновления и удаления пакета. Тип файла: изменяемый/неизменяемый, но, на самом деле, там есть еще и другие варианты, например, файл может отсутствовать при установке и присутствовать при удалении, это какой-нибудь кэш: при удалении пакета было бы неплохо его кэши тоже снести, при этом они могут даже и не существовать. Изменяемые/неизменяемые - довольно важная штука. В разных конкретных реализациях понятия «пакет», возможно, работа с изменяемыми/неизменяемыми файлами происходит слегка неодинаково, но общий принцип такой: если мы пакет устанавливаем - к ним приезжает конфигурационный файл, тот факт, что его контрольная сумма не изменилась означает, что это тот же файл, который был в пакете и мы можем его удалять смело. Если пакет обновляется - такой неизменившийся конфигурационный файл можно смело удалять и ставить на его место обновленный. Если же контрольная сумма конфигурационного файла изменилась, то, например, при удалении пакета было бы неплохо этот измененный конфигурационный файл где-нибудь запасать. Ну, смотрите ситуацию: вы разрабатываете какое-нибудь решение, сели настраивать какой-нибудь cервис, не знаю, Apache какой-нибудь там страшный, и наплодили конфигурационных файлов, наизменяли имеющиеся, это все лежит в вашем каталоге. Потом по какой-то причине вам пришлось этот пакет удалить, ну, не знаю по какой: например, тот хост, на котором вы это делали - на нем нельзя хранить этот пакет, еще чего-нибудь. Короче говоря, вы пакет удалили, и конфигурационные файлы, над которыми вы трудились, внезапно пропали, потому что они пропали. Так делать, конечно, не надо. То есть, в случае, если конфигурационный файл изменен, а пакет удаляется - его надо куда-то перенести или переименовать… Короче, что-то с ним делать, чтобы он не мешал последующим поколениям, но при этом существовал. Если же вы пакет обновляете (то есть ставите пакет пакет с новой версией вместо пакета со старой) - все еще хуже. Что делать с конфигом, если пакет просто обновляется? Ну, если, на ваше счастье, синтаксис конфигурационного файла вообще никак не изменился и обновление не затрагивает разбор конфига, то надо просто оставлять старый как он был и не мучаться, но может быть такая штука, что с новым пакетом приезжает новый конфигурационный файл, в котором что-то поменялось: тогда у вас вообще ситуация неразрешима без применения мозга: с одной стороны у вас есть старый конфиг, который вы меняли, с другой стороны есть новый конфиг, который поменялся у разработчика. Надо из двух сделать один. Здесь разные уровни дисциплины требуют разных подходов: если, допустим, вы очень серьезное сообщество, как, например, Debian, то у вас есть прямо в полисе описанные правила: после обновления пакет должен продолжать работать. Что это значит: это значит, что вы, как мэйнтейнер, как человек, который формирует вот этот самый пакет, должны предусмотреть некую программу, которая возьмет и из двух конфигов из старого модифицированного и нового немодифицированного сделать новый модифицированный и он будет работать. Это не всегда возможно, но, как показывает практика, довольно часто это возможно. А если у вас уровень ответственности не такой высокий, пакет не такой критичный, можно просто сделать так: у вас останется старый конфиг, а новый конфиг приедет в специальное имя и потом вы скажете администратору, чтобы он вручную их слил. Это возможно всегда.
Да, значит, как следствие - поддержка удаления/обновления. Ну и всякие промежуточные варианты: файл может меняться, но заморачиваться с ним не надо, вот с этим переименованием - вот этот вот кэш, и так далее. Что еще? Этого мало! Если ваш пакет состоит только из данных или из каких-то утилит, которые вы будете запускать, то у вас все хорошо. Вполне возможно, что пакет является компонентом чего-то еще и ему при установке (и, кстати, при удалении тоже), необходимо не только разложить в разные места нужные файлы, но и дернуть какой-то компонент системы, скажем, запустить какую-нибудь программу переконфигурации. Типичный пример - библиотека разделяемая. Когда мы ставим разделяемую библиотеку - ее, вообще говоря, никто не видит, если мы подкладываем файл в какое-то место - его никто не видит. Нам нужно запустить программу ldconfig, которая бы обновила кэш имеющихся разделяемых библиотек и тогда в этом кэше ее имя станет доступна и все начнут ее видеть. Например, если вы библиотеку обновили: было бы недурно, чтобы операционная система узнала, что та библиотека, которая в памяти - уже не та библиотека, которая на диске. Или если вы положили библиотеку в какой-то специальный каталог и ее там должно быть видно. И так дальше. Другой пример - какие-нибудь модули для имеющегося сервера. Скажем, у вас есть веб-сервер и вы добавили в него возможность, скажем, писать прямо в него программы на перле или на питоне. Короче говоря, при установке и удаления пакета, помимо стандартной регистрации в системе, которую пакетный менеджер сделает сам, у вас, возможно, будет необходимость дополнительного запуска каких-то программ, которые эту установку или это удаление или это обновление поддержат. Это так называемые установочные и прочие сценарии. Я не знаю, как правильно сказать, они вообще называются установочными, но они же запускаются и при обновлении, и при удалении, а в дебиане - еще и при конфигурировании пакета. Написать сюда «служебные» - никто ничего не поймет:
- установочные и прочие сценарии
Идея в чем: вы можете в процедуру установки пакета руками забить все эти свойства, но для каждого пакета индивидуально должна быть возможность определить какой-то набор команд, которые должны быть выполнены при установке, удалении, обновлении, а в некоторых случаях - еще при попытке перенастроить этот пакет. И вот этот набор команд - уже чисто индивидуальный: модуль апаша должен перезагружать апаш, приложения, которые приносят с собою, допустим, иконку на рабочий стол, должно перегенерировать кэш иконок и так дальше и так дальше. Вот эти установочные сценарии - суть индивидуальные действия, кторые нужно производить при установке/удалении/обновлении и возможной конфигурации пакета. Забегая немножко вперед, скажу, что, вообще говоря, эти сценарии делятся на 2 вида: 1 вид - сценарии, которые определяются вручную, то есть, у вас прямо в пакете написано «при установке запускать такие-то программы, делать то-то и то-то». Как правило, это очень индивидуальные штуки, решения относительно которых принимает, собственно сопровождающий пакет. Какой-нибудь плагин к какому-нибудь редактору, который требует перенастройки системных конфигов этого редактора, наверняка про него знает только автор этого редактора, поэтому при установке этого плагина нужно запускать какую-то программу, которая будет этот конфиг перегенерировать.
- явные установочные сценарии
Но гораздо чаще встречается ситуация такая. Ваш пакет содержит библиотеку. Если он содержит библиотеку - все равно уже известно, что нужно запускать программу ldconfig, которая пересоздаст кэш библиотек. Ваш пакет содержит иконку. Уже известно, что нужно запускать обновлятель кэша иконок, чтобы ее было хорошо видно. Ваш пакет содержит документацию в формате .man, значит нужно перезапускать перегенерилку базы манов и еще чего-то. Таких ситуаций, на самом деле, для пакетов даже больше, чем ситуаций, когда вам явно нужно указать, где и что перегенерировать. Они называются триггерами, и означают запуск действий по типу и/или расположению файла. Это не имеет пярмого отношения к теме нынешнего семестра, но наличие триггеров, о которых договорились сопровождающие пакеты, очень сильно разгружают задачу сопровождающего пакета. Я сделал шрифт - я в общем не заморачиваюсь, согласно какой полисе эти шрифты регистрируются в нашей системе, я просто кладу шрифт. Тот факт, что этот шрифт повлечет за собою запуск соответствующего триггера, который будет перегенерировать соответствующие кэши. Я сейчас говорю про триггеры, потому что вы можете счесть тот факт, что установился пакет и внезапно от него иконка появилась в меню, некоторым чудом, потому что, вроде, никто ничего не делал, в пакете нигде не было написано «засандалить иконку в мое меню», а на самом деле это сделал триггер.
Что мы еще можем хотеть от пакетов? Вообще говоря, установочные сценарии - такая таблетка, с помощью которой все остальное, что мы можем хотеть от пакетов, решается, потому что мы пишем произвольный скрипт, который запускается и все хорошо. Вот. Но, пока мы формулировали эти свойства, которые мы хотим от пакетов, у нас наросло несколько свойств, которые при этом у них образуются. Во-первых, у нас появилось три понятия - установка, удаление, и, главное, обновление пакетов. Что это означает? Как явствует из предыдущего, это довольно непростая процедура, которая включает в себя разные хитрости. Это означает, что у нас должно быть понятие «установщик пакета» - отдельная программа такая, он же удалятель, он же обновлятель. Понятие «обновление» означает, что у нас, помимо просто установки, есть еще такая штука: удалить более старый пакет, установить более новый и ничего при том не поломать. И, кроме того, по всей видимости, хотя это лежит немножко в стороне, собственно, обновление означает, что у нас есть какая-то штука, которая сравнивает две версии пакета: какая-то меньше, какая-то больше, и поэтому вторая у нас идет вместо первой.
А, у нас еще есть конфликты и альтернативы. Смотрите: не все конфликты не нужны. То есть, может так случится, что у вас в операционной системе есть несколько программ, которые одинаково называются, но на самом деле являются различными бинарниками. Ну, какая-нибудь программа Sendmail: она нужна для того, чтобы осуществлять локальный мэйл-деливери и отсылку почты, у нее достаточно стандартные ключи и любой приличный почтовый сервер содержит в себе программу sendmail, более того, так уж получилось, что многие другие программы хотят именно, чтобы была программа sendmail, которая работает именно так, как вам нужно. Что получается: получается, что у вас конфликт по файлу, который невозможно разрешить, сказавши «а ты переименовывай», потому что, ну, любая из этих программ должна называться sendmail. Вот.
Или, что еще у нас есть из такого, часто встречающегося по части альтернатив. Например, дефолтная версия программы или библиотеки. Как я уже говорил, может быть такое, что в состав операционной системы будет входить несколько, как правило - 2, старая и новая, программ или библиотек с одинаковым именем, но разных версий, и тогда было бы очень неплохо договариваться о том, какая из этих программ воспринимается как программа просто, а какая - как программа.номер_версии. То есть, у нас есть несколько программ, которые называются одинаково, мы должны сделать так, чтобы иметь возможность их установить и просто решать, какую из альтернатив мы сейчас используем, это тоже свойство пакета. Вернемся к понятию обновления. Давайте договоримся о том, что у нас есть понятие установщик пакета. Это программа, которой мы даем файл с пакетом и говорим «установи этот пакет», или «установи этот пакет и удали более старый», или «удали мне пакет, который у тебя там в базе данных твоей зарегистрирован», то есть, все, что здесь написано, наш установщик отлично обслуживает:
В случае, если есть какая-то зависимость - он говорит «так, этот пакет нельзя установить, потому что есть зависимость», мы ему говорим «а, ну, установи два пакета, установи три пакета» и все хорошо. Давайте попробуем что-нибудь такое установить несложное. Для этого нам надо забраться на какой-нибудь сервер и прямо оттуда скачать пакет. Я как раз хочу показать, в чем скрытый смысл разделения инструментов на установщик и пакетный менеджер. Установщик занимается файлами: установить пакет из файла, удалить пакет, установленный в системе. И, фактически, установщик обслуживает все, что мы описали в понятии package. Нам ничего с точки зрения пакета больше не нужно. На самом деле, так исторически сложилось, что та же программа, которая устанавливает - она еще может собрать программу из исходных текстов. По сути, это другая задача, совсем, но, поскольку, скажем так, 30-20 лет назад пакеты чаще всего, все-таки, собирали из исходных текстов, чем запускали из бинарных, потому что бинарники в силу известных причин вряд ли по-человечески бы заработали на вашей машине - то это был такой инструмент, который делает и то и другое. Сейчас же я лично, наверное, ни разу не собирал пакет из исходных текстов в хост системе, вообще ни разу за последние несколько лет. Я это всегда делаю в сборочнице…
Вот мы, значит, берем какой-нибудь пакетик, ну, какой-нибудь поменьше, и попытаемся его установить. Устанавливаем мы его с помощью программы-установщика, который в случае ALT Linux называется RPM, вот.
Расшифровывается как Redhat Package Manager, но, во-первых, он не только Redhat, а во-вторых, он не package manager, поэтому не обращайте внимания. RPM обладает всеми свойствами установщика и поддерживает все, что мы сейчас захотели от пакета, за исключением - специально выделять конфигурационные сценарии RPM не умеет: предустановочные сценарии - сценарии, которые запускаются перед установкой пакета, постустановочные - после установки, предудалятельные, которые запускаются перед удалением пакета, например, удалить соответствующий сервис - вот это все есть, а конфигурационных сценариев в RPM нету. Ну, нету и нету.
Итак, у нас есть пакет:
Вот он сейчас пожужжит немножко, он жужжит диском, и скажет, что все хорошо. А пока он ставится, давайте я поставлю какой-нибудь пакет с зависимостью, а то он сейчас поставится и скажет, что все хорошо. А пока посмотрим, что еще умеет RPM. Умеет просматривать список пакетов, умеет выводить паспорт пакета, где написаны всякие интересные вещи.
Он умеет выводить зависимости пакета, естественно, он их знает. Зависимости существуют на пакет, на файл, на библиотеку. Тем временем, эта штука установилась. Ничего не сказала - внимание - это означает, что установка пакета произошла успешно: все произошло, но вы ничего не видели, и это значит, что теперь lftp у нас есть. Теперь попытаемся удалить пакет libpurple: он говорит - «не, не могу».
Все, как мы просили: установщик пакетов говорит «я не могу удалить пакет libpurple, потому что установлен pidgin». Окей, давайте удалим и pidgin тоже:
А, есть еще один пакет, который называется pidgin-libnotify, который без pidgin не работает. Вот теперь все три пакета удалить можно. Самое неприятное начинается после этого, потому что мы сейчас хотим, допустим, установить этот пакет. Хорошо, вот мы скачаем этот пакет какой-нибудь, да хоть тот же pidgin, тем временем, там все удалилось. Отлично! Давайте попробуем установить этот пакет. Как вы думаете, он установится? Ну, очевидно, нет, потому что ему еще нужно эти два было.
Неудовлетворенные зависимости, такие неудовлетворенные-неудовлетворенные. И вот тут мы понимаем, что что-то не так с нашим установщиком пакетов, потому что, смотрите, он говорит - а мне нужен еще libpurple! Я скачаю libpurple, он скажет - а мне нужно еще 100500 каких-нибудь библиотек! Я их скачаю, а он скажет - а мне нужно еще и еще! Вроде бы мы все, что нужно, все формальные требования выполнили, но, оказывается, понятие «установка-удаление» это не понятие, связанное с одним пакетом, это понятие, связанное с пакетной базой.
Давайте мы lftp поставим его с ключиком -vh:
Видали триггеры? Че-то такое происходит. Давайте посмотрим:
Никаких собственных установочных скриптов у него вообще нет. Все это делалось хуками.
Окей, давайте тогда вернемся к нашему требованию к пакетам и введем понятие «пакетный диспетчер».
С чем мы столкнулись, когда пытались пользоваться RPM для установки/удаления пакетов? С тем фактом, что нам нужно не только про один пакет знать, но и вообще про все, то есть, возникает новое понятие «хранилище пакетов», в котором хранится полная информация обо всех пакетах, которые есть, об их зависимостях и вообще обо всем. Соответственно, наш диспетчер должен уметь лазить в это хранилище. Виды зависимостей. Вы уже видели несколько видов зависимостей:
- Вид зависимости на пакет: для работы пакета нужен другой пакет
- Вид зависимости на файл: для работы пакета нужен конкретный файл, например, конкретный бинарник, а из какого пакета он приедет - не моего ума дела
- Вид зависимости на библиотеку (или модуль другого языка программирования) с конкретной версией или более тонкой настройкой, на самом деле, например, в качестве версии мы можем понимать список предоставляемых имен этой библиотекой
На произвольную сущность, на нечто: один пакет предоставляет нечто, provides почтовый сервер, второй говорит requires почтовый сервер.
Теперь, значит, относительно хранилища. Что должно представлять собой хранилище: во-первых, индекс всех пакетов, во-вторых - а собственно все, мы в следующий раз будем говорить о том, что технологически будет происходить в хранилище, а это уже совсем другая тема.
Что должен уметь диспетчер? Он должен уметь, например, составлять дерево зависимостей по пакетам, исходя из соображений в каком хранилище они лежат - дерево зависимостей. Граф, на самом деле, конечно же. Если мы говорим об обновлении, то эти зависимости он должен еще уметь принимать решения о том, какой пакет новый, а какой пакет старый, допустим, у нас более одного хранилища, мы видим сразу несколько пакетом различных версий - сравнение версий. Сравнение версий умеет также и установщик. Это дело установщика - сравнивать версии, иначе у нас будет двойная бухгалтерия. А вот вычисление множества пакетов, которые требуется установить или удалить по удалению/обновлению/установке - замыкание множества пакетов. Кстати, я еще одно вспомнил свойство довольно забавное связанное с установщиком, оно следует как раз из того, что обычно одна транзакция установки пакета, например, обновление системы - это не просто установка одного пакета вместо другого: это установка целой пачки пакетов вместо целой пачки других. Это свойство не обязательное установщика, но очень желательное. Представьте себе ситуацию: вы совершили довольно массовое обновление, у вас обновилось 500 пакетов и к каждому из них есть манпейдж. Сколько раз будет запущен хук, который обновляет кэш манпейджей? Понимаете, да? То есть, у вас должна быть возможность определения таких сценариев, которые на одну транзакцию выполняются один раз, а не много. То есть, понятно, да? При установке каждой библиотеки нужно каждый раз запускать ldconfig, но, по-хорошему, при установке 10 или 1000 библиотек нужно 1 раз запускать ldconfig. Как бы это назвать - отложенный, ленивый запуск идемпотентных триггеров. Помните, да, что такое идемпотентный? Идемпотентная операция - это такая операция, про которую не важно, сколько раз она применена, если она одинаковая. Ну, например, запись определенного значения в определенное место диска. Не важно, сколько раз вы запустили триггер при генерации манпейджа - 1, 2, 3, 10 - результат один и тот же.
На самом деле, чуть ли не главным свойством диспетчера пакетов является знание того, где на самом деле эти пакеты лежат и откуда их брать - доставка! RPM ничего не знает, откуда брать пакеты, если бы он знал - он бы их взял… Понимаете, да? Более того, вот в этот индекс, вообще говоря, входит соответствие файла, в котором лежит пакет, и настоящего названия пакет, который так и будет называться в базе данных. RPM даже этого не знает. Диспетчер пакетов, в отличии от установщика пакетов, как раз будет знать, как называется пакет. Что еще? Пока не знаю. Давайте попробуем воспользоваться этим диспетчером… Да, в ALT Linux традиционно используется RPM в качестве установщика, и advance package tool из дебиана в качестве диспетчера. Почему так случилось? Так случилось исторически, потому что к тому моменту, когда сообщество уже достаточно неплохо сложилось за первые два года существования альта, основным установщиком и таким главным установщиком был RPM, потому что в 2000-м году ALT был сделан из Mandrake, а Mandrake - в свою очередь Redhat-based, значит RPM. Но еще много лет спустя, лет 10 с тех пор, никакого нормального инструмента, который выполнял функции пакетного диспетчера кроме apt из дебиана просто не было, вот, поэтому вариантов не оставалось: либо все делать руками, либо использовать apt, и очень долго мы фактически были апстримом той части апта, который работает с RPM и вообще у нас был собственный апт в результате, сейчас, вроде бы, ведется большая работа, чтобы поддерживать только apt RPM-a, а сам apt использовать апстримный. Вот. Что касается апта - то у апта, собственно, его функции следующие: работа с хранилищами, составление индексов, составление замыканий, доставка, и, собственно, вызов установщика.
Вот. Как это выглядит в системе. В системе это выглядит примерно так: у вас есть достаточно простой настроечный файл, который лежит в каталоге
Вот он, он правда пустой, да, потому что есть по схеме .d сделанные подкаталоги. Смотрите: у вас может быть более одного хранилища подключено к вашей системе. Более того, у вас, скорее всего, подключено более одного хранилища, сейчас расскажу почему. Но и этого мало, допустим, базовое хранилище того дистрибутива, который вы используете, лежит на том конце земного шара, а вам хочется какое-нибудь более близлежащее использовать, тогда вы берете и создаете несколько разных конфигураций.. Вот, кстати, типичный пример: базовое хранилище на ftp.altlinux.org:
но у нас на факультете есть зеркало, вот оно:
Как устроено определение хранилища? Да очень просто: вы пишите, какого формата пакет, вы пишите, кем подписан пакет, это, кстати, важная вещь, когда не вы принимаете решение о доставке. Поскольку доставка, постольку и проверка подписи, аутентичность. Потому что так-то вы сам себе злобный буратино, скачали себе пакет черт знает откуда, установили его, он вам взорвался - ну, пожалуйста. А так вы сказали - я доверяю вон тому репозиторию и, пожалуйста, диспетчер, проверяй, что там. Установщик проверяет цельность пакета, а диспетчер проверяет аутентичность репозитория. Ну, вот. Дальше у вас идет адрес хранилища, дальше идет тип хранилища, и дальше идет раздел хранилища, ну, в принципе, это интересно, если у вас, например, есть несколько команд, которые работают над одним и тем же хранилищем, например, у вас есть часть мегазащищенная и вся остальная, значит мегазащищенная будет лежать в одном разделе, вся остальная - в другом. Или у вас entertainment-ориентированный дистрибутив и вы собираете страшные игрушки: одна занимает 1 гб, другая - 2 гб, и если пользователь не хочет им пользоваться этими игрушками, то он просто не подключает хранилище с игрушками. А в случае если вы дебиан, у вас есть очень строгое полиси относительно того, какие пакеты считаются свободными, а какие не очень свободными, если, допустим, их лицензия не совместима с идеологией дебиан, но при этом она допускает распространение пакетов из хранилища. В случае современного состояния дела какой-нибудь убунты - у вас есть Вася Пупкин, который говорит «я предоставляю вам самые лучшие в мире пакеты, подключайте мое хранилище и наступит вам счастье!», а вместо счастья вам наступит кирдык, когда Вася Пупкин перестанет предоставлять какие-то пакеты или просто про них позабудет, но, тем не менее, это довольно частая ситуация: вы заходите на сайт какого-нибудь производителя, там будет «о, а вот репа под убунту, тыкайте сюда!», и у вас в это место, или в похожее место, вписывается еще и хранилище, в котором хранятся конкретные пакеты от конкретного производителя. Мы в следующий раз поговорим, насколько это хорошо или плохо, а пока что, собственно, все. Какие есть 2 главных подразделения у пакетного диспетчера: это работа с самими хранилищами и работа с пакетами, которые берутся из этих хранилищ. Традиционно, правда, сейчас уже, кажется от этого в дебиане отказываются, apt состоял из двух кусочков: apt-get и apt-cache - уже отказались, потому что трудно вспомнить, что из них что, но, в принципе, понятно: все, что ходит наружу и залезает в хранилище и оттуда что-то скачивает, называется apt-get, а все, что связано с локальным кэшом - apt-cache. Традиционный метод - apt-get update, который зайдет на соответствующее хранилище, скачает оттуда все индексы, построит дерево, и у вас будет адекватное представление о том, какое содержимое пакетов лежит в вашем хранилище. Традиционные альтовые деревья строятся довольно долго в силу тонкого понятия зависимости. Теперь вы можете пользоваться программой apt-cache, которая по этому локально построенному дереву, которое содержит имя пакетов, паспорт пакетов, зависимости.. давайте попробуем че он требует:
Вот он вам выводит список всего, причем все дерево, что может потребоваться от этого вашего pidgina для установки. Теперь давайте установим: сначала его нужно скачать, а то, что качает - называется apt-get:
Видите, он ставит сразу два пакета. А давайте попробуем побольше поставить:
Во, видите, три пакета. То есть вот эта задача
а) определить дерево зависимостей
б) понять, откуда их забирать
в) скачать - вот она решается.
Обратите внимание на это сообщение, которое возникает. Нас предупреждают: у нас 3 пакета будет установлено а еще 56 пакетов не будет обновлено. Оказывается, за время, когда я все это в последний раз проделывал, в нашем хранилище p8 произошло 56 обновлений. И это, собственно, последнее, что я хотел сказать на сегодня относительно того, что делает диспетчер пакетов. Он занимается обновлением всей системы в целом: говорит, так, а ну-ка вычисли мне новые зависимости новых пакетов и если есть более новые версии пакетов поверх более старых - устанавливай. У это операции есть одна подводная грабля, большая и толстая: а что если в результате такого обновления понадобится пакеты УДАЛЯТЬ? Например, у вас обновился пакет, он требует новой версии библиотеки, а еще есть старый пакет, который требует старой версии библиотеки, и что делать? И он не обновился еще. Вы не можете поставить две одновременно версии библиотеки, если специально не разрулены конфликты в этом месте. Значит, какого-то из них нужно удалить. И вам об этом будет сообщено отдельно, что при таком-то обновлении будет обновлено вот это, а вот это будет удалено, потому что, ну, все - в текущих хранилищах для них уже нету соответствующих пакетов. Вы можете, конечно, полениться и сказать, так, удали только то, что не требует удаления, то, что называется apt-get upgrade. Не советую этого делать никогда, потому что у вас получится пакетный состав абсолютно уникальный, которого нет ни у кого в мире. У вас есть какие-то старые пакеты, новые пакеты, какие-то обновленные, какие-то не обновленные. Если при этом у вас что-нибудь перестанет работать - ну, кроме себя самого жаловаться будет некому. А вот если у вас перестанет работать текущая система, поставленная из текущей копии хранилища, особенно хранилища стабильного, где комьюнити следит за тем, чтобы все работало, то у вас будет кому пожаловаться. Так что правильная команда - apt-get dist-upgrade, только все равно читайте, что он вам предлагает, а то мало ли что.
Что он делает: сначала скачивает все эти пакеты, и потом тихонечко их ставит. И в конце запускаются триггеры.