Avalonia UI решает задачу кроссплатформенной разработки. Правильно написанный код без изменений работает под Windows, Linux и macOS. Сложности начинаются на следующем этапе — при подготовке приложения к распространению. Под Windows и macOS есть универсальные форматы инсталлеров, которые работают на всех версиях этих ОС. А вот с Linux ситуация иная. Экосистема Linux фрагментирована. Применяется несколько разных форматов упаковки приложений: deb, rpm. Применяются разные менеджеры пакетов. К этому добавляется проблема версий системных библиотек: сборка, работающая на одном дистрибутиве, может не запуститься на другом из-за иной версии glibc или отсутствия необходимой .so-библиотеки. В результате один и тот же бинарник Avalonia-приложения требуется упаковывать несколько раз, разными инструментами, с разными конфигурациями сборки, и каждую такую сборку затем отдельно тестировать, поскольку успешная сборка пакета не гарантирует его работоспособность у конечного пользователя.
Сегодня мы рассмотрим одно из решений этой проблемы — Flatpak. Поддержка Flatpak официально заявлена и документирована у крупных российских дистрибутивов:
-
Astra Linux (входит в базовый репозиторий начиная с версии 1.7)
-
РЕД ОС (отдельная статья в официальной базе знаний)
Поэтому для разработок, ориентированных на российский рынок, Flatpak — вполне подходящий вариант.
Flatpak версия “Delta Design Домашняя”
У нас недавно был релиз хоббийной версии Delta Design. Мы писали о нём в этой статье. Линукс версия “Delta Design Домашняя” получила унифицированный Flatpak инсталлер. Инсталлер в теории может работать на любой Linux-системе, которая поддерживает Flatpak, но мы в системных требованиях сейчас указываем только те дистрибутивы, на которых у нас запускаются автоматизированные тесты.
Как это устроено внутри
Чтобы не пересказывать документацию по Flatpak, рассмотрим только самые интересные места и проблемы, с которыми мы столкнулись. Если где-то будет непонятно — приходите в комментарии.
DeltaDesignHome представляет собой Avalonia-приложение, которое публикуется в CI в заданную папку.
dotnet publish DeltaDesignHome.Setup.slnf -bl -c Release --use-current-runtime --self-contained -o bin/PublishProduction
Далее эти бинарные файлы пакуются с помощью утилиты flatpak-builder. Рассмотрим файл манифеста для упаковки нашего приложения.
app-id: ru.eremex.deltadesignhomeruntime: org.gnome.Platformruntime-version: '49'sdk: org.gnome.Sdkcommand: delta-design-home-wrapper
app-id — уникальный идентификатор приложения в обратной нотации домена, по нему Flatpak различает пакеты, хранит для них отдельные каталоги с данными и настройками. runtime — это базовый образ, набор системных библиотек фиксированной версии, который Flatpak подкладывает приложению вместо того, чтобы полагаться на то, что установлено в системе. Он нужен для того, чтобы GUI-приложению было на чём работать: GTK или его аналоги, графический стек, базовые зависимости рабочего стола — всё то, что у десктопного приложения почти всегда есть под капотом, даже если оно написано на Avalonia и сама Avalonia это не использует напрямую. org.gnome.Platform — один из самых ходовых runtime’ов на Flathub, есть ещё org.kde.Platform и более минимальный org.freedesktop.Platform; на практике для конкретного приложения часто можно взять runtime, который уже использует похожее по типу приложение, и не подбирать состав зависимостей с нуля. sdk — то же самое, но с инструментами для сборки, нужен только на этапе flatpak-builder и в финальный пакет не попадает.
command — какой исполняемый файл запускать при старте приложения. В нашем случае это не сам бинарник DeltaDesignHome, а обёртка delta-design-home-wrapper — её назначение рассматривается далее.
finish-args: что разрешено снаружи песочницы
Это самая важная с точки зрения безопасности часть манифеста — по умолчанию Flatpak-приложение не видит почти ничего, кроме себя самого. Всё, что ему нужно от системы, нужно прописать явно.
В этом манифесте права разбиваются на четыре смысловые группы.
Системные ресурсы:
- --share=ipc- --share=network- --socket=pulseaudio- --socket=x11- --device=all
--share=network — выход в интернет. --socket=x11 — отрисовка окна на экране (без этого графическое приложение просто не сможет показать UI). --socket=pulseaudio — звук. --device=all — доступ ко всем устройствам, в том числе GPU; для CAD-системы с 3D-отрисовкой без него не заведётся аппаратное ускорение.
Здесь следует учитывать особенность, которая на практике приводит к проблемам при запуске: на момент написания статьи Avalonia-приложения используют только X11 (нативной поддержки протокола Wayland у фреймворка нет), поэтому достаточно одного --socket=x11 — в манифесте DeltaDesignHome указан именно он. Если же приложение поддерживает нативный Wayland и требуется доступ к обоим бэкендам, указывать --socket=x11 и --socket=wayland одновременно как два равноправных сокета не следует — это создаёт конфликт. Для такого случая в Flatpak предусмотрен отдельный флаг --socket=fallback-x11, который даёт доступ к X11 только если Wayland недоступен, и используется в паре с --socket=wayland. Корректная комбинация для приложения с нативной поддержкой обоих бэкендов выглядит так:
- --socket=fallback-x11- --socket=wayland
а не --socket=x11 плюс --socket=wayland напрямую.
Интеграция с рабочим столом:
- --talk-name=org.freedesktop.Notifications- --talk-name=org.gnome.Mutter.IdleMonitor- --talk-name=org.kde.StatusNotifierWatcher- --talk-name=com.canonical.AppMenu.Registrar
Notifications — системные уведомления, StatusNotifierWatcher — иконка в трее (KDE-вариант), AppMenu.Registrar — глобальное меню в духе Unity/Ubuntu. Здесь перечислены сервисы и для GNOME, и для KDE одновременно — приложение не знает заранее, в каком окружении его запустят, поэтому запрашивает права на оба варианта сразу.
Доступ к файлам:
- --filesystem=home- --talk-name=org.freedesktop.portal.FileChooser
Этот пункт требует отдельного рассмотрения. --filesystem=home — это разрешение на чтение и запись всего домашнего каталога пользователя, не только конкретных подпапок. Это широкое право, и в общем случае хорошей практикой считается запрашивать более узкие права, такие как --filesystem=xdg-documents. Но для CAD-приложения, где пользователь может открыть проект из произвольного места на диске, а не только из ~/Documents, это осознанный компромисс: иначе пользователю придётся каждый раз проходить через диалог выбора файла при открытии проекта вне стандартных папок.
Постоянное хранение данных:
- --persist=.local/share/ru.eremex.deltadesignhome/data- --persist=.config/ru.eremex.deltadesignhome- --filesystem=~/.local/share/ru.eremex.deltadesignhome/data:rw
--persist — без этого атрибута данные будут считаться частью песочницы и не будут сохраняться при переустановке приложения.
Модули: из чего собирается /app
Далее идёт modules — список того, что попадает внутрь финального пакета, в порядке выполнения. У каждого модуля свой buildsystem (тут везде simple — просто список shell-команд, никакой автоматической сборки из исходников) и свой набор sources.
custom-fonts — копирует шрифты в /app/share/fonts.
DeltaDesignHome — собственно само приложение. Берёт опубликованную (dotnet publish) сборку из ./bin/PublishProduction, плюс .desktop-файл, иконку, appdata.xml и тот самый delta-design-home-wrapper. Здесь же создаются стандартные каталоги Flatpak-пакета:
- mkdir -p /app/share/applications- mkdir -p /app/share/icons/hicolor/scalable
.desktop-файл и иконка кладутся по стандартным для Linux путям (applications, icons/hicolor) — именно по этой структуре система понимает, что появилось новое приложение, и рисует для него ярлык в меню.
Отдельного внимания заслуживает delta-design-home-wrapper — command в начале манифеста указывает именно на него, а не на сам бинарник DeltaDesignHome. Перед запуском основного UI требуется выполнить некоторую проверку или подготовку. Назначение этого скрипта рассматривается в отдельном разделе, посвящённом wrapper-скрипту. Сам .NET-бинарник Avalonia-приложения практически никогда не запускают напрямую как command в продакшен-манифестах — обёртка предоставляет точку, через которую можно изменить процесс старта, не затрагивая код самого приложения.
Автоматизированное тестирование инсталляции
Успешная сборка Flatpak-пакета не означает завершение работы. Манифест может скомпилироваться без единой ошибки и при этом приводить к неработающему приложению у пользователя: может не хватить права на нужный D-Bus интерфейс, persist может не подхватить каталог с данными после обновления, либо приложение может завершаться сразу после старта в чистом окружении, где отсутствуют необходимые шрифты. Таким образом, успешная сборка пакета не гарантирует его работоспособности — собранный артефакт необходимо проверять.
Для этого используются обычные MSTest-тесты — тот же фреймворк, что и для остальной кодовой базы на .NET. Идея состоит в следующем: тестовый набор запускается не на изолированном коде, а на уже установленном Flatpak-пакете, так что проверяется именно то, что в итоге получит пользователь — со всеми правами из finish-args, с тем же .NET runtime, что работает внутри песочницы, и с той же файловой структурой.
Процесс состоит из трёх шагов.
Сборка тестового проекта и копирование его внутрь установленного пакета. Flatpak-приложение после установки располагается в /var/lib/flatpak/app/<app-id>/current/active/files — это содержимое того самого /app, который собирался модулями в манифесте. Туда же, рядом со штатным бинарником, копируется собранный MSTest-проект:
cp artifacts/bin/DeltaDesign.E2E.InstallDDHome.Tests/release/DeltaDesign.E2E.InstallDDHome.Tests \ /var/lib/flatpak/app/ru.eremex.deltadesignhome/current/active/files/bin/
Отдельной упаковки теста в манифест не требуется — он оказывается там же, где и всё остальное содержимое пакета, и использует тот же .NET runtime, те же зависимости. Отметим, что Flatpak-пакет может быть установлен на уровне системы или в пользовательскую папку. Это влияет на путь к бинарным файлам Flatpak. В данном примере рассматривается первый вариант.
Запуск тестов через подмену команды, а не самого приложения. У flatpak run есть параметр --command, который переопределяет точку входа, заданную в манифесте полем command. Вместо запуска delta-design-home-wrapper напрямую вызывается тестовый бинарник:
flatpak run --command=DeltaDesign.E2E.InstallDDHome.Tests \ ru.eremex.deltadesignhome \ $TEST_ARGS --results-directory=$CI_PROJECT_DIR
Ключевой момент здесь — тесты выполняются внутри той же песочницы, что и сама программа. Если в finish-args чего-то не хватает (например, не указан --persist для каталога с данными), это будет выявлено уже на данном этапе, а не после релиза пакета. $TEST_ARGS — стандартные параметры MSTest (фильтры, конфигурация запуска), а --results-directory=$CI_PROJECT_DIR направляет отчёт о тестах в каталог CI-джобы, откуда его впоследствии можно прикрепить как артефакт пайплайна.
Получение отчёта в привычном формате. Поскольку используется MSTest, результаты выходят в том же формате (.trx и так далее), что и обычные unit-тесты проекта — отдельный парсер вывода под Flatpak не требуется, CI читает их так же, как любой другой тестовый прогон.
Практическое преимущество такого подхода: тест видит то же окружение, в котором будет работать приложение у пользователя. Если приложению не хватает прав на сеть, на конкретный D-Bus сервис или на каталог с данными, тест выявит это так же, как столкнулся бы с этим реальный пользователь — но на этапе CI, до релиза.
Адаптация Flatpak к российским реалиям: установка без интернета
Flatpak по умолчанию рассчитан на постоянное подключение к удалённому репозиторию, такому как Flathub. Пакет и его рантаймы скачиваются оттуда при установке. Однако это не означает, что Flatpak в принципе нельзя установить на изолированную машину.
flatpak create-usb — эта команда копирует приложение вместе со всеми его зависимостями (включая нужный runtime) в заданный каталог.
flatpak create-usb /media/user/FLATPAKS ru.eremex.deltadesignhome
В результате в заданной папке появляется полноценный мини-репозиторий — внутри него каталог .ostree/repo, такой же по структуре, как настоящий удалённый репозиторий.
Установка на машине без интернета выполняется следующей командой:
flatpak install --sideload-repo=/media/user/FLATPAKS/.ostree/repo flathub ru.eremex.deltadesignhome
Для корректной работы данного механизма необходимо учитывать ряд условий:
-
Remote должен быть настроен заранее.
--sideload-repoуказывает, откуда брать данные пакета, но сам факт наличия remote с именемflathub(или любым другим) на офлайн-машине должен быть обеспечен ещё до этой команды —create-usbпереносит содержимое пакетов, а не сам источник. -
Репозиторий должен быть подписан. Для офлайн-установки нужны GPG-подписи у репозитория и настроенный collection ID — и на стороне сервера, откуда исходно ставилось приложение, и в локальной конфигурации remote’а на офлайн-машине. Без этого Flatpak откажется доверять данным с носителя.
Таким образом, ограничение в виде закрытой сети не является препятствием для использования Flatpak. Процесс установки версии “Delta Design Домашняя” без интернета описан в этой статье.
Первый запуск вместо мастера установки
Ещё одна особенность, с которой мы столкнулись, — отсутствие мастера установки. Если приложение ранее распространялось через классический Windows-инсталлятор, пользователи привыкли к графической оболочке, в которой показывается EULA и настраиваются некоторые параметры установки. У flatpak install отсутствует какой-либо интерактивный мастер настройки. Команда либо загружает пакет и проверяет его подпись, либо разворачивает уже скачанные данные в /var/lib/flatpak — без диалогов, без мастера настройки, без возможности показать пользователю что-либо и запросить согласие непосредственно в процессе установки.
Таким образом, если приложению ранее требовалось показывать лицензионное соглашение или настраивать параметры подключения к базе данных при установке, эту логику необходимо переносить не на этап установки, а на этап первого запуска самого приложения. Технически это реализуется по следующей схеме:
-
При старте приложение проверяет, есть ли в его каталоге настроек (
$XDG_CONFIG_HOME, то есть~/.var/app/<app-id>/config) маркер-файл или запись в конфигурации, означающая, что первичная настройка уже выполнена. -
Если маркер отсутствует — это «первый запуск». Приложение отображает собственный диалог: с EULA, с настройкой подключения к БД, с любым другим содержимым, ранее реализованным мастером инсталлятора.
-
После того как пользователь прошёл этот шаг (принял лицензию, указал параметры), приложение записывает маркер в тот же
$XDG_CONFIG_HOMEи более не показывает этот экран.
Преимущества такого подхода: маркер располагается в персистентном каталоге пользователя, а не в /app, и поэтому сохраняется при обновлении пакета — пользователь не увидит EULA повторно после каждого flatpak update, если только не требуется показывать его снова при смене версии лицензии. Поскольку данная логика реализована в коде приложения, а не в отдельном скрипте установщика, она работает одинаково независимо от способа установки пакета — как через Flathub, так и офлайн через create-usb.
Рассмотрим подробнее delta-design-home-wrapper из манифеста — этот файл упоминался ранее при разборе поля command. Именно этот скрипт реализует логику первого запуска, описанную выше.
Принцип работы заключается в следующем: command в манифесте, а значит и Exec в .desktop-файле, указывает не на основной .NET-бинарник DeltaDesignHome, а на промежуточный bash-скрипт. При запуске приложения пользователем выполняется именно этот скрипт, который определяет дальнейшие действия.
Логика внутри минимальная:
#!/bin/shCONFIG_MARKER="$XDG_CONFIG_HOME/ru.eremex.deltadesignhome/initialized"if [ ! -f "$CONFIG_MARKER" ]; then # маркера нет - это первый запуск exec /app/bin/DeltaDesignHome firstrunelse # обычный запуск exec /app/bin/DeltaDesignHomefi
Проверка основана на наличии файла-маркера в $XDG_CONFIG_HOME — каталоге настроек, который физически располагается в ~/.var/app/ru.eremex.deltadesignhome/config и сохраняется при обновлениях пакета. Если маркер отсутствует, скрипт запускает основной исполняемый файл с дополнительным аргументом firstrun, и приложение, получив этот параметр при старте, отображает экран первичной настройки (EULA, параметры подключения к базе и так далее), а после прохождения пользователем этого шага создаёт файл-маркер. При следующем запуске обёртка обнаруживает наличие маркера и передаёт управление приложению без дополнительных параметров.
Переменные окружения
Если приложение использует переменные окружения хоста, следует учитывать, что внутри песочницы окружение отличается от окружения вне её. Часть переменных Flatpak действительно передаёт в приложение по умолчанию, если не указан флаг --clear-env, однако полагаться на это не следует — набор переданных переменных зависит от конкретной системы и версии Flatpak, и это не тот контракт, на который стоит ориентироваться в продакшен-приложении.
Если приложению для работы нужна конкретная переменная окружения — будь то путь к лицензионному серверу, флаг конфигурации, который раньше выставлялся в /etc/environment, или что-то специфичное для рендеринга — её нужно объявить явно в finish-args через --env:
- --env=VARIABLE_NAME=значение
Если переменных много или их значения нежелательно задавать непосредственно в манифесте (например, они различаются для разных сборок), у flatpak run есть runtime-альтернатива — flatpak override --env=VAR=VALUE <app-id>, которая сохраняет переопределение постоянно для конкретного пользователя без пересборки пакета. Однако для переменных, без которых приложение не может корректно запуститься, правильное место — именно finish-args в манифесте: тогда переменная будет установлена у любого пользователя сразу после установки, а не только у тех, кто отдельно выполнил override.
Заключение
Один унифицированный Flatpak-инсталлер обеспечивает установку пакета одинаковым образом на Ubuntu, Fedora и openSUSE, без отдельной работы с зависимостями и версиями системных библиотек для каждого дистрибутива. Для Avalonia-приложений этот подход особенно удобен, поскольку .NET-рантайм и необходимые нативные библиотеки упаковываются вместе с приложением и не зависят от того, что предустановлено в целевой системе.
У нас были опасения: не приведёт ли изоляция в песочнице к снижению производительности по сравнению с обычной установкой. На практике для рассматриваемого приложения заметного снижения производительности зафиксировано не было — ни при запуске, ни при работе. На более тяжёлых I/O-сценариях разница, по всей видимости, может проявляться сильнее, однако как общий аргумент против использования Flatpak довод о снижении производительности в данном случае не подтвердился.
Flatpak не подойдёт, если приложение должно работать в виде демона — классические deb/rpm будут удобнее. Также, если требуется компактный размер дистрибутива, лучше остаться на deb/rpm.
Во всех остальных случаях Flatpak существенно снижает издержки, связанные с фрагментацией Linux-экосистемы, и позволяет избежать поддержки отдельного пайплайна под каждый пакетный менеджер. Изолированная от интернета сеть также не является препятствием.
ссылка на оригинал статьи https://habr.com/ru/articles/1050648/