The application manager uses a very simple package format: a standard UNIX gzip-compressed TAR archive. Metadata is embedded as normal files, but using the reserved name prefix
--PACKAGE-. Even though USTAR tar archives support a lot of features, the application manager only supports standard files and directories with relative paths only (using
../ in a path is not allowed). Modes, except the owner's
x bit, are ignored.
This makes it very easy to write custom packagers as well as custom app-store server backends, since TAR archive handling is available as a utility library in any programming language.
Note: gzip is currently hardcoded, but since the (de)compression is done by libarchive, this could easily be replaced by bzip2 or xz compression. Both would lead to additional 3rd-party dependencies on Windows though.
These are the important files in a package:
|Exactly one YAML document that needs to be the first file in the package - see below for a format description. This file is used as format selector.|
|The manifest of the application. The packaging tool needs to make sure that this file is within the first 10 entries of the tar archive. See Manifest Definition for the documentation of the file format.|
|The icon of the application that is used in the app-store. Can also be used as the app's icon in the launcher menu on the device: see info.yaml. The packaging tool needs to make sure that this file is within the first 10 entries of the tar archive.|
|One or more YAML document(s) that need to be the last file(s) in the package - see below for a format description. If adding more than one footer (e.g. appending the store signature by the app-store server), an arbitrary string can be appended to the file name (e.g. |
In short, here is what is NOT allowed inside a package:
- You are not allowed to have file names that start with
- You are not allowed to have absolute paths
- You are not allowed to have
../in relative paths
- You are not allowed to have sym-links, hard-links, device files, sockets, etc.
- File modes except the owner's
xbit are ignored.
All of package data, as well as important meta-data is protected by a SHA256 digest. This checksum has two purposes: it protects against transmission errors, and the package signing is done by encrypting this checksum.
The actual calculation of this checksum is done as follows:
- For every file that is not a
--PACKAGE-*meta-data file, the complete content is added to the digest. Afterwards the following UTF-8 encoded string is also added to the digest:
"F/<file size in bytes>/<file path>"(e.g. the file
foo/test.qmlwith a size of 250 bytes would generate
- For every directory, the following UTF-8 encoded string is added to the digst:
"D/0/<directory path>"(e.g. the directory
The generated digest is put into the
--PACKAGE-FOOTER-- as a 32-byte hex-encoded string.
The package format currently supports two signatures: a developer signature (generated by the developer before uploading to an appstore-server) and a store signature (generated by the appstore-server before the client actually downloads a package).
Both signatures are calculated the same way and are appended to the package's meta-data the same way: The binary digest is used as an input to create a detached PKCS7 signature. This PKCS7 signature is then BASE64 encoded and stored in
--PACKAGE-FOOTER-- - either in the
If you want to implement this signing algorithm yourself, then please look at these four existing implementations:
- C++ OpenSSL based in
- C++ WinCrypt based in
- C++ SecurityFramework based in
- Python m2crypt based in the appstore-server reference implementation.
The fields within the
--PACKAGE-HEADER-- file headers (first YAML document) are as follows:
|int||Required. Currently always |
|string||Required. Always |
The fields within the
--PACKAGE-HEADER-- data (second YAML document) are as follows:
|string||The unique ID of the package. This has to match the ID of the package as described by the encompaning |
|string||Required. The icon's file name. The file must be located in the same directory as |
Note: The old format (pre 5.14) had the formatVersion header field set to
1 and used the field name
applicationId instead of
This is an example of a minimal QML application package. The actual package can be created by tar'ing all these files up with
tar cvzf qmlapp.appkg ./--PACKAGE-HEADER-- info.yaml icon.png main.qml ./--PACKAGE-FOOTER--
%YAML 1.1 --- formatType: am-package-header formatVersion: 2 --- packageId: com.pelagicore.minimal diskSpaceUsed: 1000
%YAML 1.1 --- formatType: am-package formatVersion: 1 --- id: 'com.pelagicore.minimal' icon: 'icon.png' name: en: 'Minimal App' applications: - id: com.pelagicore.minimalapp code: 'main.qml' runtime: 'qml'
|a standard png image|
|your QML application|
%YAML 1.1 --- formatType: am-package-footer formatVersion: 2 --- # SHA256 digest in 32 char hex representation digest: '9df5635bb50e93846954c6377468c07835119e2835475ec90b3e9a9f7261cf27'
© 2023 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.