Containers
In the application manager context, a 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.
Predefined Containers
The application manager comes with one built-in container type: the process
container, that spawns a new Unix process to execute the requested binary.
In addition, you can find a basic integration of Pelagicore's SoftwareContainer in examples/applicationmanager/softwarecontainer-plugin
. For more information, see SoftwareContainer Plugin Example. This 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.
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:
TEMPLATE = lib CONFIG += plugin TARGET = mycontainer-plugin QT += appman_plugininterfaces-private
Then, you only have to implement two classes that derive from ContainerInterface and from ContainerManagerInterface respectively:
#include <QtAppManPluginInterfaces/containerinterface.h> class SoftwareContainer : public ContainerInterface { // ... }; class SoftwareContainerManager : public QObject, public ContainerManagerInterface { Q_OBJECT Q_PLUGIN_METADATA(IID AM_ContainerManagerInterface_iid) Q_INTERFACES(ContainerManagerInterface) // .... };
Be aware that your container plugin has to support a few basic requirements to support UI clients in multi-process mode:
- The plugin has to be able to forward Unix local sockets into the container. This is needed for both the 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.
- 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
/dev/dri/*
available within the container. - 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
--no-security
command line option.
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:
Name | Description |
---|---|
WAYLAND_DISPLAY | The path to the Wayland server socket. If your container uses its own filesystem namespace, make sure that this socket is forwarded accordingly. |
QT_QPA_PLATFORM | Always set to wayland . |
QT_IM_MODULE | Not set, but explicitly unset by the application manager. Make sure to leave it unset, to use the automatic Wayland input method implementation. |
QT_SCALE_FACTOR | 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. |
QT_WAYLAND_SHELL_INTEGRATION | Set to xdg-shell . This is the preferred wayland shell integration. |
DBUS_SESSION_BUS_ADDRESS | The standard D-Bus session bus. |
AM_CONFIG | A YAML, UTF-8 string encoded version of the amConfig map. |
AM_NO_DLT_LOGGING | Tells the application to not use DLT for logging. |
Configuration
A container configuration has three parts:
- Configure which containers are available when loading the container plugins
- Add specific settings for each container integration available
- Configure which container solution to select to run a specific application
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:
plugins: container: [ "/full/path/to/softwarecontainers.so", "/another/plugin.so" ]
Note: The application manager does not load plugins automatically if they are placed in a specific directory. This is because container plugins control the central security mechanism for separating applications.
Add Container Integration Settings
Each container integration has a unique ID, which can be used to add settings to the application manager's config file, such as:
containers: process: defaultControlGroup: "foo" softwarecontainers: bar: [ 1, 2, 3 ]
The process
container accepts the following configuration settings:
Settings Name | Type | Description |
---|---|---|
controlGroups | object | A two-stage mapping object to allow for more readable code when dealing with cgroups from the System UI via Container::controlGroup. The top-level keys are readable group names that are used to interface with Container::controlGroup. The values themselves are maps between multiple low-level cgroup sub-system names and the actual cgroup names within those sub-systems, such as:controlGroups: foreGround: memory: mem1 cpu: cpu_full backGround: memory: mem2 cpu: cpu_minimal |
defaultControlGroup | string | The default control group for an application when it is first launched. |
For other container plugins, refer to their respective documentation.
Container Selection Configuration
When you start an application from within the application manager, there are multiple ways to control which container integration is used:
- If the config file does not contain the
containers/selection
key, the container integration ID defaults toprocess
. - If the
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.containers: selection: - com.pelagicore.*: "process" - com.navigation: "special-container" - '*': "softwarecontainers" # a single asterisk needs to be quoted
- 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.
ApplicationManager.containerSelectionFunction = function(appId, containerId) { var app = ApplicationManager.application(appId) if (app.capabilities.indexOf("non-secure") != -1) return "process" else return containerId }
© 2021 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.