[[packages]]= Cockpit PackagesCockpit is separated into various packages, each of which bringsspecific features and/or code.[WARNING]====In addition, any APIs or behavior not explicitly documented here is aninternal API and can be changed at any time.====[[package-layout]]== Layout of Package FilesA package consists of one or more files placed in a directory or itssubdirectories. It must have a `+manifest.json+` file and follow certainnaming conventions.The name of a package is the name of the directory.The name of the package must be ASCII alphanumeric, and may contain anunderscore. Names of directories and files in the package must consistof ASCII alphanumeric along with dash, underscore, dot, and comma. Nospaces are allowed.Cockpit uses the data directories from thehttps://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html[XDGBase Directory Specification] to locate packages. The `+$XDG_DATA_DIRS+`represents a colon separate list of system data directories, and`+$XDG_DATA_HOME+` is a user specific data directory. If the environmentvariables are not set, defaults are used, according to the spec. Ifcockpit has been built with an alternate `+--prefix=/path+` then the`+$prefix/share/cockpit+` is used by default.A `+cockpit/+` subdirectories in any of these data directories is thelocation where packages are loaded by Cockpit. If Cockpit finds apackage with the same name, in multiple data directories, then the firstone wins. According to the spec the first data directory is`+$XDG_DATA_HOME+` and then `+$XDG_DATA_DIRS+` in order.This means that, by default the following directories are searched forcockpit packages, and in this order:* `+~/.local/share/cockpit/+`* `+/usr/local/share/cockpit/+`* `+/usr/share/cockpit/+`Packages placed in `+$XDG_DATA_HOME+` are not cached by Cockpit or theweb browser. Other packages are cached aggressively, and are accessedusing a checksum of the files in the packages and their names.You can use the following command to list the packages installed on aserver. You'll note that it's output may change when you run the commandas different users, if there are packages installed in the user's homedirectory.....$ cockpit-bridge --packages.......To further clarify things, here is an example package called"my-package" and its file layout:..../usr/share/cockpit/my-package/manifest.jsonfile.htmlsome.js....Place or symlink packages in your `+~/.local/share/cockpit+` directory(or appropriate `+$XDG_DATA_HOME+` location) that you would like tomodify and develop. System installed packages should not change whileCockpit is running.== Package ManifestEach package has a `+manifest.json+` file. It is a JSON object. Thefollowing fields may be present in the manifest:content-security-policy::By default Cockpit serves packages using a stricthttps://en.wikipedia.org/wiki/Content_Security_Policy[Content SecurityPolicy], which among other things does not allow inline styles orscripts. This can be overridden on a per-package basis, with thissetting.+If the overridden content security policy does not contain a`+default-src+`, `+connect-src+`, `+base-uri+`, `+form-action+`,`+object-src+`, or `+block-all-mixed-content+` then these will beadded to the policy from the manifest.name::An optional string that changes the name of the package. Normallypackages derive their name from the directory that they are locatedin. This field overrides that name.priority::An optional integer that specifies which package is preferred in caseswhere there are conflicts. For example given two packages with thesame `+name+` a package is chosen based on its priority. A higher integermeans higher priority.conditions::An optional list of `+{"predicate": "value"}+` objects. Cockpit willonly consider the package if _all_ conditions are met. This ispreferable to using `+priority+`. ++Currently supported predicates are `+path-exists+` and `+path-not-exists+`(since Cockpit 286) and `+any+` (since Cockpit 348). `+any+` accepts alist of conditions and evaluates to true if _at least one_ of the nestedconditions is met. Unknown predicates are ignored.requires::An optional JSON object that contains a `+"cockpit"+` string versionnumber. The package will only be usable if the Cockpit bridge andjavascript base are equal or newer than the given version number.version::An informational version number for the package.preload::A list of identifiers of the components that should be preloaded.Normally, the files of a component are loaded when the user navigatesto it for the first time. The files of a preloaded component areloaded immediately after the user logs in, and the initialization codeof the component is invoked.+The value of this field is an array of strings, where each string isone of the keys used in the `+dashboard+`, `+menu+`, or `+tools+`fields that are explained below.parent::This option is used when module does not have its own menu item but isa part of a different module. This is described by JSON object withproperties `+component+` which takes name of the superordinatecomponent and `+docs+` with list of documentation URLs for the givenpage. See below for structure of `+docs+` property.In addition, the following keys contain information about wherecomponents of the package should appear in Cockpit's user interface.Each of these keys is optional and contains an object mapping uniqueidentifiers to menu items, which are described below. (The naming ofthese fields doesn't perfectly match the current user interface forhistorical reasons.)dashboard::Dashboard items appear in the menu under the section _Apps_.menu::These items appear in the menu under the section _System_. Thissection is roughly ordered into these categories (with their _order_in parentheses):+* System Information (10)* Logs (20)* Configuring major subsystems (30-40)* Things running on the machine (VMs, Containers - 50-60)* Implementation Details (Accounts, Services - 70-100)tools::These items appear in the menu under the section _Tools_.Menu items and tools are registered using JSON objects that have thefollowing properties:label::The label for the menu item or tool.order::An optional order integer to place this menu item or tool. Lowerintegers are listed first.path::The relative path to the HTML file within the package that implementsthe menu item or tool.docs::List of documentation URLs for the given page. Each item is an objectcontaining `+label+` and `+url+`.keywords::Keywords that describe the page and which are used for searching.These keywords should be lowercase. Keywords is a list containingkeyword items as described below. Page label is prepended as firstkeyword in the first keyword item.Keyword items are registered using JSON objects that have the followingproperties:matches::List of keywords to be matched.goto::Optional path that is used for all keywords in this item. When thisargument starts with slash, then it is used as pathname, otherwise itis used as hash. Defining `+goto:"page_hash"+` in page with`+path:"/page_path"+` would redirect to `+/page_path#page_hash+`,while `+goto:"/page_path"+` would redirect to `+/page_path+` ignoringdefault page path.weight::How much keywords are prioritized over others. Default is 3.translate::`+false+` when keywords should not be localized. Default is `+true+`.An example manifest.json with some optional properties set:....{"version": 0,"require": {"cockpit": "120"},"tools": {"mytool": {"label": "My Tool","path": "tool.html"}}}........{"version": 0,"require": {"cockpit": "120"},"conditions": [{"path-exists": "/usr/bin/mytool"},{"path-exists": "/etc/mytool.conf"},{"path-not-exist": "/etc/incompatible-tool"}],"tools": {"mytool": {"label": "My Tool","path": "tool.html"}}}....The following example shows how to use disjunctive conditions with `+any+`to specify alternative requirements (available since Cockpit 348). Thispackage will be available if either `+/usr/bin/alt1+` or `+/usr/bin/alt2+`exists. In any case, `+/etc/incompatible-tool+` must not exist....."conditions": [{"any": [{"path-exists": "/usr/bin/alt1"}, {"path-exists": "/usr/bin/alt2"}]},{"path-not-exists": "/etc/incompatible-tool"}]....[[package-manifest-override]]== Manifest overridesTo change a manifest system-wide, a file`+<package-directory-name>.override.json+` may be placed into`+/etc/cockpit/+`, or below `+$XDG_CONFIG_DIRS+` if set (seelink:./cockpit.conf.5.html[cockpit.conf]). To change it for a particularuser only, put the override into `+~/.config/cockpit+`.These override the information in the manifest in the simplehttps://tools.ietf.org/html/rfc7386[JSON Merge Patch] format.This can be used to hide or modify menu items of an existing package.For example `+/etc/cockpit/systemd.override.json+` or`+~/.config/cockpit/systemd.override.json+` could hide the _Logs_ menuitem and move the _Services_ menu item to the top of the menu.....{"menu": {"logs": null,"services": {"order": -1}}}....[[package-links]]== Package Links and PathsWhen referring to files in your package, such as in a hyperlink or a`+<style>+` tag or `+<script>+` tag, simply use a relative path, andrefer to the files in the same directory. When you need to refer tofiles in another package use a relative link.For example here's how to include the base `+cockpit.js+` script in yourHTML from the `+latest+` package:....<script src="../base1/cockpit.js"></script>....Do not assume you can link to any file in any other package. Refer tothe xref:development.adoc[list of API packages] for those that are availablefor use.[[package-minified]]== Content NegotiationIn order to support gzipped and/or minified data, the files in a packageare loaded using content negotiation logic. A HTTP request for the file`+test.js+` in the package named `+mypackage+` will return`+mypackage/test.js+` or `+mypackage/test.js.gz+` (in undefinedpreference). If neither exists, then it returns`+mypackage/test.js.min+` or `+mypackage/test.js.min.gz+` (again inundefined preference).When packages are loaded from a system directory, Cockpit optimizes thefile system lookups above, by pre-listing the files. This is one of thereasons that you should never change packages installed to a systemdirectory while Cockpit is running.[[package-api]]== Using Cockpit APICockpit has API available for writing packages. There is no APIavailable for external callers to invoke via HTTP, REST or otherwise.API from various packages can be used to implement Cockpit packages.Each package listed here has some API available for use. Only the APIexplicitly documented should be used.* xref:development.adoc[API Listing]To include javascript from the API, simply load it into your HTML usinga script tag. Alternatively you can use an javascript loader.[[package-bridges]]== Bridges for specific tasksOn the server side the man:cockpit-bridge[1]connects to various system APIs that the front end UI requests it to.There are additional bridges for specific tasks that the main`+cockpit-bridge+` cannot handle, such as using the PCP C library API.These additional bridges can be registered in a `+"bridges"+` section ofa package's `+manifest.json+` file. Building such a bridge is a complextasks, and we will skip over that here. However it is useful to adjusthow these additional bridges are called, and so we'll look at how theyare registered.An example `+manifest.json+` with a bridges section:....{"bridges": [{"match": { "payload": "metrics1" },"spawn": [ "/usr/libexec/cockpit-pcp" ]}]}....The bridges are considered in the order they are listed in the array.Use the `+manifest.json+``+"priority"+` field to control order betweenpackages. The bridges are registered using JSON objects that have thefollowing properties:environ::Optional, additional environment variables to pass to the bridgecommand.match::The `+"match"+` object describes which channel open command optionsneed to match for a given channel to be handed over to this bridge.privileged::If set to `+true+`, this marks the bridge as a superuser bridge.Cockpit will start one of these explicitly when trying to escalate theprivileges of a session. A privileged bridge can not have a`+"match"+` property.label::Setting this enables selection of privileged bridges in the UI. Whenno privileged bridge has a `+label+`, then Cockpit will start thebridge that runs `+sudo+`. This is the case in a default Cockpitinstallation. When at least one privileged bridge has a `+label+` thenthe user can select one of them when escalating privileges. As aspecial case, if only one bridge has a `+label+`, then the step ofselecting a bridge is omitted in the UI and that one bridge is alwaysstarted.+Thus, if you add a privileged bridge with a `+label+` in a newmanifest, Cockpit will use that bridge the next time a user opens the"Administrative access" dialog. If you want the user to choose betweenthe `+sudo+` method and your new one, you need to duplicate the`+sudo+` bridge definition in your manifest and give it a label.problem::If a problem is specified, and this bridge fails to start up thenchannels will be closed with this problem code. Otherwise laterbridges or internal handlers for the channel will be invoked.spawn::The command and arguments to invoke.The `+spawn+` and `+environ+` values can be dynamically taken from amatching open command values. When a value in either the `+spawn+` or`+environ+` array contains a named variable wrapped in `+${}+`, thevariable will be replaced with the value contained in the matching opencommand. Only named variables are supported and name can only containletters, numbers and the following symbols: `+._-+`For example a bridges section like:....{"bridges": [{"match": { "payload": "example" },"environ": [ "TAG=${tag}" ],"spawn: [ "/example-bridge", "--tag", "${tag}" ],"problem": "access-denied"}]}....when a open command is received with a payload of `+example+` with`+tag+` value of `+tag1+`. The following command will be spawned....TAG=tag1 /example-bridge --tag tag1....Processes that are reused so if another open command with a "tag" of`+tag1+` is received. The open command will be passed to existingprocess, rather than spawning a new one. However a open command with antag of `+tag2+` will spawn a new command:....TAG=tag2 /example-bridge --tag tag2....If you need to include `+${}+`, as an actual value in your arguments youcan escape it by prefixing it with a `+\+`[[package-replace]]== Replacing an existing packageIf the functionality in a package replaces that of another package thenit can replace that package by claiming the same `+name+` and a higher`+priority+`.For example, a package in the `+/usr/share/cockpit/disks+` directorycould replace Cockpit's _storage_ package with a `+manifest.json+` likethis:....{"version": 0,"name": "storage","priority": 10,"menu": {"index": {"label": "Disk Storage","order": 15}}}....