Устройство пакетной системы OpenWrt +45


Операционная система OpenWrt обычно используется как прошивка для роутеров. Типичное применение заключается в том, чтобы установить и забыть. Но если вдруг вам чего-то не хватит, то придётся разбираться в устройстве дистрибутива.



OpenWrt использует opkg в качестве пакетного менеджера, точнее, собственный форк. Дебианщикам он во многом покажется знакомым: похожие команды, похожий формат репозитория и пакетов.

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

Устройство репозитория


В файловой системе OpenWrt есть файл /etc/opkg/distfeeds.conf, в нём указывается системный (предоставленный разработчиками OpenWrt и opkg) список репозиториев. Собственные и сторонние репозитории можно указать в /etc/opkg/customfeeds.conf. Формат однострочный, состоит из трёх слов:

  1. src или src/gz, от этого зависит, будет качаться файл Packages или Packages.gz. Судя по коду, есть другие опции для первого слова, но я не нашёл репозиториев, для которых это было бы актуально. Несмотря на src в названии, это репозиторий для бинарных пакетов. Специального формата репозиториев для пакетов с исходным кодом, аналогичного тому, что используется в Debian/APT, у opkg не предусмотрено.
  2. Название репозитория или «фида» в терминологии opkg/OpenWrt.
  3. URL, внутри которого лежит файл Packages/Packages.gz.

При выполнении opkg update к URL через / добавляется Packages или Packages.gz, список пакетов и подписи сохраняются в /tmp/opkg-lists, файл называется соответственно второму слову в списке репозиториев. Отсюда два важных вывода:

  1. При перезагрузке кэш очистится. На встроенных системах вроде роутеров это абсолютно разумно.
  2. В /etc/opkg/customfeeds.conf можно оверрайдить системные фиды своими собственными, дав им такое же название. opkg ругнётся, но проглотит оверрайд, сложив нужный файлик вместо загруженного ранее.

Заодно будет загружен Packages.sig, в котором должен лежать хэш распакованного списка пакетов. В самом списке просто перечисляются пакеты, для каждого пакета есть несколько значений, значения для разных пакетов отделяются пустой строкой. Вот самые важные поля для каждого пакета:

  • Package, имя пакета;
  • Version, версия, при наличии нескольких пакетов с одинаковым именем можно выбрать версию, по умолчанию установится самая свежая;
  • Depends, зависимости от других пакетов, пакетный менеджер доустановит перечисленные пакеты в случае их отсутствия в системе;
  • Filename, путь к файлу относительно базового URL репозитория, обычно репозиторий плоский и всё лежит там же, где и `Packages.gz`;
  • SHA256sum, заявленный репозиторием хэш пакета.

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

Бинарные пакеты


Бинарные пакеты почти аналогичны пакетам Debian. Разница следующая:

  1. Расширении .ipk вместо .deb.
  2. Упаковывается всё с помощью `tar` и сжимается с помощью gzip, это же справедливо для вложенных архивов. В Debian архив верхнего уровня упаковывается более примитивным ar, а вложенные архивы чаще всего имеют расширение .tar.xz, инструменты используются соответствующие.

Если вы поменяете расширение пакета для OpenWrt на .tar.gz и распакуете, то обнаружите внутри два архива и один текстовый файл. Файл называется debian-version, в нём содержится версия формата бинарного файла и она нам не очень интересна, в современных системах эта версия всегда равняется 2.0.

Архив data.tar.gz содержит исполняемые файлы, файлы конфигурации и всё, ради чего устанавливается пакет. Если распаковать его в корень ФС, вы получите все ожидаемые файлы на нужных местах, в /usr/bin/, /etc/ и так далее.

А в control.tar.gz находятся вспомогательные файлы для пакетного менеджера. Это скрипты, которые должны выполняться до или после установки и удаления (preinst, postinst, prerm, postrm), сведения о файлах, являющихся конфигурационными, и метаинформация о пакете, во многом повторяющая ту, что содержится в Packages.

Система сборки пакетов


Сборочная система (она же SDK) выполнена в виде Make-фреймворка. Фреймворк не подразумевает, что вы будете собирать пакеты по отдельности, его основная задача заключается в сборке целых репозиториев.

SDK для x86_64 лежит в git. Есть архив (ссылка скоро устареет, но найти свежий несложно), который сэкономит вам время на компиляции тулчейна для сборки. Внутри особый интерес представляет файл feeds.conf.default. Формат несложный, через пробел:

  1. Ключевое слово src-git. Поддерживается не только git, но сейчас репозиториев в иных VCS нет.
  2. Название фида.
  3. URL git-репозитория, в котором можно указать коммит или тег. Если вы знаете, как называется такая спецификация, подскажите, пожалуйста.

Сам репозиторий с пакетами устроен максимально просто: в корне репозитория категория пакета, на втором уровне директория с названием пакета, а внутри него лежит Makefile, опционально `Config.in` для дополнительных опций при выполнении make menuconfig и субдиректория patches с соответствующим содержимым. Для простейшего пакета достаточно только Makefile. Для примера можете заглянуть в зеркало основного репозитория.

Тестовая сборка


Я попробовал собрать GNU Hello, чтобы проверить, как работает SDK. Это сравнительно монструозный Hello World, написанный в строгом соответствии с гайдлайнами проекта GNU, его единственная задача заключается в иллюстрации этих гайдлайнов. Отдельный репозиторий для него не создавал, а вместо этого «подсунул» в базовые пакеты SDK, откуда и скомпилировал.

Для работы самого SDK в окружениии Debian понадобятся пакеты libncurses-dev (для меню сборки), build-essential (GCC и прочие стандартные зависимости программ на C), gawk, unzip, file, rsync и python3. Также для создания репозитория из собранных пакетов, потребуется утилита для генерации ключей usign. Её в репозитории нет, поэтому дополнительно потребуется `cmake` для сборки. Этот инструмент можно заменить как на GPG, так и на signify-openbsd, но она рекомендуется и разрабатывается проектом OpenWrt, а также гораздо приятней в использовании.

Компилируем и устанавливаем usign:

git clone https://git.openwrt.org/project/usign.git
cd usign
cmake .
make
sudo make install 

Вместо установки (sudo make install) можете просто запомнить, где находится бинарь, чтобы в дальнейшем дёргать его руками.

Теперь базовая настройка SDK:

git clone https://git.openwrt.org/openwrt/openwrt.git
cd openwrt
./scripts/feeds update -a
./scripts/feeds install -a

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

Выполняя ./scripts/feeds update -a мы клонируем/обновляем все репозитории из feeds.conf(.default), проверяем зависимости и готовим директорию staging_dir/host/bin с исполняемыми файлами (в основном это симлинки на системные утилиты). Следующая команда, ./scripts/feeds install -a, рассовывает симлинки в package/feeds, откуда они и будут браться для компиляции. Эти две команды не обязательны для сборки моего кастомного пакета.

Далее выполняется make menuconfig. Можно пропустить, но при компиляции пакета всё равно выдаст соответствующее окошко. В нём достаточно поменять таргет и сабтаргет, чтобы всё скомпилировалось под x86_64 и выйти, согласившись с сохранением конфига. Также потребуется собрать вспомогательный инструментарий для сборки (make tools/install) и тулчейн (make toolchain/install). Если вы качали SDK из архива, то make menuconfig вам не покажет опций для выбора таргета, а сборка инструментария и тулчейна не требуется — всё уже есть на месте.

Теперь я создаю директорию package/devel/hello, в которой размещаю Makefile следующего содержания:

Makefile
include $(TOPDIR)/rules.mk

PKG_NAME:=hello
PKG_VERSION:=2.9
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0-or-later

PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=@GNU/hello/
PKG_HASH:=ecbb7a2214196c57ff9340aa71458e1559abd38f6d8d169666846935df191ea7

include $(INCLUDE_DIR)/package.mk

define Package/hello
        SECTION:=devel
        CATEGORY:=Development
        TITLE:=GNU Hello
        URL:=https://www.gnu.org/software/hello/
endef

define Package/hello/description
        The GNU Hello program produces a familiar, friendly greeting. Yes,
        this is another implementation of the classic program that prints
        “Hello, world!” when you run it. However, unlike the minimal version
        often seen, GNU Hello processes its argument list to modify its
        behavior, supports greetings in many languages, and so on. The primary
        purpose of GNU Hello is to demonstrate how to write other programs that
        do these things; it serves as a model for GNU coding standards and GNU
        maintainer practices.
endef

define Package/hello/install
        $(INSTALL_DIR) $(1)/usr/bin
        $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/hello $(1)/usr/bin/
endef

$(eval $(call BuildPackage,hello))

В основном всё должно быть понятно без пояснений. Подключаются файлы фреймворка, описываются основные параметры пакета, @GNU подменяется на зеркала проекта GNU (определены во фреймворке), а путь состоит из двух частей: PKG_SOURCE_URL, в котором указывается базовый URL для всех версий и расширяется конкатенацией именем файла из PKG_SOURCE через слэш. В Package/hello/install содержатся инструкции по сборке бинарей в архив data.tar.gz. Дополнительные опции для сборки, если потребуются, доступны в документации. Кстати, не забудьте, что make очень требователен к отступам, у меня вместо начальных пробелов были одиночные табы.

Снова вызываете make menuconfig, проверяете, что в обозначенной секции (Development в моём случае) отмечен пакет hello и выходим сохранив конфиг. Наконец, собираем пакет в три этапа; скачивание, распаковка и собственно компиляция:

make package/hello/download
make package/hello/prepare
make package/hello/compile

В результате я получил пакет bin/packages/x86_64/base/hello_2.9-1_x86_64.ipk. Можно собирать репозиторий. Генерируем пару ключей (usign -G -c 'openwrt test repo' -s key-build -p key-build.pub, приватный ключ обязательно должен называться `key-build`), и собираем репозиторий: make package/index. На этом этапе сборка может ругнуться на отсутствие usign в директории со вспомогательными утилитами, я решил проблему симлинком: ln -s `which usign` staging_dir/host/bin/usign. Теперь рядом с пакетом лежит полный набор, необходимый для репозитория.

Проверяем репозиторий вместе с пакетом


Вы можете проверить всё на настоящем роутере (не забудьте только выбрать правильный таргет), но я воспользовался Докером. В Докерхабе есть образ OpenWrt для x86_84, который можно запустив, пробросив внутрь контейнера директорию с SDK: sudo docker run -it --name openwrt_test -v $PWD:/opt openwrtorg/rootfs. Потыкайте кнопку ввода пока не появится приглашение Баша.

Копирую ключ из проброшенной директории (cp /opt/key-build.pub /etc/opkg/keys/usign -F -p /opt/key-build.pub, название ключа обязательно должно совпадать с идентификатором), добавляю свой локальный репозиторий (echo src/gz local file:///opt/bin/packages/x86_64/base >> /etc/opkg/customfeeds.conf), обновляю репозиторий (opkg update). Вывод начинается с обнадёживающего текста, всё подписано верно:

# opkg update
Downloading file:///opt/bin/packages/x86_64/base/Packages.gz
Updated list of available packages in /var/opkg-lists/local
Downloading file:///opt/bin/packages/x86_64/base/Packages.sig
Signature check passed.

Осталось только установить и проверить:

root@34af2f6e849b:/# opkg install hello
Installing hello (2.9-1) to root...
Downloading file:///opt/bin/packages/x86_64/base/hello_2.9-1_x86_64.ipk
Configuring hello.
root@34af2f6e849b:/# hello
Hello, world!

Ура, готово! Несмотря на разбросанную по статьям документацию, процесс сборки пакетов довольно прост.






К сожалению, не доступен сервер mySQL