Skip to content

Manifest schema

View as Markdown

pithyx.json at the root of an app package is validated against one JSON Schema (draft 2020-12): sdk/schema/manifest.v1.schema.json in the repository, published as @pithyx/sdk/schema/manifest.v1.schema.json and identified as https://pithyx.org/schema/manifest.v1.schema.json. The box, pithyx validate and the TypeScript types of the SDK all read this file. The tables below are generated from it each time the site is built; how the fields work together is explained in The manifest.

Unknown fields are errors everywhere. The schema is a stable contract: a new optional field is a minor change, anything else a major one. A violation is reported as app.manifest.schema_violation with the JSON pointer of the field. Beyond the schema the box checks the id (app.manifest.id_invalid, app.manifest.id_reserved), the version ranges, that window.minSize fits into window.defaultSize, duplicates and references between fields, the cron expressions and the elevated rights (Findings).

Paths use . for nested fields, [] for the entries of an array and <name> or <language> for keys you choose. “Required” is relative to the enclosing object: backend.compose is required when there is a backend.

Field Type Required Default Allowed values Description
$schema string no – at most 500 characters Path or URL of this schema, for editors. Ignored by the box.
manifestVersion integer yes – 1 The manifest schema this file follows.
id string yes – at most 100 characters; ^[a-z][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*){2,4}$ The author’s domain reversed plus the app name: 3 to 5 parts of lowercase letters, digits and dashes, at most 100 characters, for example com.mueller.chat or io.github.jan.chat. Unique per box. org.pithyx.* is reserved for the system apps and the official catalog.
version string yes – at most 64 characters; a pattern of 148 characters, see the schema SemVer 2.0 version of the app, without build metadata, for example 1.2.0 or 2.0.0-beta.1.
name object yes – – A short text in several languages, keyed by BCP 47 language tag, for example the app’s name or a storage request’s name. en is required and used when nothing else matches. One line, at most 60 characters per language.
description object no – – A text in several languages, keyed by BCP 47 language tag, for example the app’s description of one or two sentences for the store and the install dialog. en is required and used when nothing else matches. At most 500 characters per language; line breaks are allowed.
author object yes – – Who makes the app.
license string no – at most 100 characters; ^[!-~]+( [!-~]+)*$ SPDX license expression of the app, for example MIT or MIT OR Apache-2.0.
homepage string no – at most 500 characters; ^https?://[!-~]+$ The app’s website or source repository.
entry string yes – at most 200 characters; ^frontend/([A-Za-z0-9_][A-Za-z0-9._-]*/)*[A-Za-z0-9_][A-Za-z0-9._-]*\.html$ Path of the HTML page the desktop loads in the sandboxed window, inside frontend/ of the package, for example frontend/index.html. Segments never start with a dot.
sdk string yes – at most 100 characters; a pattern of 1455 characters, see the schema SemVer range of @pithyx/sdk the app was built against, for example ^1.0.0.
pithyx string no – at most 100 characters; a pattern of 1455 characters, see the schema SemVer range of the Pithyx versions the app runs on, for example >=0.1.0. Optional; without it the app claims to run on every version. A box installs a version only when its own version is in the range, and lists a catalog version only then. Unlike npm, a box running a prerelease matches by plain SemVer precedence, so >=0.1.0 includes 0.2.0-beta.1, while ^0.1.0 does not (it ends below 0.2.0-0). Catalogs require it.
dataVersion integer no – 1 to 2147483647 The version of the format of the app’s stored data (volumes, its stored values and the files of its storage requests), an integer from 1. Optional. Raise it with every release that converts data in a way the previous version cannot read. A box goes back to an older version from a catalog only when both declare the same dataVersion; going back to the version before an update with its snapshot works without it.
permissions array of string yes – no duplicates; files.read, files.write, files.shares, notifications, directory.read, network.internet, network.lan The permissions the app asks for. Each one appears in the install dialog and can be revoked later. Each entry: A permission an app declares. Nothing is granted by using an API: the install dialog lists every declared permission, an administrator confirms them for the app, and each one can be revoked later (the app gets permissionsChange). Which users may use the app at all is a separate grant per user and group. The daemon checks every permission on every call that reaches data or leaves the window. An update that declares a new permission waits until an administrator confirms it; the old version keeps running. files.read: read files the user opens with the app or picks in a desktop file dialog. files.write: write those files, and save new files where the user chooses in a desktop save dialog. files.shares: browse every shared folder the user may access, with the user’s own rights. notifications: show notifications. directory.read: list all users and groups of the box. network.internet: outgoing connections to the internet, from the window (connect-src https:) and from the backend. network.lan: outgoing connections from the backend to the local network (private, shared and link-local addresses such as 192.168.0.0/16); without it the backend reaches no other device in the LAN, also with network.internet. The box itself is never reachable.
window object yes – – How the app’s windows behave. defaultSize is the size of a new window when the user has no remembered size for the app; minSize is the smallest size on screens that can fit it, at most defaultSize in both dimensions.
storage array of object no – at most 16 entries Places for files the app needs, each mapped by an administrator to a folder in a shared folder.
fileHandlers array of object no – at most 32 entries Files the app can open.
backend object no – – An optional backend of one or more containers, described by a Compose file in the package and reached only through the daemon’s proxy.
Field Type Required Default Allowed values Description
name.en string yes – 1 to 60 characters; ^[^\u0000-\u001f\u007f]+$ The English text, shown when no other language matches. One line, at most 60 characters.
name.<language> string no – 1 to 60 characters; ^[^\u0000-\u001f\u007f]+$ Keys matching ^[a-z]{2,3}(-[A-Za-z0-9]{1,8})*$. The text in the language of the key, a BCP 47 language tag such as de or pt-BR. One line, at most 60 characters.
Field Type Required Default Allowed values Description
description.en string yes – 1 to 500 characters; ^[^\u0000-\u0009\u000b-\u001f\u007f]+$ The English text, shown when no other language matches. At most 500 characters; line breaks are allowed.
description.<language> string no – 1 to 500 characters; ^[^\u0000-\u0009\u000b-\u001f\u007f]+$ Keys matching ^[a-z]{2,3}(-[A-Za-z0-9]{1,8})*$. The text in the language of the key, a BCP 47 language tag such as de or pt-BR. At most 500 characters; line breaks are allowed.
Field Type Required Default Allowed values Description
author.name string yes – 1 to 100 characters; ^[^\u0000-\u001f\u007f]+$ A person or an organization.
author.url string no – at most 500 characters; ^https?://[!-~]+$ The author’s website.
Field Type Required Default Allowed values Description
window.instances string yes – single, multi single: opening the app again focuses the user’s existing window on this device and passes the launch arguments to it (launch event). multi: every launch opens a new window.
window.defaultSize object yes – – A size in CSS pixels.
window.defaultSize.width integer yes – 100 to 8192 The width in CSS pixels.
window.defaultSize.height integer yes – 100 to 8192 The height in CSS pixels.
window.minSize object yes – – A size in CSS pixels.
window.minSize.width integer yes – 100 to 8192 The width in CSS pixels.
window.minSize.height integer yes – 100 to 8192 The height in CSS pixels.
window.loading object no – – What a window shows before the app is ready. The desktop always draws the app’s icon and name in a new window and an indicator after 300 ms, until the app connected (or, with untilReady, until it called app.window.ready()). While the backend starts, a line of text says so; without text it is the desktop’s own, such as “Starting Lists …”. text is the line shown while the backend starts, in the user’s language, for example “Opening your lists …”: say what happens in plain words, without technical terms.
window.loading.text object no – – A short text in several languages, keyed by BCP 47 language tag, for example the app’s name or a storage request’s name. en is required and used when nothing else matches. One line, at most 60 characters per language.
window.loading.text.en string yes – 1 to 60 characters; ^[^\u0000-\u001f\u007f]+$ The English text, shown when no other language matches. One line, at most 60 characters.
window.loading.text.<language> string no – 1 to 60 characters; ^[^\u0000-\u001f\u007f]+$ Keys matching ^[a-z]{2,3}(-[A-Za-z0-9]{1,8})*$. The text in the language of the key, a BCP 47 language tag such as de or pt-BR. One line, at most 60 characters.
window.loading.untilReady boolean no – – Whether the window keeps the placeholder after the app connected until it calls app.window.ready(), for an app that first loads its data. The desktop removes it after 30 seconds in any case. Default false.
Field Type Required Default Allowed values Description
storage[].id string yes – ^[a-z][a-z0-9-]{0,31}$ Unique within the app.
storage[].name object yes – – A short text in several languages, keyed by BCP 47 language tag, for example the app’s name or a storage request’s name. en is required and used when nothing else matches. One line, at most 60 characters per language.
storage[].name.en string yes – 1 to 60 characters; ^[^\u0000-\u001f\u007f]+$ The English text, shown when no other language matches. One line, at most 60 characters.
storage[].name.<language> string no – 1 to 60 characters; ^[^\u0000-\u001f\u007f]+$ Keys matching ^[a-z]{2,3}(-[A-Za-z0-9]{1,8})*$. The text in the language of the key, a BCP 47 language tag such as de or pt-BR. One line, at most 60 characters.
storage[].description object no – – A text in several languages, keyed by BCP 47 language tag, for example the app’s description of one or two sentences for the store and the install dialog. en is required and used when nothing else matches. At most 500 characters per language; line breaks are allowed.
storage[].description.en string yes – 1 to 500 characters; ^[^\u0000-\u0009\u000b-\u001f\u007f]+$ The English text, shown when no other language matches. At most 500 characters; line breaks are allowed.
storage[].description.<language> string no – 1 to 500 characters; ^[^\u0000-\u0009\u000b-\u001f\u007f]+$ Keys matching ^[a-z]{2,3}(-[A-Za-z0-9]{1,8})*$. The text in the language of the key, a BCP 47 language tag such as de or pt-BR. At most 500 characters; line breaks are allowed.
storage[].access string yes – read, readWrite The most the app may do with the mapped folder: read or readWrite. Users never get more than their own rights on the folder, and with read every backend service mounts it read-only.
storage[].required boolean yes – – The app cannot start until this storage is mapped.
storage[].services array of string no – at least 1 entries; no duplicates; ^[a-z0-9][a-z0-9_.-]{0,62}$ Backend services that mount this storage. Needs a backend. Each entry: A service of backend/compose.yaml.
Field Type Required Default Allowed values Description
fileHandlers[].extensions array of string yes – 1 to 32 entries; no duplicates; at most 32 characters; ^[a-z0-9]+([.+_-][a-z0-9]+)*$ Lowercase extensions without the leading dot, for example png or tar.gz.
fileHandlers[].mimeTypes array of string no – 1 to 32 entries; no duplicates; at most 127 characters; ^[a-z0-9][a-z0-9!#$&^_.+-]*/[a-z0-9][a-z0-9!#$&^_.+-]*$ MIME types, for example image/png.
fileHandlers[].isDefault boolean no – – Whether the app is the default for these files when it is the only handler.
Field Type Required Default Allowed values Description
backend.compose string yes – at most 200 characters; ^backend/([A-Za-z0-9_][A-Za-z0-9._-]*/)*[A-Za-z0-9_][A-Za-z0-9._-]*\.ya?ml$ Path of the Compose file inside backend/ of the package, usually backend/compose.yaml.
backend.endpoints object yes – at most 16 entries The ports the proxy offers, by name. api is required: it is the default endpoint of app.backend.
backend.endpoints.api object yes – – A port of a backend service that the proxy offers to the app’s windows. Container ports never appear in a URL: the endpoint api is /apps/<id>/api/, every other endpoint <name> is /apps/<id>/api-<name>/.
backend.endpoints.api.service string yes – ^[a-z0-9][a-z0-9_.-]{0,62}$ A service of backend/compose.yaml.
backend.endpoints.api.port integer yes – 1 to 65535 The port the service listens on inside the app’s internal network. It never gets a host port.
backend.endpoints.<name> object no – – Keys matching ^[a-z][a-z0-9-]{0,31}$. Each one is like backend.endpoints.api.
backend.lifecycle string yes – always, onDemand always: runs while the app is installed and its pool is available. onDemand: runs while at least one window of the app is open for any user, plus idleTimeout, or while a schedule holds it.
backend.idleTimeout integer no 600 60 to 86400 Seconds an onDemand backend keeps running after its last window closed. Default 600. Ignored for always.
backend.root boolean no false – The containers run as root inside the container (unprivileged on the host through user namespaces). The install dialog warns about it. Default false: they run as the app’s own user.
backend.network object no – – Which services get the network out of the box that network.internet and network.lan open. Without it every service gets it; the others stay on the app’s internal network only. No effect without one of the two permissions.
backend.network.services array of string yes – 1 to 32 entries; no duplicates; ^[a-z0-9][a-z0-9_.-]{0,62}$ The services of the Compose file that may connect out, for example only the one that fetches. Each entry: A service of backend/compose.yaml.
backend.health object no – – The daemon calls GET path on endpoint to learn whether the backend is ready; a 2xx answer means running.
backend.health.endpoint string yes – ^[a-z][a-z0-9-]{0,31}$ A key of endpoints.
backend.health.path string yes – at most 200 characters; ^/[!-~]*$ A path on a backend endpoint, starting with /.
backend.schedules array of object no – at most 16 entries Recurring jobs that call the backend on a cron schedule, also without an open window.
backend.schedules[].id string yes – ^[a-z][a-z0-9-]{0,31}$ Stable name, unique within the app; the Log Center names it with each run.
backend.schedules[].cron string yes – at most 100 characters Five fields separated by single spaces (minute 0-59, hour 0-23, day of month 1-31, month 1-12, day of week 0-7 with 0 and 7 for Sunday), in the box’s time zone. Each field is a comma list of *, numbers and ranges a-b; * and ranges may take a step /n. No names.
backend.schedules[].endpoint string yes – ^[a-z][a-z0-9-]{0,31}$ A key of endpoints.
backend.schedules[].path string yes – at most 200 characters; ^/[!-~]*$ A path on a backend endpoint, starting with /.
backend.elevated array of string no – at most 32 entries; no duplicates; at most 200 characters; a pattern of 131 characters, see the schema Rights beyond the sandbox. The Compose file must use exactly what these allow. Each entry: A right beyond the sandbox, for the backend only (D71): host.network (the host’s network namespace), host.pid (the host’s process namespace), device:<path under /dev> (one device node, for example device:/dev/dri/renderD128), capability:<NAME> (one Linux capability without the CAP_ prefix, for example capability:NET_ADMIN), docker.socket (the Docker Engine API through the daemon’s filtering proxy) and host.full (a privileged container: root on the host). The schema describes the planned rights; Pithyx 1.0.0 refuses every one of them with app.elevated.unsupported, and a later version grants them to administrators only, after a confirmation with password and TOTP.

In the same folder: compose.v1.rules.json (the Compose rules), images-lock.v1.schema.json (backend/images.lock.json), and the catalog formats catalog.v1.schema.json, index.v1.schema.json, index-signature.v1.schema.json, maintainers.v1.schema.json and catalog.v1.rules.json.