// Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2019 Luxoft Sweden AB // Copyright (C) 2018 Pelagicore AG // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! \page containers.html \title Containers \ingroup qtappman \ingroup qtappman-highlights \brief Describes an execution environment for an executable. In the application manager context, a \b container describes an execution environment for an executable: either an application's binary or its runtime binary, in multi-process mode. The container does not have to be something sophisticated like a Docker container, but can be as simple as a Unix process. \section1 Predefined Containers \annotatedlist qtappman-containers \section1 Configuration A container configuration has three parts: \list 1 \li Configure which containers are available when \l{load-plugins}{loading} the container plugins \li Add specific settings for each container integration available. See the container specific documentation for more information. \li Configure which container solution to \l{container-selection}{select} to run a specific application \endlist \target load-plugins \section1 Load Container Plugins To configure an existing container plugin for use in the application manager, you need to add its full path to the list of plugins to load in the application manager's config file: \badcode plugins: container: [ "/full/path/to/softwarecontainers.so", "/another/plugin.so" ] \endcode Plugins installed into Qt's plugin directory into the \c appman_container folder are picked up automatically, but you still need to enable the usage of the container by having an entry for the container's id in the main \c containers: configuration field: \badcode containers: bubblewrap: ... \endcode \target container-selection \section1 Container Selection Configuration When you start an application from within the application manager, there are multiple ways to control which container integration is used: \list 1 \li If the config file does not contain the \c containers/selection key, the container integration ID defaults to \c process. \li If the \c containers/selection key exists, its contents are parsed as a list of maps, where each map has a single mapping only. While this single mapping is awkward, it is necessary to preserve the order of the mappings. Each key is interpreted as a standard Unix wildcard expression that is matched against the application ID. The first match stops the algorithm and the mapping's value is used as the container integration ID. If no matches are found, the resulting container integration ID is an empty string. \badcode containers: selection: - com.pelagicore.*: "process" - com.navigation: "special-container" - '*': "softwarecontainers" # a single asterisk needs to be quoted \endcode \li Afterwards, if the System UI did set the ApplicationManager::containerSelectionFunction property to a valid JavaScript function, this function is called with the first parameter set to the application's ID and the second parameter set to the container integration ID that resulted from step 1 and 2. \badcode ApplicationManager.containerSelectionFunction = function(appId, containerId) { var app = ApplicationManager.application(appId) if (app.capabilities.indexOf("non-secure") != -1) return "process" else return containerId } \endcode \endlist \section1 Extend with Container Plugins Custom container solutions can be added via plugins. These plugins need not to be built as part of the application manager, but they need to be built against a private Qt module to get the interface definition. The \l{SoftwareContainer Plugin Example} can be used as a blueprint to either create a customer-specific production version of a SoftwareContainer plugin, or to integrate another container solution. Following a brief introduction what steps needs to be done to build your own plugin. First you need to configure your build system. Here's a snippet on how to do this with cmake: \badcode ... find_package(Qt6 COMPONENTS AppManPluginInterfacesPrivate) qt_add_plugin(mycontainer-plugin) target_link_libraries(mycontainer-plugin PUBLIC Qt::AppManPluginInterfacesPrivate) ... \endcode Here's the qmake version: \badcode ... TEMPLATE = lib CONFIG += plugin TARGET = mycontainer-plugin QT += appman_plugininterfaces-private ... \endcode Then, you only have to implement two classes that derive from \l ContainerInterface and from \l ContainerManagerInterface respectively: \code #include class SoftwareContainer : public ContainerInterface { // ... }; class SoftwareContainerManager : public QObject, public ContainerManagerInterface { Q_OBJECT Q_PLUGIN_METADATA(IID AM_ContainerManagerInterface_iid) Q_INTERFACES(ContainerManagerInterface) // .... }; \endcode Be aware that your container plugin has to support a few basic requirements to support UI clients in multi-process mode: \list 1 \li The plugin has to be able to forward Unix local sockets into the container. This is needed for both the \l{Qt Wayland Compositor}{Wayland} socket as well as for the private peer-to-peer D-Bus connection. If the plugin cannot map these sockets to the correct location within the container, the plugin then needs to modify the environment variables for the respective locations before passing them on to the container. The table below lists the relevant environment variables. \li To support hardware OpenGL acceleration, the container needs to have access to the necessary devices. For GPUs that follow Linux standards, such as Intel, make sure to have \c{/dev/dri/*} available within the container. \li You have to implement PID mapping in your plugin; unless your container solution shares its PID namespace with the rest of the system. This is necessary if you want to make use of the application manager's security features. Each connection coming into the application manager via the Wayland or D-Bus Unix local sockets is queried for the PID of the application that requests the connection. The application manager verifies these PIDs against the PIDs of all running applications via ContainerInterface::processId(). Connections that don't match a PID are not accepted. However, you can disable this behavior via the \l{no-security}{\c{--no-security}} command line option. \endlist The application manager uses the following environment variables to communicate various settings to the application. A custom container plugin must forward these variables or adjust them accordingly: \table \header \li Name \li Description \row \li \c{WAYLAND_DISPLAY} \li The path to the Wayland server socket. If your container uses its own filesystem namespace, make sure that this socket is forwarded accordingly. \row \li \c{QT_QPA_PLATFORM} \li Always set to \c{wayland}. \row \li \c{QT_IM_MODULE} \li Not set, but explicitly unset by the application manager. Make sure to leave it unset, to use the automatic Wayland input method implementation. \row \li \c{QT_SCALE_FACTOR} \li Empty (unset), to prevent scaling of wayland clients relative to the compositor. Otherwise running the application manager on a 4K desktop with scaling would result in double-scaled applications within the application manager. \row \li \c{QT_WAYLAND_SHELL_INTEGRATION} \li Set to \c{xdg-shell}. This is the preferred wayland shell integration. \row \li \c{DBUS_SESSION_BUS_ADDRESS} \li The standard D-Bus session bus. \row \li \c{AM_CONFIG} \li A YAML, UTF-8 string encoded version of the \l{amConfigDetails}{amConfig} map. \row \li \c{AM_NO_DLT_LOGGING} \li Tells the application to not use DLT for logging, if set to \c 1. \endtable */ /*! \page process-container.html \title Process Container \ingroup qtappman-containers \brief Spawns a new Unix process to execute the requested binary. The \c process container is built into the application manager and enabled by default. It can be configured in the application manager config file using its unique ID: \c process: \badcode containers: process: defaultControlGroup: "foo" \endcode The \c process container accepts the following configuration settings: \table \header \li Settings Name \li Type \li Description \row \li \c controlGroups \li object \target control group mapping \li A two-stage mapping object to allow for more readable code when dealing with \c cgroups from the System UI via Container::controlGroup. The top-level \c keys are readable group names that are used to interface with Container::controlGroup. The values themselves are \c maps between multiple low-level \c cgroup sub-system names and the actual \c cgroup names within those sub-systems, such as: \badcode controlGroups: foreGround: memory: mem1 cpu: cpu_full backGround: memory: mem2 cpu: cpu_minimal \endcode \row \li \c defaultControlGroup \li string \li The default control group for an application when it is first launched. \endtable */ /*! \page bubblewrap-container.html \title Bubblewrap Container \ingroup qtappman-containers \brief Spawns a process in a kernel namespace using the bubblewrap utility. The \c bubblewrap container uses the \l{bubblewrap} utility to create a new kernel namespace and runs the requested binary in this sandbox isolated from the rest of the system. This is the base technology used in the \l{Flatpak}{Linux Flatpak ecosystem}. See the \l {Bubblewrap Container Example} for an example setup with test applications. At least \c bubblewrap version 0.5 needs to be installed on the target system. Using the latest upstream release is recommended though. The \c bubblewrap container is built as a plugin and loaded, but not enabled by default. It can be configured in the application manager's config file using its unique ID: \c bubblewrap: \badcode containers: bubblewrap: sharedNamespaces: [ '-all', '+net' ] bindMountHome: yes configuration: symlink: usr/lib: '/lib' usr/lib64: '/lib64' usr/bin: [ '/bin', '/sbin' ] ro-bind: /usr/bin: '/usr/bin' /usr/lib: '/usr/lib' /etc: '/etc' /usr/share/fonts: '/usr/share/fonts' /usr/share/fontconfig: '/usr/share/fontconfig' /usr/share/ca-certificates: '/usr/share/ca-certificates' /sys/dev/char: '/sys/dev/char' ${CONFIG_PWD}/imports: '${CONFIG_PWD}/imports' ro-bind-try: /usr/lib64: '/usr/lib64' '/sys/devices/pci0000:00': '/sys/devices/pci0000:00' /usr/share/glvnd/egl_vendor.d: '/usr/share/glvnd/egl_vendor.d' /usr/share/X11/xkb: '/usr/share/X11/xkb' /run/resolvconf: '/run/resolvconf' dev: '/dev' dev-bind: /dev/dri: '/dev/dri' tmpfs: /tmp proc: /proc \endcode The \c bubblewrap container accepts the following configuration settings: \table \header \li Settings Name \li Type \li Description \row \li \c bwrap-location \li string \li The path to the \c bwrap binary. If no path is configured the standard \c{$PATH} is used to find the executable. \row \li \c configuration \li object \li A two-stage mapping object to configure the sandboxing of the plugin. The top-level \c keys are translated into options passed to the bubblewrap binary. The values themselves are used as arguments for those options. Here is an example configuration: \badcode configuration: symlink: usr/lib: '/lib' usr/lib64: '/lib64' usr/bin: [ '/bin', '/sbin' ] ro-bind: /usr/bin: '/usr/bin' /usr/lib: '/usr/lib' /usr/lib64: '/usr/lib64' \endcode \row \li \c bindMountHome \li bool \li Mounts the whole Home directory of the current user into the container. This can be used for development purposes. (default: false) \row \li \c sharedNamespaces \li list \li This gives you fine-grained control over the shared kernel namespaces that are (not) shared with the container. The following namespaces are supported: \c{all, net, user, ipc, pid, net, uts, cgroup}. Each entry has to be prefixed with \b{+} or \b{-} to indicate whether the namespace should be shared or not. The first entry has to be \c all and sets the baseline: you can unshare all and then share specific namespaces (\c{-all, +pid, +ipc}), or you can share all and then unshare individual namespaces (\c{+all, -net}). (default: \c{-all}) \row \li \c networkSetupScript \li string \li The path to a shell script, that is executed when the container starts and stops in order to correctly setup and shutdown networking for the container. See the \l {Bubblewrap Container Example} for an example script. \note You most likely want to have the network namespace shared, if you set this option: \c{ sharedNamespaces: [ '-all', '+net' ]} \row \li \c unshareNetwork \li string \li \e Deprecated. Please use \c sharedNamespaces and \c networkSetupScript instead. \endtable */