Приложение на Electron#

Electron-приложение приходит от поставщика собранным: движок Chromium, Node и код приложения лежат в одном каталоге. При упаковке нужно разложить этот каталог в /app и запустить движок так, чтобы внутри песочницы Flatpak заработала его собственная изоляция.

Почему нужна обёртка#

Chromium изолирует свои процессы через вспомогательный бинарник chrome-sandbox с битом setuid. В песочнице Flatpak setuid запрещён, поэтому движок либо не стартует, либо работает без внутренней изоляции.

Обходится это обёрткой zypak: она подменяет собой вспомогательный бинарник и переводит запуск дочерних процессов на механизмы, разрешённые в песочнице. Обёртка приезжает с базовым приложением org.astra.Electron.BaseApp. Движок базовое приложение не содержит: Electron приносит приложение, а базовое приложение даёт только обёртку и вспомогательные обвязки.

Чем собирать#

Базовое приложение подключается ключом base, а его понимает flatpak-builder. У aft-app такого поля нет, поэтому Electron-приложения собираются flatpak-builder. Про сам инструмент - в блоке Сборка с помощью flatpak-builder.

Подготовка:

sudo flatpak install astra-main org.astra.Gtk//1.8.6
sudo flatpak install astra-extended org.astra.gtkSdk//1.8.6
sudo flatpak install astra-extended org.astra.Electron.BaseApp//1.8.6

Что приходит от поставщика#

Внутри поставки, независимо от того, deb это или архив, лежит каталог примерно такого вида:

Messenger/
|-- messenger                    исполняемый файл движка
|-- chrome-sandbox               вспомогательный бинарник, работать не будет
|-- chrome_crashpad_handler
|-- libEGL.so, libGLESv2.so, libffmpeg.so, libvk_swiftshader.so
|-- icudtl.dat, resources.pak, snapshot_blob.bin, v8_context_snapshot.bin
|-- locales/
`-- resources/
    `-- app.asar                 код приложения

Посмотреть содержимое, не распаковывая:

dpkg -c messenger_5.2.0_amd64.deb
tar -tf messenger-5.2.0-linux-x64.tar.xz

В deb-пакетах такой каталог обычно лежит в /opt/<Название>, а рядом приезжают usr/share/applications/<имя>.desktop и иконки в usr/share/icons/hicolor/<размер>/apps/<имя>.png. Именно на это дерево рассчитан манифест ниже. Если у поставки другая раскладка, меняются только пути в командах.

Дерево проекта сборки#

messenger/
|-- ru.example.Messenger.yml
|-- messenger.sh
|-- ru.example.Messenger.metainfo.xml
`-- messenger_5.2.0_amd64.deb

Скрипт запуска messenger.sh:

#!/bin/bash
export TMPDIR="${XDG_RUNTIME_DIR}/app/${FLATPAK_ID}"
exec zypak-wrapper /app/Messenger/messenger "$@"

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

Описание ru.example.Messenger.metainfo.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
  <id>ru.example.Messenger</id>
  <metadata_license>CC0-1.0</metadata_license>
  <project_license>LicenseRef-proprietary</project_license>
  <name>Messenger</name>
  <summary>Пример упаковки Electron-приложения</summary>
  <description>
    <p>Готовая поставка Electron, упакованная во Flatpak.</p>
  </description>
  <launchable type="desktop-id">ru.example.Messenger.desktop</launchable>
</component>

Манифест#

ru.example.Messenger.yml:

id: ru.example.Messenger
base: org.astra.Electron.BaseApp
base-version: '1.8.6'
runtime: org.astra.Gtk
runtime-version: '1.8.6'
sdk: org.astra.gtkSdk
command: messenger
separate-locales: false

finish-args:
  - --share=ipc
  - --share=network
  - --socket=wayland
  - --socket=fallback-x11
  - --socket=pulseaudio
  - --device=dri
  - --filesystem=xdg-download
  - --talk-name=org.freedesktop.Notifications
  - --talk-name=org.freedesktop.secrets
  - --talk-name=org.kde.StatusNotifierWatcher
  - --system-talk-name=org.freedesktop.login1

modules:
  - name: messenger
    buildsystem: simple
    build-commands:
      - dpkg-deb -x messenger.deb unpack
      - mkdir -p ${FLATPAK_DEST}
      - mv unpack/opt/Messenger ${FLATPAK_DEST}/Messenger
      - rm -f ${FLATPAK_DEST}/Messenger/chrome-sandbox
      - install -Dm0755 messenger.sh ${FLATPAK_DEST}/bin/messenger
      - install -Dm0644 unpack/usr/share/applications/messenger.desktop
          ${FLATPAK_DEST}/share/applications/${FLATPAK_ID}.desktop
      - sed -i "s|^Exec=.*|Exec=messenger %U|"
          ${FLATPAK_DEST}/share/applications/${FLATPAK_ID}.desktop
      - sed -i "s|^Icon=.*|Icon=${FLATPAK_ID}|"
          ${FLATPAK_DEST}/share/applications/${FLATPAK_ID}.desktop
      - |
        for s in 16 32 48 64 128 256 512; do
          src="unpack/usr/share/icons/hicolor/${s}x${s}/apps/messenger.png"
          if [ -f "$src" ]; then
            install -Dm0644 "$src" \
              "${FLATPAK_DEST}/share/icons/hicolor/${s}x${s}/apps/${FLATPAK_ID}.png"
          fi
        done
      - install -Dm0644 ru.example.Messenger.metainfo.xml
          ${FLATPAK_DEST}/share/metainfo/${FLATPAK_ID}.metainfo.xml
    sources:
      - type: file
        path: ./messenger_5.2.0_amd64.deb
        dest-filename: messenger.deb
      - type: file
        path: ./messenger.sh
      - type: file
        path: ./ru.example.Messenger.metainfo.xml

Распаковка идёт через dpkg-deb, который есть в SDK Astra. Готовых утилит bsdtar и desktop-file-edit, которыми пользуются манифесты Flathub, в SDK нет, поэтому ключи desktop-файла правятся sed.

Файл chrome-sandbox удаляется: с потерянным битом setuid он всё равно неработоспособен, а его наличие сбивает движок с толку. Изоляцию обеспечивает обёртка.

Если поставка приходит архивом#

Меняются только источник и первые две команды:

build-commands:
  - tar -xf messenger.tar.xz
  - mv Messenger ${FLATPAK_DEST}/Messenger
  - rm -f ${FLATPAK_DEST}/Messenger/chrome-sandbox
  - install -Dm0755 messenger.sh ${FLATPAK_DEST}/bin/messenger
  - install -Dm0644 ru.example.Messenger.desktop
      ${FLATPAK_DEST}/share/applications/${FLATPAK_ID}.desktop
  - install -Dm0644 icon.png
      ${FLATPAK_DEST}/share/icons/hicolor/256x256/apps/${FLATPAK_ID}.png
  - install -Dm0644 ru.example.Messenger.metainfo.xml
      ${FLATPAK_DEST}/share/metainfo/${FLATPAK_ID}.metainfo.xml
sources:
  - type: file
    path: ./messenger-5.2.0-linux-x64.tar.xz
    dest-filename: messenger.tar.xz
  - type: file
    path: ./messenger.sh
  - type: file
    path: ./ru.example.Messenger.desktop
  - type: file
    path: ./icon.png
  - type: file
    path: ./ru.example.Messenger.metainfo.xml

В архиве обычно нет ни desktop-файла, ни иконки, поэтому оба готовятся рядом с манифестом. Desktop-файл пишется сразу с правильными значениями, и править его sed не нужно:

[Desktop Entry]
Type=Application
Name=Messenger
Exec=messenger %U
Icon=ru.example.Messenger
Terminal=false
Categories=Network;

Сборка и установка#

cd messenger
flatpak-builder --force-clean --repo=./repo --default-branch=stable \
    build-dir ru.example.Messenger.yml
sudo flatpak remote-add --if-not-exists --no-gpg-verify local-repo ./repo
sudo flatpak install -y local-repo ru.example.Messenger
flatpak run ru.example.Messenger

Проверить, что изоляция движка действительно работает, можно по дочерним процессам приложения: у процессов Browser, GPU, Utility и Tab должен быть включён режим фильтрации системных вызовов. Если обёртка не подхватилась, Chromium сообщает об этом при старте сообщением про chrome-sandbox.

Тонкости Electron#

Идентификаторы приводятся к идентификатору приложения. Desktop-файл и иконки в поставке названы по имени программы, а Flatpak ждёт имя вида <app-id>.desktop, поэтому файлы переименовываются при установке, а ключи Exec и Icon внутри правятся отдельно.

Самообновление отключается. Electron-приложения часто умеют скачивать себе новую версию в /app. В песочнице /app доступен только для чтения, и попытка обновления заканчивается ошибкой. Механизм отключается настройкой поставщика или правкой файла с описанием сборки внутри поставки.

Хранение паролей идёт через org.freedesktop.secrets. Без этого разрешения приложение переключается на хранение в открытом виде или не запускается совсем, в зависимости от программы.

Значок в области уведомлений требует org.kde.StatusNotifierWatcher, а сами уведомления - org.freedesktop.Notifications. Оба имени на сессионной шине.

Доступ ко всему домашнему каталогу Electron-приложению обычно не нужен: открытие и сохранение файлов идут через портал, а загрузки достаточно направить в xdg-download.