Skip to content

Add Linux packaging: deb, rpm, archlinux and a reproducible build path - #152

Open
jidckii wants to merge 6 commits into
ergohaven:mainfrom
jidckii:feat/linux-packages
Open

Add Linux packaging: deb, rpm, archlinux and a reproducible build path#152
jidckii wants to merge 6 commits into
ergohaven:mainfrom
jidckii:feat/linux-packages

Conversation

@jidckii

@jidckii jidckii commented Aug 16, 2026

Copy link
Copy Markdown

Первый из серии небольших PR, на которые разрезан #143. Здесь только Linux: точка входа сборки, воспроизводимый контейнер и нативные пакеты. release.yml не тронут, src/ не тронут, публикации пакетов пока нет — она приедет отдельным PR после того, как Windows и macOS переедут в этот же flow.

Ни одного из четырёх блокеров из ревью #143 этот PR не касается: фильтр тегов остаётся v0.*, имена macOS-артефактов не меняются, Inno Setup на месте, релизные пути не трогаются.

RU

Что появляется

  • task linux:all.deb, .rpm, .pkg.tar.zst (nfpm) и AppImage в dist/linux/.
  • task docker:linux — те же четыре артефакта из пиннутого образа, на любом хосте с Docker и без единого установленного инструмента.
  • task prepare — ставит необходимое на текущем хосте; знает пакетные менеджеры Debian/Ubuntu, openSUSE, Fedora, Arch и Alpine.

Пакет несёт бинарник, .desktop, AppStream-метаданные, udev-правило и иконки hicolor. AppImage раздаёт ровно те же файлы, поэтому приложение, поставленное пакетом, и оно же из AppImage описывают себя одинаково — раньше AppImage генерировал .desktop инлайном и нёс синий плейсхолдер вместо логотипа.

Почему Docker здесь, а не отдельным шагом

Сам по себе Taskfile — обёртка над cargo build, проверять в нём нечего. Вместе с контейнером и пакетами он даёт одно законченное проверяемое утверждение: Linux-артефакты собираются одинаково у разработчика и в CI. Разрезать это ещё мельче — значит мержить две половины, ни одна из которых сама по себе не работает.

Зависимости пакетов выписаны руками

У бинарника ровно три ELF-зависимости — libc, libm и libgcc_s. Весь GUI-стек (libGL, xkbcommon, X11/xcb, wayland) грузится через dlopen, hidapi собран с чисто растовым hidraw-бэкендом вместо libudev, а файловые диалоги идут через xdg-desktop-portal, а не GTK. Автоопределение объявило бы почти пустой список, и пакет ставился бы, но не запускался.

Для rpm зависимости объявлены провайдами по soname (libX11.so.6()(64bit)), а не именами пакетов: имена разъезжаются между дистрибутивами (libX11 в Fedora против libX11-6 в openSUSE), soname — нет.

Иконки

assets/icons/hicolor/ раскладывается из уже лежащего в репозитории assets/entropy.ico и коммитится рядом с ним, поэтому упаковка не требует никакого графического тулинга. В .ico есть отрисованные вручную варианты 16, 32 и 48 — они копируются как есть, ресайзом из кадра 256 добираются только недостающие размеры. Перегенерация — task icons, при смене логотипа.

Что меняется в существующих файлах

  • scripts/build_linux_appimage.sh — переиспользует общие .desktop, metainfo и иконки; кэш инструмента переехал в .cache/tools (общий с nfpm, переживает cargo clean). Логика проверки и загрузки appimagetool не тронута: три теста из Verify AppImage packaging tool downloads #111 проходят без единой правки в самих тестах.
  • Оттуда же убран libudev.so.1, который копировался с хоста сборки и подставлялся через LD_LIBRARY_PATH. Бинарник его не линкует, а LD_LIBRARY_PATH перебивает системные библиотеки у всего, что подгрузится позже, — чистый риск ABI без выгоды.
  • build.yml — добавлены шаги сборки пакетов в существующую Linux-джобу и отдельная джоба на контейнер. Матрица, имена джоб и шаг с тестами Verify AppImage packaging tool downloads #111 не тронуты.

Проверено

  • task clean && task linux:all и task docker:linux с нуля: все четыре артефакта, содержимое каждого проверено scripts/test_linux_packages.sh.
  • После контейнерной сборки все файлы принадлежат вызвавшему пользователю, root-owned мусора не остаётся.
  • desktop-file-validate и appstreamcli validate проходят (единственное замечание — pedantic-уровня releases-info-missing; секцию <releases> имеет смысл генерировать из CHANGELOG.md, это отдельная задача).
  • Пины nfpm, go-task и appimagetool сверены с официальными checksums.txt; путь загрузки nfpm прогнан вживую.
  • shellcheck по всем скриптам чистый, cargo test — 565 тестов.

Не проверено: установка .deb на живой Debian/Ubuntu — это делает CI отдельным шагом, apt-get install ./*.deb. Именно он проверяет, что руками выписанные Depends существуют в архиве и разрешаются; распаковка такого не ловит.

На решение мейнтейнеров

maintainer: в packaging/nfpm/nfpm.yaml сейчас Ergohaven <ergohaven@users.noreply.github.com> — для deb это обязательное поле, и адрес стоит заменить на реальный.

EN

The first of the small PRs #143 is being split into. Linux only: the build entry point, a reproducible container and native packages. release.yml untouched, src/ untouched, no package publishing yet — that comes in a later PR, once Windows and macOS have moved into the same flow.

None of the four blocking points from the #143 review are touched here: the tag filter stays v0.*, macOS artifact names are unchanged, Inno Setup stays, and no release path is modified.

What lands

  • task linux:all.deb, .rpm, .pkg.tar.zst (nfpm) and the AppImage in dist/linux/.
  • task docker:linux — the same four artifacts from a pinned toolchain image, on any host with Docker and nothing else installed.
  • task prepare — installs the prerequisites for the current host across the Debian/Ubuntu, openSUSE, Fedora, Arch and Alpine package managers.

Packages ship the binary, the .desktop entry, AppStream metainfo, the udev rule and the hicolor icons. The AppImage now ships exactly the same files, so an app installed from a package and one launched from the AppImage describe themselves identically — previously the AppImage generated its .desktop inline and carried a blue placeholder instead of the logo.

Why Docker rides along

A Taskfile on its own is just a wrapper around cargo build — there is nothing to verify in it. Together with the container and the packages it makes one complete, testable claim: Linux artifacts build the same way locally and in CI. Splitting it further would mean merging two halves, neither of which works alone.

Dependencies are declared by hand

The binary has exactly three ELF dependencies — libc, libm and libgcc_s. The whole GUI stack is loaded through dlopen, hidapi uses its pure-Rust hidraw backend instead of libudev, and file dialogs go through xdg-desktop-portal rather than GTK. Auto-detection would declare almost nothing and the package would install and then fail to start. The rpm dependencies use soname provides rather than package names, because those names differ between rpm distros while the sonames do not.

Icons

assets/icons/hicolor/ is generated from the already-committed assets/entropy.ico and committed next to it, so packaging needs no image tooling at all. The .ico carries hand-drawn 16, 32 and 48 pixel variants; those are copied as-is and only the missing sizes are scaled from the 256 pixel frame. Regenerate with task icons when the logo changes.

Changes to existing files

scripts/build_linux_appimage.sh reuses the shared desktop entry, metainfo and icons, and its tool cache moves to .cache/tools. The appimagetool verification and download logic is untouched — the three tests from #111 pass with no changes to the tests themselves. The bundled libudev.so.1 and its LD_LIBRARY_PATH are dropped: the binary does not link it, and forcing host libraries ahead of system ones is an ABI risk with no upside. In build.yml the matrix, job names and the #111 test step are unchanged; only new steps and one new job are added.

Validation

Full task linux:all and task docker:linux from scratch, contents of all four artifacts asserted by scripts/test_linux_packages.sh, no root-owned files left after the container run, desktop-file-validate and appstreamcli validate clean, every tool pin checked against the upstream checksums.txt, shellcheck clean, cargo test green (565 tests). Installing the .deb on a real Debian/Ubuntu is done by CI via apt-get install ./*.deb — that step is what proves the hand-written Depends actually resolve.

Related: #143

Единый источник описания приложения для всех Linux-форматов: .desktop,
AppStream-метаданные, udev-правило и набор иконок hicolor. Конфиг nfpm
собирает из них deb, rpm и archlinux.

Зависимости выписаны руками, а не определяются автоматически. У бинарника
ровно три ELF-зависимости — libc, libm и libgcc_s: весь GUI-стек (libGL,
xkbcommon, X11/xcb, wayland) грузится через dlopen, hidapi собран с чисто
растовым hidraw-бэкендом вместо libudev, а файловые диалоги идут через
xdg-desktop-portal, а не GTK. Автоопределение объявило бы почти пустой
список, и пакет ставился бы, но не запускался.

Для rpm зависимости объявлены провайдами по soname (libX11.so.6()(64bit)),
а не именами пакетов: имена разъезжаются между дистрибутивами (libX11 в
Fedora против libX11-6 в openSUSE), soname — нет.

Иконки hicolor раскладываются из assets/entropy.ico скриптом
generate_icons.sh и коммитятся рядом с ним, поэтому упаковка не требует
никакого графического тулинга. В .ico есть отрисованные вручную варианты
16, 32 и 48 — они копируются как есть, ресайзом из кадра 256 добираются
только недостающие размеры.

Правило udev ставится в /usr/lib/udev и не конфликтует с копией в
/etc/udev, которую пишет linux/udev/install-vial-rules.sh для сборок из
исходников: копия в /etc по-прежнему имеет приоритет.
`task linux:all` собирает deb, rpm, archlinux и AppImage; `task prepare`
ставит всё необходимое для этого на текущем хосте, зная пакетные менеджеры
Debian/Ubuntu, openSUSE, Fedora, Arch и Alpine.

Метка архитектуры в пакете — обещание пользователю, поэтому PKG_ARCH
следует хосту, а не считается равным amd64, и сверяется с бинарником через
readelf. Пакет с меткой amd64 и arm64-бинарником внутри ставится и не
запускается, так что расхождение роняет сборку.

VERSION по умолчанию берётся из Cargo.toml и переопределяется снаружи —
это понадобится релизному workflow, чтобы имена артефактов совпадали с
тегом, а не с ещё не поднятой версией крейта. Разбор Cargo.toml сделан
средствами самого shell: встроенный интерпретатор go-task внешние утилиты
не подменяет, и с awk/sed весь Taskfile не читался бы на хосте без них.

nfpm скачивается в .cache/tools и сверяется по SHA-256 из tool_pins.sh, а
не ставится в систему; PATH дополняется только на время команды, и уже
установленный у разработчика nfpm имеет приоритет.

Бинарник стажируется в target/nfpm/entropy, а не подставляется в конфиг:
nfpm раскрывает ${...} в скалярных полях, но не в contents.src, поэтому
альтернативой был бы генерируемый конфиг и зависимость от envsubst.
AppImage описывал себя сам: .desktop и иконка генерировались скриптом
инлайном, причём иконка была синим плейсхолдером, а не логотипом Entropy.
Из-за этого приложение, поставленное пакетом, и оно же, запущенное из
AppImage, выглядели и назывались по-разному.

Теперь берутся те же packaging/linux/entropy.desktop, metainfo и иконки
hicolor, что уходят в deb/rpm/arch. X-AppImage-Version дописывается к копии
в корне AppDir — это метаданные самого формата, и в пакетах им не место.

Заодно убран libudev.so.1, который копировался с хоста сборки и
подставлялся через LD_LIBRARY_PATH. Бинарник его не линкует (hidapi собран
с растовым hidraw-бэкендом), а LD_LIBRARY_PATH перебивает системные
библиотеки у всего, что подгрузится позже, — то есть чистый риск ABI без
выгоды.

Кэш инструмента переехал из target/tools в .cache/tools: там же лежит
nfpm, и каталог переживает cargo clean. Тесты из ergohaven#111 задают APPIMAGETOOL
явно и проходят без изменений.
PR-гейт теперь собирает все четыре артефакта тем же путём, которым их
собирает разработчик, и проверяет результат.

test_linux_packages.sh сверяет, что каждый из трёх форматов действительно
несёт бинарник, .desktop, AppStream-метаданные, udev-правило, иконки и
лицензию: ошибки такого рода иначе всплывают только после установки на
живой системе. .deb приходится распаковывать в два прохода — это ar-архив,
и libarchive показывает у него только data.tar/control.tar.

Отдельным шагом .deb ставится по-настоящему через apt-get. Только это
проверяет, что руками выписанные Depends существуют в архиве Ubuntu и
разрешаются; распаковка такого не ловит.

Собранные пакеты выкладываются артефактом сборки, чтобы их можно было
скачать из PR и поставить у себя.
BUILD.md описывает задачи go-task, состав пакета, откуда берутся
зависимости и почему они выписаны руками, а также поведение версий:
предрелизный суффикс nfpm разворачивает в 0.3.10~rc.1 для deb и rpm, но у
pacman тильды нет, и Arch-пакет получает 0.3.10rc.1 — то есть считается
новее стабильного релиза. Это стоит знать до публикации Arch-пакетов для
release candidate.

README получает короткую врезку в разделе Development со ссылкой на
BUILD.md. Списки загрузок и таблица платформ не тронуты: релизный workflow
пакеты пока не публикует.
`task docker:linux` собирает те же deb, rpm, archlinux и AppImage на любом
хосте с Docker и без единого установленного инструмента. Задачи внутри
контейнера те же самые, что запускает разработчик, — образ фиксирует
тулчейн, а не подменяет путь сборки.

Базовый образ пиннится дайджестом, а не тегом: rust:1.97-bookworm
пересобирается апстримом, и без дайджеста один и тот же Dockerfile давал бы
разный тулчейн в разные дни. nfpm, go-task и appimagetool берутся из тех же
пинов, что использует хостовая подготовка, поэтому два пути не разъезжаются.

Контейнер запускается от UID хозяина каталога: иначе всё созданное внутри
остаётся root:root, и следующая локальная задача падает на правах. По той
же причине образ удаляет за собой root-only кэш cargo-реестра. На
SELinux-системах добавляется --security-opt label=disable — каталог
репозитория помечен user_home_t, и контейнер его иначе не прочитает.

CI получает отдельную джобу, которая гоняет ровно `task docker:linux` и
проверяет содержимое собранных пакетов: без неё образ был бы кодом, который
никто не выполняет.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant