# Technical Documentation: cockpit-project/cockpit

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/cockpit-project/cockpit)

### Overview (`README.md`)

<p align="center">
    <a href="https://cockpit-project.org/">
      <img src="https://cockpit-project.org/images/site/cockpit-logo-blue.svg" height="75" alt="Cockpit logo">
    </a>
    <br>
    <i>A sysadmin login session in a web browser</i>
</p>

---

[](https://github.com/cockpit-project/cockpit/releases/latest)
[](https://flathub.org/en/apps/org.cockpit_project.CockpitClient)
[](https://repology.org/project/cockpit/versions)
[](https://translate.fedoraproject.org/projects/cockpit/cockpit/)

[](https://matrix.to/#/#cockpit:fedoraproject.org)
[](https://fosstodon.org/@Cockpit)


## What is Cockpit?

Cockpit is an interactive server admin interface. It is easy to use and very lightweight.
Cockpit interacts directly with the operating system from a real Linux session in a browser.

Cockpit makes Linux discoverable, allowing sysadmins to easily perform tasks such as starting
containers, storage administration, network configuration, inspecting logs and so on.

Jumping between the terminal and the web tool is no problem. A service started via Cockpit
can be stopped via the terminal. Likewise, if an error occurs in the terminal, it can be seen
in the Cockpit journal interface.

You can also easily add other machines that have Cockpit installed and are accessible via SSH and jump
between these hosts.

## Installation

> [!NOTE]
> An installation guide can be found at [cockpit-project.org/running.html](https://cockpit-project.org/running.html). Some systems may need extra work, read about it in our [FAQ](https://cockpit-project.org/faq.html#installation)

Cockpit is [available in numerous Linux distributions](https://repology.org/project/cockpit/versions) and is usually packaged as `cockpit`. 

### Flatpak 'Cockpit Client'

We also provide a Flatpak app called [Cockpit Client](https://flathub.org/en/apps/org.cockpit_project.CockpitClient) which includes SSH login support to Linux servers.

## Screenshots

| Light                                                                         | Dark                                                                        |
| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|   |   |
|    |    |
|  |  |

## Contributors

To quickly make code contributions we have a [HACKING.md](./HACKING.md) that outlines the devel environment and running tests. For more in-depth we have a [developer documentation](https://github.com/cockpit-project/cockpit/wiki/Contributing) that links to more resources about setting up the local environment, how designs are proposed, creating and running tests, and our overall contribution process.

Translations are managed in [Weblate](https://translate.fedoraproject.org/projects/cockpit/cockpit/).

## Security

Please see [SECURITY.md](https://github.com/cockpit-project/.github/blob/main/SECURITY.md).

## Licenses

* [LGPL-2.1 or later](./LICENSES/LGPL-2.1.txt)
* [GPL-3.0 or later](./LICENSES/GPL-3.0.txt)
* [BSD-3](./LICENSES/BSD-3-Clause.txt)
* [CC-BY-SA-3.0](./LICENSES/CC-BY-SA-3.0.txt)
* [MIT](./LICENSES/MIT.txt)

## Links

 * Matrix Channel: [`#cockpit:fedoraproject.org`](https://matrix.to/#/#cockpit:fedoraproject.org)
 * [Mailing List](https://lists.fedorahosted.org/admin/lists/cockpit-devel.lists.fedorahosted.org/)
 * [Guiding Principles](https://cockpit-project.org/ideals.html)
 * [Release Notes](https://cockpit-project.org/blog/category/release.html)
 * [Privacy Policy](https://cockpit-project.org/privacy.html)

---

### Overview (`containers/README.md`)

Cockpit Containers
==================

 * [ws](./ws/): Cockpit's web server, for installation on CoreOS; uses SSH to connect to the local host or remote machines
 * [bastion](./bastion/): A reduced variant of the web server that runs unprivileged, and can only connect to remote machines. Suitable for deploying on e.g. Kubernetes. This is currently a prototype.
 * [unit-tests](./unit-tests/): Our project's unit tests run in this container; usually on GitHub PRs, but you can also run it locally for reproducing failures.
 * [flatpak](./flatpak/): Scripts for locally building, running, and testing our [Cockpit Client flatpak](https://github.com/flathub/org.cockpit_project.CockpitClient).

See the individual README.md files in the subdirectories for details.

ws container development
========================

Build the container:

    make ws-container

You can install locally built RPMs by copying them into the `ws/rpms/`
directory.

For fast iteration, you can also build the container with the binaries
(`cockpit-ws`, `cockpit-tls`, etc.) and the pages (`dist/*`) from the local
build tree. For that to work, you *have* to build the project on the same OS as
the container, and configure with `--prefix=/usr`. No warranties, you have to
know what you are doing!

    make ws-container-build-tree

Run the built container and log in interactively as a shell:

    make ws-container-shell

When running docker the 'sudo' command will be used to get necessary
privileges.

---

### Overview (`doc/README.md`)

# Cockpit Docs

We have two distinct workflows for our docs, one relates to bundling documentation within Cockpit packages, the other regards online documentation for Cockpit with features for ease-of-use.

Explanation of existing directories and what their purpose is. Mostly follows an Antora designated directory design that is explained in [Antora folder structure](#antora-folder-structure).

```
doc/                  # All of Cockpit's doc is here; Plugins should be excluded.
├── modules           # Antora expects a module structure, irrelevant for AsciiDoctor.
│   ├── guide         # Cockpit Guide docs, encompasses everything that relates to Cockpit
│   │   ├── bundling  # Specific to AsciiDoctor bundling where we put single-page wrappers
│   │   ├── pages     # All pages that relate to Cockpit itself
│   │   └── nav.adoc  # Antora navigation file, displays pages from the file as a sidebar navigation for the specific component
│   └── man           # Cockpit man pages. Packaged as Manpages with AsciiDoctor, HTML with Antora.
│       ├── pages     # All manpages for packages distributed in cockpit
│       ├── partials  # Antora-designated folder, partials are imported by AsciiDoc manpages for Bugs, and Author
│       └── nav.adoc  # Antora navigation file, displays pages from the file as a sidebar navigation for the specific component
└── antora.yml        # Config for Antora to pickup that designates navigation files and attributes
```

## Antora folder structure

Since Antora is a versatile documentation tool, they have structured directories for managing the modules and module-specific features - like pages and attachments. 

> Antora assigns preset, content-specific behavior to the source files stored in the family directories.

https://docs.antora.org/antora/latest/family-directories/

## Local testing

We do not need to run Antora from cockpit-docs repo to make updates here, instead you can run Antora from NPM to compile it, then simply open the output or run a http server.

With NPM
```
npm exec antora generate antora-playbook.yml
```
With PNPM
```
pnpx antora generate antora-playbook.yml
```
You'll get an output for site generation completed followed by a file to open. If you run Flatpak you might have to run a simple server instead

```
python -m http.server -d ./doc/output/antora
```

---

### Overview (`test/README.md`)

# Integration Tests of Cockpit

This directory contains automated integration tests for Cockpit, and the
support files for them. The architecture of the automated integration tests is
described in [ARCHITECTURE](./ARCHITECTURE.md)

To run the tests on Fedora, refer to the [HACKING](../HACKING.md) guide for
installation of all of the necessary build and test dependencies. There's
no need to trigger a build manually - the test suite preparation step below
will handle that.

If test failures are encountered that look like they may be related to problems
with nested virtualization, refer to
[this Fedora guide](https://docs.fedoraproject.org/en-US/quick-docs/using-nested-virtualization-in-kvm/index.html)
for more details and recommendations on ensuring it is enabled correctly.

## Preparation and general invocation

*Warning*: Never run the build, test, or any other command here as root!

You first need to build cockpit, and install it into a VM:

    test/image-prepare

This uses the default OS image. See `$TEST_OS` below how to select a different one.

See `test/image-prepare --help` for some special modes, like skipping unit
tests, building overlays, or preparing an image with the
[cockpit/ws container](https://quay.io/repository/cockpit/ws) instead of RPMs.
See our scenarios in [test/run](./run) for how they are being used.

In most cases you want to run an individual test in a suite, for example:

    test/verify/check-metrics TestCurrentMetrics.testCPU

You can get a list of tests by inspecting the `def test*` in the source, or by
running the suite with `-l`/`--list`:

    test/verify/check-metrics -l

Sometimes you may also want to run all tests in a test file suite:

    test/verify/check-session

To see more verbose output from the test, use the `-v`/`--verbose` and/or `-t`/`--trace` flags:

    test/verify/check-session --verbose --trace

If you specify `-s`/`--sit` in addition, then the test will wait on failure and
allow you to log into cockpit and/or the test instance and diagnose the issue.
The cockpit and SSH addresses of the test instance will be printed:

    test/verify/check-session -st

You can also run *all* the tests, with automatic parallelism:

    test/common/run-tests --test-dir test/verify

However, this will take *really* long. You can specify a subset of tests (see
`--help`); but usually it's better to run individual tests locally, and let the
CI machinery run all of them in a draft pull request.

The tests will automatically download the VM images they need, so expect
that the initial run may take a few minutes.

## Interactive browser

Normally each test starts its own chromium headless browser process on a
separate random port. To interactively follow what a test is doing:

    TEST_SHOW_BROWSER=1 test/verify/check-session --trace

You can also run a test against Firefox instead of Chromium:

    TEST_BROWSER=firefox test/verify/check-session --trace

See below for details.

## Manual testing

You can conduct manual interactive testing against a test image by starting the
image like so:

     bots/vm-run -s cockpit.socket arch

Once the machine is booted and the cockpit socket has been activated, a
message will be printed describing how to access the virtual machine, via
ssh and web.  See the "Helpful tips" section below.

To login using the web client you have to use the username and password listed below. If you wish to enable `root` login using the web you can remove `root` from `/etc/cockpit/disallowed-users`.

- Username: `admin`
- Password: `foobar`

By default, it's only possible to contact the virtual machine from the host
machine on which it's running.   If you want to conduct manual testing from
other devices on your network, set `TEST_BIND_GLOBAL=1`, for example:

     TEST_BIND_GLOBAL=1 bots/vm-run -s cockpit.socket arch

This will bind the Cockpit and SSH ports to all interfaces, making it possible
to access a URL like http://yourhost.local:9091/ to test Cockpit from another
machine on your LAN.

## Pixel tests

Pixel tests in Cockpit ensure that updates of our dependencies or code changes
don't break the UI: for example slight changes of layout, padding, color and
everything which isn't easily spotted by a human. They also give us confidence
that an update of our UI Framework doesn't introduce changes in how Cockpit
looks.

Pixel tests make a screenshot of a selector and compare it to a known good
reference image. if there is a difference, the test fails and a pixel
difference is shown.

This works as our tests run in the [cockpit/tasks container](https://ghcr.io/cockpit-project/tasks)
which pins the browser and font rendering so repeated runs provide the same
pixels. To generate new pixels, this tasks container must be used; your own
browser and font rendering software might generate different results. For more
information read the ["introduction blog post"](https://cockpit-project.org/blog/pixel-testing.html).

The test images are stored in a git submodule in the `test/reference` directory
and be fetched with:

```sh
./test/common/pixel-tests update
```

As Cockpit tests under multiple distributions and it is not worth the effort to
run pixel tests on every supported distribution we only run them for the
image configured in `test/reference-image`.

Our tests call `Browser.assert_pixels` at interesting and strategic places.
This assertion method requires at least a CSS selector and an image title.
Pixel tests are generated in five layouts by default: desktop, medium, mobile,
dark and rtl.

Take a screenshot of the content in `#detail-content`:
```python
browser.assert_pixels("#detail-content", "filesystem")
```

Take a screenshot of the content in `#detail-content` and ignore all elements
with a class `disk-stats` as they change per test run:
```python
browser.assert_pixels("#detail-content", "filesystem", ignore=[".disks-stats"])
```

Take a screenshot of the content in `#detail-content` and skip it for a
specific layout as it generates unstable pixels:
```python
browser.assert_pixels("#detail-content", "filesystem", skip_layouts=["rtl"])
```

To update pixel tests, locally run the test in the current tasks container, or
create a draft PR and let the tests run for `test/reference-image` and
afterwards fetch the new pixels:

```
./test/common/pixel-tests fetch "https://cockpit-logs.us-east-1.linodeobjects.com/<snip>/log.html"
```

Finally, upload the new pixel tests and commit the newly generated submodule commit:
```
./test/common/pixel-tests push
```

**Note** that you have to a part of the [Contributors group](https://github.com/orgs/cockpit-project/teams/contributors)
to push pixel tests.

## Test Configuration

You can set these environment variables to configure the test suite:

 * `TEST_OS`: The OS to run the tests in, like "fedora-coreos" or
    "debian-testing". See the "cockpit-project/cockpit" section in the
    [test map](https://github.com/cockpit-project/bots/blob/main/lib/testmap.py)
    for all supported values. "fedora-44" is the default (`TEST_OS_DEFAULT` in
    bots' [constants.py](https://github.com/cockpit-project/bots/blob/main/lib/constants.py)).

 * `TEST_BROWSER`: What browser should be used for testing. Currently supported
   values are "chromium" and "firefox". "chromium" is the default.

 * `TEST_SHOW_BROWSER`: Set to run browser interactively. When not specified,
   browser is run in headless mode. When set to "pixels", the browser will be
   resized to the exact dimensions that are used for pixel tests.

 * `TEST_TIMEOUT_FACTOR`: Scale normal timeouts by given float value. Useful for
   slow/busy testbeds or architectures.

See the [bots documentation](https://github.com/cockpit-project/bots/blob/main/README.md)
for details about the tools and configuration for these.

## Convenient test VM SSH access

It is recommended to add a snippet like this to your `~/.ssh/config`. Then
you can log in to test machines without authentication:

    Match final host 127.0.0.2
        User root
        StrictHostKeyChecking no
        UserKnownHostsFile /dev/null
        CheckHostIp no
        IdentityFile CHECKOUT_DIR/bots/machine/identity
        IdentitiesOnly yes

You need to replace `CHECKOUT_DIR` with the actual directory where you cloned
`cockpit.git`, or `bots.git` if you have a separate clone for that.

Many cockpit developers take it a step further, and add an alias to
allow typing `ssh c`:

    Host c
        Hostname 127.0.0.2
        Port 2201

The `final` keyword in the first rule will cause it to be checked (and matched)
after the `Hostname` substitution in the `c` rule.

## Fast develop/test iteration

Each `image-prepare` invocation will always start from the pristine image and
ignore the current overlay in `test/images`. It is thorough, but also rather
slow. If you want to iterate on changing only JavaScript/HTML code, as opposed
to the bridge or webserver, the whole build and test cycle can be done much
faster.

You always need to do at least one initial `test/image-prepare $TEST_OS` run.
Afterwards it depends on the kind of test you want to run.

### Nondestructive tests

Many test methods or classes are marked as `@nondestructive`, meaning that
they restore the state of the test VM enough that other tests can run
afterwards. This is the fastest and most convenient situation for both
iterating on the code and debugging failing tests.

Start the prepared VM with `bots/vm-run $TEST_OS`. Note the SSH and cockpit
ports. If this is the only running VM, it will have the ports in the
examples below, otherwise the port will be different.

Then start building the page you are working on
[in watch and rsync mode](../HACKING.md#working-on-cockpits-session-pages), e.g.

    RSYNC=c ./build.js -w users

(Assuming the `c` SSH alias from the previous section and first running VM).

Then you can run a corresponding test against the running VM, with additional
debug output:

    TEST_OS=... test/verify/check-users -t --machine 127.0.0.2:2201 --browser 127.0.0.2:9091 TestAccounts.testBasic

### Destructive tests

Other tests need one or more fresh VMs. Instead of a full `test/image-prepare`
run (which is slow), you can update the existing VM overlay with updated
bundles. Start the build in watch mode, but without rsyncing, e.g.

    ./build.js -w storaged

and after each iteration, copy the new bundles into the VM overlay:

    bots/image-customize -u dist:/usr/share/cockpit/ $TEST_OS

Then run the test as you would normally do, e.g.

    TEST_OS=... test/verify/check-storage-stratis -t TestStorageStratis.testBasic

Use `bots/vm-reset` to clean up all prepared overlays in `test/images`.

## Debugging tests

If you pass the `-s` ("sit on failure") option to a test program, it
will pause when a failure occurs so that you can log into the test
machine and investigate the problem.

A test will print out the commands to access it when it fails in this
way. You can log into a running test-machine using ssh.  See the
"Helpful tips" section below.

You can also put calls to `sit()` into the tests themselves to stop them
at strategic places.

That way, you can run a test cleanly while still being able to make
quick changes, such as adding debugging output to JavaScript.

## Guidelines for writing tests

If a test is not decorated with `@nondestructive`, it is OK for a test to
destroy the test machine OS installation, or otherwise modify it without
cleaning up.  For example, it is OK to remove all of `/etc` just to see what
happens.  The next test will get a pristine test machine.

Tests decorated with `@nondestructive` will all run against the same test
machine. The nondestructive test should clean up after itself and restore the
state of the machine, such that the next nondestructive test is not impacted.

A fast running test suite is more important than independent,
small test cases.

Thus, it is OK for tests to be long.  Starting the test machine is so slow that
we should run as many checks within a single session as make sense. Note that
nondestructive tests do not suffer from this, and are much quicker.

Still, within a long test, try to have independent sections, where
each section returns the machine to more or less the state that it was
in before the section.  This makes it easier to run these sections
ad-hoc when doing incremental development.

## Coverage

Every pull request will trigger a `$DEFAULT_OS/devel` scenario which creates a
coverage report of the JavaScript code executed and writes comments about
uncovered code in the pull request. The overall coverage percentage is recorded
in prometheus for a subset of our projects and [visualized in Grafana](https://grafana-cockpit.apps.ocp.cloud.ci.centos.org/d/ci/cockpit-ci?orgId=1).

To generate coverage locally for `TestApps`:

```
export NODE_ENV=devel
./build.js
./test/image-prepare -q
./test/common/run-tests --test-dir test/verify --coverage TestApps
```

Code which is impossible or very hard to test in our tests can be excluded from
appearing in a pull request as comment by adding a `not-covered` comment with a
short justification:

```javascript
return cockpit.script(data, { superuser: "try", err: "message" })
              .catch(console.error); // not-covered: OS error
```

## Helpful tips

For web access, if you'd like to avoid Chromium (or Chrome) prompting
about certificate errors while connecting to localhost, you can change
the following setting:

    chrome://flags/#allow-insecure-localhost

---

### Overview (`containers/ws/README.md`)

# Cockpit webserver container

[Cockpit](https://cockpit-project.org/) is a web-based graphical interface for Linux servers.
It is [packaged in most major Linux distributions](https://cockpit-project.org/running.html).

This container image provides Cockpit's web server and a subset of available pages (like the cockpit-system package)
for deployment on container hosts such as Fedora CoreOS or Kubernetes, where installing rpms is difficult or impossible.

## Usage on container host distributions

The standard Fedora and Red Hat Enterprise Linux CoreOS images do not contain Cockpit packages. The
`cockpit/ws` container includes a minimal set of builtin Cockpit pages which are being used when connecting to
such a machine, i.e. a host which doesn't have the `cockpit-bridge` package installed.

If these builtin pages are not enough for your use cases, you can install desired Cockpit packages
as overlay RPMs. For example:

```
rpm-ostree install cockpit-system cockpit-ostree cockpit-podman
reboot
```

Depending on your configuration, you may want to use
[other extensions](https://packages.fedoraproject.org/search?query=cockpit-) as
well, such as `cockpit-podman` or `cockpit-networkmanager`.

If you have a custom-built OSTree, simply include the same packages in your build.

These packages are enough when the CoreOS machine is only connected to through another host running Cockpit.

You also need to run a Cockpit web server somewhere, as the "entry point" for browsers. That can
then connect to the local host or any remote machine via ssh to get a Cockpit UI for that machine.

This web server can be deployed as container. It has two modes, which are described below.

## Privileged ws container

Privileged mode provides tight integration with the host: You can use the
host's users and their passwords to log in, and Cockpit's branding will adjust
to the host operating system.

1. Enable password based SSH logins, unless you only use [SSO logins](https://docs.cockpit-project.org/cockpit-guide/latest/guide/sso.html).
   From localhost only:
   ```bash
    cat << EOF > /etc/ssh/sshd_config.d/02-enable-passwords.conf
    # enables Cockpit web login without exposing password auth to network
    Match Address 127.0.0.1,::1
      PasswordAuthentication yes
    EOF
   ```
   Or from any host:
   ```bash
    echo 'PasswordAuthentication yes' | sudo tee /etc/ssh/sshd_config.d/02-enable-passwords.conf
   ```
   ```
   sudo systemctl try-restart sshd
   ```

3. Run the Cockpit web service with a privileged container (as root):
   ```
   podman container runlabel --name cockpit-ws RUN quay.io/cockpit/ws
   ```

   You can append additional [cockpit-ws CLI options](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit-ws.8.html),
   most commonly to change the port:
   ```
   podman container runlabel --name cockpit-ws RUN quay.io/cockpit/ws -- -p 80
   ```

   Run this to show the "RUN" label command if you want to run the container with different arguments:
   ```
   podman container runlabel --display RUN quay.io/cockpit/ws
   ```

4. Make Cockpit start on boot:
   ```
   podman container runlabel INSTALL quay.io/cockpit/ws
   systemctl enable cockpit.service
   ```

Afterward, use a web browser to log into port `9090` on your host IP address as usual.

## Unprivileged bastion container

When you run the container without `--privileged`, it presents an unbranded
login page. The user always has to specify a host name, and it connects to that
host with SSH.

```
podman run -d --name cockpit-bastion -p 9090:9090 quay.io/cockpit/ws
```

This mode is suitable for deploying to e.g. Kubernetes or similar environments
where you cannot have or want privileged containers. In this "bastion host
mode", you can get a Cockpit for servers in your data center without opening an
extra port for cockpit-ws on them.

You can still log into the ws container's host if you specify its *public*
address. [podman](https://podman.io/) provides the special name
`host.containers.internal` for that.

### Configuration

By default, the container uses an internal
[cockpit.conf](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit.conf.5.html)
which sets `RequireHost = true` and a neutral login title. You can customize
this by passing your own configuration as a volume:

    -v /path/to/your/cockpit.conf:/etc/cockpit/cockpit.conf:ro,Z

Similarly you can also provide a custom `/etc/os-release` to change the
branding.

### SSH authentication: Share keys with container

The login page asks the user to confirm unknown SSH host key fingerprints.  You
can mount your known host keys into the container at
`/etc/ssh/ssh_known_hosts`, or set the environment variable
`COCKPIT_SSH_KNOWN_HOSTS_FILE` to specify a different location:

    -v /path/to/known_hosts:/etc/ssh/ssh_known_hosts:ro,Z

You can also mount encrypted private keys inside the container. You can set an environment variable, `COCKPIT_SSH_KEY_PATH_MYHOST`, where `MYHOST` is the uppercased hostname used in the `Connect to` field, and cockpit will use that private key to login for the specified host. Private keys can be set for multiple hosts this way by changing the value of `MYHOST`. You can also set an environment variable, `COCKPIT_SSH_KEY_PATH`, which will be used as a fallback key if no host-specific key is set:

    -e COCKPIT_SSH_KEY_PATH_MYHOST=/.ssh/myhost_id_rsa \
    -e COCKPIT_SSH_KEY_PATH_MYSERVER=/.ssh/myserver_id_rsa \
    -e COCKPIT_SSH_KEY_PATH_192.168.1.1=/.ssh/another_id_rsa \
    -e COCKPIT_SSH_KEY_PATH=/.ssh/id_rsa \
    -v ~/.ssh/:/.ssh:ro,Z

Private keys can be encrypted; then cockpit uses the provided password to decrypt the key.

### SSH authentication: Share SSH agent with container

Alternatively, if you use [ssh-agent](https://linux.die.net/man/1/ssh-agent) on
your host, you can share it with the container and run the container as your
own user (*not* as system container!). Then logging into remote machines from
Cockpit's login page re-uses the loaded private keys. For that, bind-mount the
agent socket in the container and tell it its path. If your host has SELinux
enabled, you need to disable the isolation for the container, so that it is
allowed to connect to the agent socket:

    -v $SSH_AUTH_SOCK:/ssh-agent \
    -e SSH_AUTH_SOCK=/ssh-agent \
    --security-opt=label=disable

## More Info

 * [Cockpit Project](https://cockpit-project.org)
 * [Cockpit Development](https://github.com/cockpit-project/cockpit)
 * [cockpit/ws quay.io page](https://quay.io/repository/cockpit/ws)

---

### Overview (`examples/long-running-process/README.md`)

## Launch and reattach to a long-running process

Cockpit pages should never spawn long-running precious processes, as a Cockpit
session is not reliably connected for a long time:  Network interfaces go down
or  roam, TCP timeouts happen, browsers crash, tabs get closed accidentally,
batteries drain, people close/suspend their laptops, and so on.

Most services that Cockpit talks to manage their own runtime state and jobs,
such as udisks, libvirt, or podman. But this is not the case for e.g. running
Ansible playbooks or installer scripts. These should be wrapped into a
transient service unit, so that systemd takes over the role of the job manager.

This example demonstrates how to do that. It runs an arbitrary shell command in
a transient cockpit-longrunning.service, shows the live log output, and
reattaches to it on page load. I.e. you can start the process, log out, log
back in, and the service is recognized as "already running" with the complete
output.

For your own use case, the most important design question is the choice of the
service name, especially if the page can manage more than one process (such as
launching parallel playbooks with different environment variables). It must
contain exactly all the identifying properties in the name, so that it is
predictable.

If your page does manage multiple processes in parallel, it needs to enumerate
running units with [ListUnits()](https://www.freedesktop.org/wiki/Software/systemd/dbus/).
This example page only manages one at a time, and thus just uses a `GetUnit()` call
with a known name.

---

### Overview (`src/tls/README.md`)

cockpit TLS proxy
=================

`cockpit-tls` is a TLS terminating HTTP proxy for cockpit-ws. TLS termination
should not be done directly in `cockpit-ws`, as that is hard to audit and trust:
This code is subject to external attacks through HTTP, does a *lot* of
interpretation of data streams, uses a lot of external dependencies, and
multiplexes sessions of different users in one process. Thus any vulnerability
can easily lead to complete privilege escalation across all running and future
sessions.

By isolating the TLS termination and certificate checking from the http
processing, and isolating http processing for each client certificate, Cockpit
can do certificate based client authentication (smart cards, browser-imported
certificates, and similar) in a sufficiently robust manner. Any tampering with
a `cockpit-ws` process is then unable to affect other sessions and the
certificate attestation.

Design goals
------------

 * Run TLS termination (cockpit-tls) and the actual data interpretation and
   sessions (cockpit-ws) in separate security contexts, so that vulnerabilities
   in the HTTP session cannot affect the trust put into the TLS authentication.
 * Run separate cockpit-ws processes for each TLS client certificate, plus one
   instance for "https without certificate", and another one for "unencrypted
   http", so that one tampered session cannot affect the others.
 * cockpit-tls *does not* interpret the HTTP data stream, to avoid risking any
   vulnerability. It is only allowed to inspect the first byte of a new
   connection to decide between TLS or plain HTTP, and do the TLS negotiation
   and encryption/decryption. In other words, it treats the payload on the TLS
   connection as a black box.
 * Minimal dependencies: Only glibc and a TLS library (GnuTLS at the moment),
   so that the code can be audited (by humans or things like coverity) more
   easily.

Structure
---------

```
+---------+  http://machine:9090                           +------------------------+
|Browser A|+----------------------+                    +-->|cockpit-ws http instance|
+---------+                       |                    |   +------------------------+
                                  |                    |
                                  |                    +   (plain HTTP over Unix socket)
                                  |                    |
+---------+  https://machine:9090 |    +------------+  |   +------------------------------------+
|Browser B|+----------------------+--->| cockpit-tls|--+-->|cockpit-ws https instance for cert B|
+---------+    client cert B      |    +------------+  +   +------------------------------------+
                                  |                    |
                                  |                    |
+---------+  https://machine:9090 |                    |   +------------------------------------+
|Browser C|+----------------------+                    +-->|cockpit-ws https instance for cert C|
+---------+    client cert C                               +------------------------------------+
```

The startup of these instances and isolating them from one another makes heavy
use of systemd features.

 * The top-level unit that the admin enables/controls is [cockpit.socket](../ws/cockpit.socket.in),
   which listens to the port (9090 by default).
 * That activates [cockpit.service](../ws/cockpit.service.in), which ensures
   that a server TLS certificate is present and starts `cockpit-tls`, usually
   as user `cockpit-ws` (for historical reasons, to avoid having to change the
   ownership of `/etc/cockpit/ws-certs.d/*` on upgrades).
 * When accepting a connection, cockpit-tls checks if it uses TLS:
   - If not, it connects to [cockpit-wsinstance-http.socket](../src/ws/cockpit-wsinstance-http.socket.in) or
     [cockpit-wsinstance-http-redirect.socket](../src/ws/cockpit-wsinstance-http-redirect.socket.in).
   - If the connection does use TLS, it calculates the fingerprint of the
     client certificate (using the empty string if there is none), and connects
     to [cockpit-wsinstance-https-factory.socket](../src/ws/cockpit-wsinstance-https-factory.socket.in).
     This starts a helper factory process `cockpit-wsinstance-factory` that
     reads the fingerprint from stdin, and asks systemd to start a new
     [cockpit-wsinstance-https@fingerprint.socket](../src/ws/cockpit-wsinstance-https@.socket.in)
     and .service pair.
 * Each instance runs in its own systemd cgroup, as another unprivileged
   dynamic system user `cockpit-wsinstance-socket`.
 * cockpit-tls exports the client certificates to `/run/cockpit/tls/<fingerprint>`
   while there is at least one open connection with that certificate, i. e. as
   long as there is an active Cockpit session.

Client certificate authentication
---------------------------------

This must be explicitly enabled in cockpit.conf with `ClientCertAuthentication = yes`,
see the [guide](../../doc/guide/cert-authentication.xml) and
[manpage](../../doc/man/cockpit.conf.xml). Then cockpit-tls will ask the
browser for a client certificate.

Commonly this is provided by a smart card, but it's equally possible to import
certificates directly into the web browser.

This requires the host to be in an Identity Management domain like
[FreeIPA](https://www.freeipa.org/) or [Active
Directory](https://en.wikipedia.org/wiki/Active_Directory), which can associate
certificates to users. See the [FreeIPA User Certificates
documentation](https://www.freeipa.org/page/V4/User_Certificates) for details.

The `sssd-dbus` package must be installed for this to work.

If the web browser presents a client certificate, cockpit-tls will write this
certificate to `/run/cockpit/tls`.   If cockpit-ws sees that cockpit-tls
exports a certificate for its connection (by checking its cgroup instance name,
which is the certificate fingerprint, and checking /run/cockpit/tls for it),
then it will request the `tls-cert` authentication schema from cockpit-session,
instead of the usual `basic` or `gssapi`. cockpit-session then uses the content
of this file to ask sssd to map the certificate to a username.  If the mapping
is successful, cockpit-session sets the user name and opens the PAM session
without further authentication.


Code layout
-----------

 * A `Connection` (in `connection.[hc]`) object represents a single TCP
   connection from a client (browser) towards cockpit-tls. Each connection is
   handled in its own thread, so that blocked connections cannot starve others.
   It has the code for launching ws instances and shoveling data back and forth
   between the browser and the ws instance.

 * A `Server` (in `server.[hc]`) object represents the cockpit-tls logic. It is
   a singleton (not instantiated), and mostly split out into a separate object
   so that it can be properly unit tested. It maintains some global
   configuration, listens to the port, and coordinates the connection threads.
   and `WsInstance` objects according to incoming requests.

 * `certfile.[hc]` deals with exporting current certificates to
   /run/cockpit/tls/, and the refcounting from all Connections that belong to a
   particular certificate.

The other files are helpers or unit tests.

---

### Overview (`tools/mock-build-env/README.md`)

These are some stub pkg-config and include files that allow us to run
`make dist` in a restricted build environment without the regular build
dependencies, in particular [Packit](https://packit.dev/)'s [sandcastle container](https://github.com/packit/sandcastle).

These are used in [packit.yaml](./packit.yaml).

---

### Doc Anaconda (`doc/anaconda.md`)

Cockpit Storage in Anaconda Mode
================================

Anaconda (the OS Installer) can open the Cockpit "storaged" page for
advanced setup of the target storage devices. When this is done,
storaged is in a special "Anaconda mode" and behaves significantly
different.

In essence, the storaged page restricts itself to working with the
target environment. It will hide the real root filesystem (on the USB
stick that the Live environment was booted from, say), but let the
user create a "fake" root filesystem on some block device.

Entering Anaconda mode
----------------------

The "storaged" page is put into Anaconda mode by storing a
"cockpit_anaconda" item in its `window.sessionStorage`.  The value
should be a JSON encoded object, the details of which are explained
below.

Ignoring storage devices
------------------------

Anaconda needs to tell Cockpit which devices can be used to install
the OS on. This is done with the "available_devices" entry, which is
an array of strings.

```json
{
  "available_devices": [ "/dev/sda" ]
}
```

This list should only contain entries for top-level block devices. It
should not contain things like partitions, device mapper devices, or
mdraid devices.

Mount point prefix
------------------

Cockpit can be put into a kind of "chroot" environment by giving it a
mount point prefix like so:

```json
{
  "mount_point_prefix": "/sysroot"
}
```

This works at the UI level: filesystems that have mount points outside
of "/sysroot" are hidden from the user, and when letting the user work
with mount points below "/sysroot", the "/sysroot" prefix is omitted
in the UI. So when the user says to create a filesystem on "/var",
they are actually creating one on "/sysroot/var".

However, Cockpit (via UDisks2) will still write the new mount point
configuration into the real /etc/fstab (_not_
/sysroot/etc/fstab). This is done for the convenience of Cockpit, and
Anaconda is not expected to read it.

BIOS or EFI
-----------

Anaconda needs to tell Cockpit whether a BIOS or a EFI system is being
installed. This controls which kind of special partitions can be
created easily.

This is done by setting the "efi" flag to true or false:

```json
{
  "efi": true
}
```

Default filesystem type
-----------------------

Cockpit tries to be smart about which filesystem type to select by
default when formatting something.  In normal operation, it will fall
back to the type of the filesystem mounted as "/". When in Anaconda
mode, there might not be anything assigned to "/" yet, and in this
case, Cockpit will use the type from "default_fsys_type".

```json
{
  "default_fsys_type": "xfs"
}
```

Exported information
--------------------

Cockpit maintains some information in the session storage that can be
used by Anaconda to learn things that it doesn't get from blivet. This
is mostly information from fstab.

The "cockpit_mount_points" entry in local storage will have a JSON
encoded object, for example:

```json
{
  "/dev/sda": {
    "type": "filesystem",
    "dir": "/"
  },
  "/dev/sdb": {
    "type": "swap"
  },
  "/dev/sdc": {
    "type": "crypto",
    "content": {
      "type": "filesystem",
      "subvolumes": {
        "home": { "dir": "/home" }
      }
    }
  }
}
```

The keys are pathnames of device nodes in /dev.

Each value is an object with a "type" field. The type determines which
other fields might be present, and what they mean.  The following
types might appear:

 - "filesystem"

 A filesystem with an entry in fstab. A filesystem without subvolumes
 has a "dir" field that is its mount point. A filesystem with
 subvolumes has a "subvolumes" field that is a map from subvolume
 names to mount points.

 There might also be both a "dir" and a "subvolumes" field. The "dir"
 field then has the mount point for the default subvolume of the
 filesystem. This is hopefully rare.

 - "swap"

 A swap device. No other fields are present.

 - "crypto"

 An encrypted device. It has a "content" field with a value that is
 structured like a value for "cockpit_mount_points", i.e., a object
 with a "type" field and maybe a "dir" field if "type" is
 "filesystem". This might also be present when the crypto device is
 closed.

 It might also have a "cleartext_device" field if the encrypted device
 is currently open.

Cockpit does some magic (via the "x-parent" options in fstab and
crypttab) to produce information also for locked LUKS devices, and
inactive logical volumes.

Cockpit also remembers and exports encryption passphrases in session
storage, in the "cockpit_passphrases" entry. This is a map from device
names to cleartext passphrases. This is only done when Cockpit runs in
a "secure context", see
https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts

Cockpit Networking in Anaconda Mode
===================================

The Cockpit networking page behaves differently when it is
used in Anaconda mode. Instead of the full networking page it shows a
simplified layout that is adjusted for the requirements of the Anaconda
installer.

Entering Anaconda mode
----------------------

Anaconda mode is activated the same way as on the storage page, by
storing a "cockpit_anaconda" item in `window.sessionStorage`. The value
should be a JSON encoded object. Unlike on the storage page, none of the
contents are actually used by the networking page, so setting any valid
JSON encoded object is enough to activate the Anaconda layout.

The code for this alternative layout lives in
`pkg/networkmanager/anaconda` and is a simplified version of the regular
networking page.

---

### Doc Antora.Yml (`doc/antora.yml`)

name: cockpit-guide
version: true
title: Cockpit
start_page: guide:index.adoc
nav:
- modules/guide/nav.adoc
- modules/man/nav.adoc

---

### Doc Authentication (`doc/authentication.md`)

Cockpit Authentication
======================

Cockpit authorizes users by looking at the Authorization header on requests
to /login. Cockpit will attempt to perform the start the authentication command that
is configured for the auth scheme contained in the header.

To configure an auth scheme add a section to cockpit.conf for that scheme. For example
to configure an command for the "Bearer" auth scheme your cockpit.conf should contain
the following section.

```
[bearer]
command = example-verify-token
timeout = 300
```

The command is then responsible to:

 * verify the given credentials
 * setup an appropriate session and environment based on those credentials
 * launch a bridge that speaks the cockpit protocol on stdin and stdout.

The default command is `cockpit-session` it is able to handle Password
(`[basic]`), Kerberos/GSSAPI (`[negotiate]`), and TLS client certificate
(`[tls-cert]`) authentication.

Authentication commands are called with a single argument which is the host that the user
is connecting to. They communicate with their parent process using the cockpit protocol on
stdin and stdout.

Instead of `Command`, it's also possible to specify `UnixPath` to connect to a unix socket at a
given path.  The protocol is the same.

Credentials can then be retrieved by issuing a authorize command with a challenge. The challenge
should correspond to the authorization type in header (ei: Basic or Bearer). For example:


```
{
    "command": "authorize",
    "cookie": "cookie",
    "challenge": "*"
}
```

The response will look something like this

```
{
    "command": "authorize",
    "cookie": "cookie",
    "response": "Basic dXNlcjpwYXNzd29yZAo=",
    "remote-peer": "1.2.3.4",
}
```

`remote-peer` is the IP address of the connecting user, if known (otherwise
unset).

A `*` challenge requests whatever credentials the parent process has. Most auth
commands will want to begin by issuing a `*` challenge.

By default cockpit-ws will wait a maximum of 30 seconds to receive this response.
The number of seconds to wait can be adjusted by adding a timeout parameter along
side the auth schema configuration in your config file. The given value should be
a number between 1 and 900.

If more information is needed the command should respond with a `X-Conversation` challenge.
This takes the following format.

```
X-Conversation nonce base64(prompt message)
```

The message will be displayed to the user and the user will be prompted for a response.
If the user does not respond within 60 seconds the command will be closed and the login
aborted. The number of seconds to wait can be adjusted by adding a response-timeout parameter
along side the auth schema configuration in your config file. The given value should be a
number between 1 and 900.

Once a result is known a "init" command should be sent. If the login was successful you can usually
just let the bridge do this.

If the login was not successful the JSON should include a problem field. Values of
`authentication-failed`, `authentication-unavailable` or `access-denied`
are translated to the appropriate cockpit error codes. Any other values are treated
as generic errors. Additionally a message field may be included as well.

If an process exits without sending a init command, that will be treated as an internal error.

If the authentication command has additional data that it would like to return with a successful response
it can do so by sending a `x-login-data` challenge. The command should have an additional JSON field
`login-data`. The string placed there will be returned by along with a successful json response.

For a simple python example see
[cockpit-auth-ssh-key](https://github.com/cockpit-project/cockpit/blob/main/containers/ws/cockpit-auth-ssh-key).

Remote machines
---------------

Cockpit also supports logging directly into remote machines. The remote machine to
connect to is provided by using a application name that begins with `cockpit+=`.
The default command used for this is `python3 -m cockpit.beiboot`, which
invokes `ssh`.

The section `Ssh-Login` defines the options for all ssh commands. The section
has the same options as the other authentication sections with the following additions.

 * `host` The default host to log into. Defaults to 127.0.0.1. That host's key
   will not be checked/validated.
 * `connectToUnknownHosts`. By default cockpit will refuse to connect to any machines that
   are not already present in ssh's global `known_hosts` file (usually
   `/etc/ssh/ssh_known_hosts`). Set this to `true` is to allow those connections
   to proceed.

After the user authentication with the `"*"` challenge, if the remote
host is not already present in any local `known_hosts` file, this will send an
`"x-host-key"` challenge:

```
{
    "command": "authorize",
    "challenge": "x-host-key"
    "cookie": "cookie",
}
```

The caller responds to that with either a valid key like below, or an empty
string response if there is no available key.

```
{
    "command": "authorize",
    "cookie": "cookie",
    "response": "ssh-rsa AAAA1234...",
}
```

Client certificate authentication
---------------------------------
When a machine is joined to an Identity Management domain (like
[FreeIPA](https://www.freeipa.org) or Active Directory) which has [client-side
user certificates](https://www.freeipa.org/page/V4/User_Certificates) set up,
then these can be enabled for authentication to Cockpit by setting this option
in cockpit.conf:

```
[WebService]
ClientCertAuthentication = yes
```

This uses the `[tls-cert]` authentication scheme.

When enabling this mode, other authentication types commonly get disabled. See
the next section for details.

See the [Certificate/smart card authentication guide](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cert-authentication.html)
for details how to set this up.

Actions
-------

Setting an action can modify the behavior for an auth scheme. Currently two actions
are supported.

 * **remote-login-ssh** Use the `Ssh-Login` section instead.
 * **none** Disable this auth scheme.

To configure an action add the `action` option. For example to disable basic authentication,
cockpit.conf should contain the following section:

```
[basic]
action = none
```

Likewise, if the machine is part of a Kerberos domain, but that should not be used to
authenticate to cockpit, create the following section:

```
[negotiate]
action = none
```

Limits
------

Cockpit can be configured to limit the number of concurrent login processes. See cockpit.conf
for more details. This will affect how many custom authentication processes can be launched.

Environment Variables
---------------------

The following environment variables are set by cockpit-ws when spawning an auth process for
SSH connections:

 * **COCKPIT_SSH_CONNECT_TO_UNKNOWN_HOSTS** Set to `1` to  allow connecting to
   hosts that are not present in the current `known_hosts` files. If not set,
   Cockpit will only connect to unknown hosts if either the remote peer is
   local, or if the `Ssh-Login` section in `cockpit.conf` has an
   `connectToUnknownHosts` option set to a true value (`1`, `yes` or `true`).

 * **COCKPIT_SSH_KNOWN_HOSTS_FILE** Path to knownhost files. Defaults to
   `PACKAGE_SYSCONF_DIR/ssh/ssh_known_hosts`

Login process: User/Password
----------------------------

This section entirely ignores cockpit-tls for simplicity, as it is not involved in the authentication. See [cockpit-tls docs](../src/tls/README.md) for details.

 1. _User_ connects to cockpit URL.
 2. _cockpit-ws_ responds with "401 Authentication failed" and sends the Login page, unless when Kerberos is available (see below) or the server is configured to not use any authentication.
 3. _User_ fills in username/password and clicks "Log In". The login page sends a new request to _cockpit-ws_ with an `Authorization: Basic base64(user:password)` header
 4. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session `Command` or `UnixPath` for `basic`. If not, defaults to `cockpit-session` (via `UnixPath = /run/cockpit/session`). It parses user/password from the header and spawns the session command with the target host as argument.
 5. _cockpit-session_ sends an `authorize` command with a `*` challenge (see above) to _cockpit-ws_, which responds with the user/password
 6. _cockpit-session_ starts a _PAM_ session for the user, and sets the initial credential to the received password
 7. If _PAM_ sends more messages, like e.g. 2FA prompts or changing expired passwords:
    * _cockpit-session_ sends corresponding `X-Conversation` authorize messages (see above) to _cockpit-ws_
    * _cockpit-ws_ forwards them to the Login page, which displays the text, and sends the user response as the authorize reply
    * _cockpit-ws_ forwards the authorize reply to PAM
 8. When PAM succeeds, _cockpit-session_ executes the bridge and the session starts.

```mermaid
sequenceDiagram
    participant User
    participant cockpit-ws
    participant cockpit-session
    participant PAM

    User->>cockpit-ws: GET https://server:9090
    cockpit-ws->>User: 401 + Login page
    User->>cockpit-ws: GET with Authorization: Basic
    cockpit-ws->>cockpit-session: Spawn
    cockpit-session->>cockpit-ws: authorize command with * challenge
    cockpit-ws->>cockpit-session: user/password
    cockpit-session->>PAM: pam_authenticate()
    PAM-->>cockpit-session: Success or conversation
    opt 2FA/password change
        cockpit-session->>cockpit-ws: X-Conversation challenge
        cockpit-ws->>User: Display prompt
        User->>cockpit-ws: Response
        cockpit-ws->>cockpit-session: Forward response
        cockpit-session->>PAM: Continue conversation
    end
    PAM->>cockpit-session: Success
    cockpit-session->>PAM: open_session()
    cockpit-ws->>User: 200 OK for the login page
```

Login process: Kerberos/GSSAPI
-------------------------------

 1. _User_ connects to cockpit URL.
 2. _cockpit-ws_ responds with "401 Authentication failed" and includes `WWW-Authenticate: Negotiate` header (if Kerberos is available)
 3. _Browser_ (if configured for SPNEGO/Kerberos) requests a service ticket from the _KDC_ for the HTTP service principal
 4. _Browser_ sends a new request with `Authorization: Negotiate <base64-gssapi-token>` header
 5. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `negotiate`. If not, defaults to `cockpit-session` and runs it in the same way as above.
 6. _cockpit-session_ calls `gss_accept_sec_context()` with the GSSAPI token to verify the Kerberos ticket
 7. If GSSAPI returns `GSS_S_CONTINUE_NEEDED` (multi-round negotiation):
    * _cockpit-session_ sends an authorize command with a `Negotiate` challenge containing the output token to _cockpit-ws_
    * _cockpit-ws_ responds with "401 Authentication failed" and `WWW-Authenticate: Negotiate <token>` to the _Browser_
    * _Browser_ sends another `Authorization: Negotiate <token>` request
    * This continues until GSSAPI negotiation completes
 8. When GSSAPI succeeds, _cockpit-session_ has the authenticated GSSAPI principal name
 9. _cockpit-session_ maps the GSSAPI name to a local username using `gss_localname()` (which applies configured mapping rules), or if that fails, falls back to `gss_display_name()` which returns the principal name as-is (e.g. `user@REALM.COM`)
 10. _cockpit-session_ starts _PAM_, skipping the auth stack (as GSSAPI already authenticated), and runs the account, credential, and session stacks
 11. _cockpit-session_ stores the delegated Kerberos credentials (if delegation was negotiated) in a credential cache at `/run/user/<uid>/cockpit-session-<pid>.ccache` and sets `KRB5CCNAME` in the PAM environment, so that the bridge can use them for accessing other Kerberos-protected services (like SSH to remote machines)
 12. Authentication completes, and the session starts as above.

Login process: Client Certificate
----------------------------------

Unlike the other sections, this one involves _cockpit-tls_ as well as it provides a crucial part of the authentication.

 1. _User_ connects to cockpit URL with a client certificate, which lands at _cockpit-tls_
 2. _cockpit-tls_ calculates the SHA256(certificate) as the user fingerprint
 3. _cockpit-tls_ connects to the `cockpit-wsinstance-https@<fingerprint>` systemd socket/service (starting a dedicated _cockpit-ws_ instance for this certificate if needed). See [cockpit-tls docs](../src/tls/README.md) and [systemd units](../src/systemd/) for details.
 4. _cockpit-tls_ exports the certificate to `/run/cockpit/tls/clients/<fingerprint>` (kept as long as there is at least one active connection with that certificate)
 5. _cockpit-tls_ includes `"client-certificate": "<fingerprint>"` in its mini JSON protocol to _cockpit-ws_
 6. _cockpit-ws_ detects the client certificate metadata and uses `tls-cert <fingerprint>` as the authorization type
 7. _cockpit-ws_ looks at `cockpit.conf` whether it has a customized session command for `tls-cert`. If not, defaults to `cockpit-session` and runs it in the same way as above.
 8. _cockpit-session_ receives the `tls-cert <fingerprint>` authorization and reads the certificate from `/run/cockpit/tls/clients/<fingerprint>`
 9. _cockpit-session_ validates that the certificate file exists and matches the expected _cockpit-ws_ cgroup
 10. _cockpit-session_ calls the _sssd_ D-Bus API (`org.freedesktop.sssd.infopipe.Users.FindByCertificate`) to map the certificate to a username
 11. When successful, _cockpit-session_ sets the username and starts _PAM_, skipping the auth stack (as the certificate itself was the authentication), and runs the account, credential, and session stacks
 13. Authentication completes, and the session starts as above.

Login process: SSH to remote machine
------------------------------------

 1. _User_ connects to a URL like `https://server:9090/=hostname`) or sets the "Connect to:" field in the Login page to `hostname`.
 2. _login.js_ sends the login HTTP request with `Authorization: Basic base64(user:password\0known_hosts)` header, where `known_hosts` contains any previously-stored SSH host keys for the target host from the browser's `localStorage`
 3. _cockpit-ws_ extracts the target host from the URL. As it has a host name, it looks at `cockpit.conf` for the `[Ssh-Login]` section's `Command` or `UnixPath`. If not customized, defaults to `cockpit.beiboot`
 4. _cockpit-ws_ spawns the ssh command with the target host as argument
 5. _cockpit.beiboot_ sends an `authorize` command with a `*` challenge to _cockpit-ws_, which responds with the user/password/known_hosts from the `Authorization` header (same as User/Password step 5)
 6. _cockpit.beiboot_ parses the credentials, writes any received `known_hosts` to a temporary file, and configures `ssh` to use it via `-o UserKnownHostsfile=...`
 7. _cockpit.beiboot_ connects to the remote host via SSH using the [ferny API](https://github.com/allisonkarlitskaya/ferny/).
 8. If the remote host's SSH key is unknown or has changed:
    * For **unknown** hosts: SSH prompts via its `SSH_ASKPASS` mechanism. _ferny_'s interaction agent detects the host key prompt, parses the fingerprint, and _cockpit.beiboot_ sends an `X-Conversation` challenge to _cockpit-ws_ with the host key fingerprint and hostname.
    * For **changed** keys: SSH immediately fails with a changed host key error. _ferny_ detects this as `SshChangedHostKeyError`. _cockpit.beiboot_ fails with `problem=invalid-hostkey`. _login.js_ catches this and retries the login **without** sending the old known_hosts entry, causing SSH to treat it as an unknown host (see above).
    * For localhost (127.0.0.1): _cockpit.beiboot_ automatically accepts the key without user interaction.
    * _cockpit-ws_ forwards the `X-Conversation` challenge to the web UI
    * _login.js_ shows a host key verification dialog with the fingerprint, key type, and hostname to the user; it remembers unknown vs. changed and shows appropriate UI
    * User accepts or rejects the key
    * _login.js_ sends the response back via `X-Conversation` header
    * _cockpit.beiboot_ returns the response to SSH's askpass mechanism
    * SSH proceeds with the connection (if accepted) or fails (if rejected)
 9. If SSH prompts for additional input (like 2FA): Similar to step 7 in User/Password section: _cockpit.beiboot_ sends `X-Conversation` messages back and forth
 10. When SSH authentication succeeds, _cockpit.beiboot_ either runs an existing remote `cockpit-bridge`, or sends its own Python module through the SSH connection (via [beipack](https://github.com/allisonkarlitskaya/beipack)). It starts bridge on the remote machine and connects its stdio to it
 11. Authentication completes, and the session starts as above.
 12. After successful SSH authentication and bridge startup, if a new host key was accepted, _cockpit.beiboot_ reads the updated `known_hosts` file and sends it to the browser in the `init` message's `login-data` field as `known-hosts`
 13. _login.js_ stores the received `known-hosts` entry in localStorage for future connections to this host

---

### Doc Branding (`doc/branding.md`)

# Branding

Typically Cockpit is branded in such a way that it looks like the admin
user interface for an operating system. The "Cockpit" brand is only used
during development.

Due to trademark law, as a general rule Cockpit does not ship logos for
operating systems in its packaging. These are expected to be present on the
system itself, and are incorporated into the branding.

## How Cockpit Selects Branding

In `$prefix/share/cockpit/branding` are multiple directories, each of which
contain branding information. Branding files are served from the directories
based in the order below, if a file is not present in the first directory on
the list, the second will be consulted, and so on.

    $prefix/share/cockpit/branding/$ID-$VARIANT_ID
    $prefix/share/cockpit/branding/$ID
    $prefix/share/cockpit/branding/default
    $prefix/share/cockpit/static

The `$ID` and `$VARIANT_ID` variables are those listed in `/etc/os-release`,
and `$prefix` is usually `/usr`.

Administrators can provide a custom local branding in `/etc/cockpit/branding/`
(more generally, `$XDG_CONFIG_DIRS/cockpit/branding/`). If that directory
exists, it is considered before the above directories.

All of the files served from these directories are available over HTTP
without authentication. This is required since these resources will be used
on the login screen.

## Branding files

The following files are interesting in the above directories for branding
purposes:

    apple-touch-icon.png
    favicon.ico
    branding.css

In addition there are image files referred to by branding.css (see below).
Since Cockpit does not package trademarked logos, typically there will be
symlinks from a branding directory to the relevant image files elsewhere
on the system.

## Branding Styles

The Cockpit login screen and navigation area loads a `branding.css` file
from the above directories.

The branding.css file should override the following areas of the login screen:

```css
/* Background of the login prompt */
html body.login-pf {
    background: url("my-background-image.jpg");
    background-size: cover;
}

/* Upper right logo of login screen */
#badge {
    width: 225px;
    height: 80px;
    background-image: url("logo.png");
    background-size: contain;
    background-repeat: no-repeat;
}

/* The brand text above the login fields */
#brand::before {
    font-size: 18pt;
    text-transform: uppercase;
    content: "${NAME} <b>${VARIANT}</b>";
}
```

Notice how we can use variables from `/etc/os-release` in the branding.
The value for these variables come from the machine that cockpit is logged into.

`branding.css` may also set the `--ct-color-host-accent` CSS variable to change
the Cockpit Shell panel's accent color. You can also customize
[PatternFly's brand colors](https://www.patternfly.org/design-foundations/colors/#brand-colors)
and other PatternFly variables. For example:

```css
:root {
    --ct-color-host-accent: #c00 !important;
    --pf-t--global--color--brand--default: red;
}
```

---

### Doc I18n (`doc/i18n.md`)

Cockpit Internationalization
============================

All human-visible strings on Cockpit's web pages should be translatable. This
document describes the entire internationalization process, which uses [GNU gettext](https://www.gnu.org/software/gettext/) and related tools as much as possible.

Marking strings
---------------
Strings that are visible to humans and which should be translated must be
marked as such on Cockpit pages, in one of two ways:

 * If the string appears in a HTML source, it must be enclosed in a tag with
   the `translate="yes"` attribute. Example:

   ```html
   <span translate="yes">Image</span>
   ```

 * If the string appears in JavaScript source, it must use the
   [cockpit.gettext()](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit-locale.html#cockpit-locale-gettext)
   or a related function, commonly called through an alias `_()`, with double-quoted
   strings so that `xgettext` can recognize it. Example:

   ```js
   const _ = cockpit.gettext;

   var translated = _("Time");

   // in a JSX block:
   <tr>
     <th>{ _("Image") }</th>
   </tr>
   ```
   For strings that involve numbers you need to use
   [cockpit.ngettext()](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit-locale.html#cockpit-locale-ngettext)
   instead:

   ```js
   var translated = cockpit.ngettext("This thing", "The things", numberOfThings);
   ```

   The [cockpit.format()](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit-util.html#cockpit-format)
   function is useful for parameterized strings where the order of parameters
   might change between languages.

Translation process
-------------------

 * Translatable strings from HTML, JavaScript (as above) and C files get
   extracted with various invocations of
   [xgettext](https://linux.die.net/man/1/xgettext), see
   [po/Makefile.am](https://github.com/cockpit-project/cockpit/blob/main/po/Makefile.am).
   Running `make po/cockpit.pot` will generate a standard PO template.

 * With `make upload-pot` the PO template gets uploaded to the
   [Weblate translation platform](https://translate.stg.fedoraproject.org/projects/cockpit/)
   where everybody can contribute translations to various languages.

 *  With `make download-po` Weblate's translations are downloaded to
    `po/XX.po`. This is done by
   [bots/po-refresh](https://github.com/cockpit-project/cockpit/blob/main/bots/po-refresh)
   which is invoked regularly by
   [bots/po-trigger](https://github.com/cockpit-project/cockpit/blob/main/bots/po-trigger).

 * Translations can also be done using any other tool or platform and be
   submitted via pull requests that update the `po/XX.po` files. In this case
   they should then also be uploaded to Weblate either manually using Weblate UI
   or by pushing these files into [cockpit-weblate](https://github.com/cockpit-project/cockpit-weblate)
   repository.

Using translations at runtime
-----------------------------
HTML and JavaScript don't directly support gettext po and mo files. The
translations from po files are converted to a JavaScript function by
[cockpit-po-plugin](../pkg/lib/cockpit-po-plugin.js)
which inserts the actual translations into a
[cockpit.locale()](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit-locale.html#cockpit-locale-locale) call.
The resulting files are written to `dist/<page>/po.XX.js` from where they
can be imported by the page and used by `cockpit.gettext()`.

HTML pages which use `translate="yes"` tags need to call
[cockpit.translate()](https://docs.cockpit-project.org/cockpit-guide/latest/guide/cockpit-locale.html#cockpit-locale-translate) which will iterate over all such tags and replace their content with the result of calling `cockpit.gettext()` on their original content.

Opening a new branch
--------------------
Occasionally we create a new upstream branch for e. g. a new stable RHEL
release. Run these steps to initialize translations for it:

 * In [cockpit-weblate](https://github.com/cockpit-project/cockpit-weblate) create a new branch
   with the same name.
   Upload there `cockpit.pot` that you generated from the new upstream branch with `make po/cockpit.pot`.
   If you have also `.po` files, you can also add them into this branch.

 * On the [Weblate Cockpit project page](https://translate.stg.fedoraproject.org/projects/cockpit/),
   click the "Add new translation component" button and create a new component. Give it a name
   that matches the new branch. Point it to [cockpit-weblate](https://github.com/cockpit-project/cockpit-weblate)
   repository to a branch you created in the previous step.

 * On the new cockpit branch, change `WEBLATE_REPO_BRANCH` in
   [po/Makefile.am](https://github.com/cockpit-project/cockpit/blob/main/po/Makefile.am)
   from "main" to the new branch name.

---

### Doc Key Auth (`doc/key-auth.md`)

Cockpit Key Based Authentication
================================

Cockpit allows you to monitor and administer several servers at the same time.
While you will need to connect to the primary server with a password
or kerberos ticket, cockpit does support accessing secondary machines via
public key based authentication.

Note that when a user is authenticated in this way the authentication happens
without a password, as such the standard cockpit reauthorization mechanisms do
not work. The user will only be able to obtain additional privileges if they do not require a password.

In order to support key based authentication cockpit adds pam_ssh_add.so
to it's pam stack. Once a user is successfully logged in a new ssh-agent
is started and ssh-add is run to load the default keys, if a password is
requested the one the user logged in with is provided.

When cockpit attempts to establish an ssh connection to a new server,
the running ssh-agent is used to offer any loaded public keys to the
remote server as authentication options.

When the user logs out or has their session terminated, the ssh-agent is also
terminated.

---

### Doc Login (`doc/login.md`)

Cockpit Login
================================

The cockpit login page signs users in by sending requests to /login the a Authorization header.
Generally this will be with a user provided username and password. In that case the authorization
header is built like this.

```
Basic base64(user:password)
```

A successful response is a 200 http code with a json body that contains a `user` field with the user
name of the user that was just logged in. Additional fields may be present

Other http codes are considered errors. Generally these are 401 or 403 http status codes.
In most cases the error can detrived from the status text. Examples are
`authentication-failed`, `authentication-unavailable` or `access-denied`
 In some cases additional error messages may be included.

In some authentication setups additional steps are required. When this happens cockpit will
return a 401 http status code. Along with a WWW-Authenticate challenge header that looks like
this.


```
WWW-Authenticate X-Conversation id base64(prompt)
```

This may be accompanied by a JSON object in the body of the response to provide additional context.

```
{
    "message" : ...
    "error": ...,
    "echo": true or false
}
```

Cockpit will then display the prompt and provide a field for the user to type the answer.
To send the answer the Authorization header is built like this.

```
Authorization: X-Conversation id base64(response)
```

Where id is the same id that was received with the WWW-Authenticate challenge header.

This can continue until either a success or an error response is received.

---

### Doc Protocol (`doc/protocol.md`)

Protocol Documentation
======================

The protocol is a simple transport for other APIs. It's a simple wrapper over
some other protocols, like DBus, REST, and so on. At the present time this
is unstable, but will need to become stable in the near future.

Channels
--------

Each message is part of a channel, which has a id. This channel id
identifies the type of data in the message payload, the host where it is
headed to/from and things like the service or user credentials.

An empty/missing channel id represents a special channel called the control
channel. It contains command messages. These are described below. Initially
only the control channel exists. Additional channels are opened and closed
via command messages.

Channels operate across all participants, and they help a forwarding layer
like cockpit-ws to know where to send messages.

Framing
-------

The channel id is a UTF-8 valid string.  The channel id string comes first
in the message, then a new line, then payload.

For example if you had a payload of the string 'abc', and it was being
sent through the channel 'a5', that might look like:

    a5\nabc

When a message is sent over a stream transport that does not have distinct
messages (such as SSH or stdio), the message is also prefixed with a base 10
integer and new line, which represents the length of the following message.
The length must be a positive number in canonical form (ie: strictly greater
than zero, with no leading zeros).

An example. When going over a stream transport with a payload of the 3 byte
string 'abc', and a channel of 'a5', would have a message length of 6: 3 bytes of
payload, 2 bytes for the channel number, 1 for the new line. It would look like
this in a stream:

    6\na5\nabc

Control Messages
----------------

Control messages let the various components such as cockpit-web, cockpit-ws and
cockpit-bridge communicate about what's going on.

Control messages are always sent in the control channel. They always have an
empty channel. The payload of a control message is always a json
object. There is always a "command" field:

```
{
    "command": <command>,
    "channel": <channel>,
    ...
}
```

If a control message pertains to a specific channel it has a "channel" field
containing the id of the channel. It is invalid to have a present but empty
"channel" field.

Unknown control messages are ignored. Control messages that have a channel are
forwarded to the correct endpoint. Control messages without a channel are not
forwarded, but only travel a single hop.


Command: init
-------------

The "init" command is the first message sent over a new transport. It is
an error if any other message is received first. The transport is not considered
open until the "init" message has been received.

The following fields are defined:

 * "version": The version of the protocol. Currently 1, and stable.
 * "capabilities": Optional array of strings advertizing capabilities.
 * "channel-seed": A seed to be used when generating new channel ids.
 * "host": The host being communicated with.
 * "problem": A problem occurred during init.
 * "csrf-token": The web service will send a csrf-token for external channels.
 * "os-release": The bridge sends fields from /etc/os-release which identify the system.
 * "packages": The bridge sends a list of package names on the system.
 * "superuser": Instructs a bridge about whether and how to start a superuser bridge.

If a problem occurs that requires shutdown of a transport, then the "problem"
field can be set to indicate why the shutdown will be shortly occurring.

The "init" command message may be sent multiple times across an already open
transport, if certain parameters need to be renegotiated.

Command: open
-------------

The "open" command opens a new channel for payload.

The following fields are defined:

 * "binary": If present set to "raw"
 * "channel": A uniquely chosen channel id
 * "payload": A payload type, see below
 * "host": The destination host for the channel, defaults to "localhost"
 * "user": Optional alternate user for authenticating with host
 * "superuser": Optional. Use "require" to run as root, or "try" to attempt to run as root.
 * "group": An optional channel group
 * "capabilities": Optional, array of capability strings required from the bridge
 * "session": Optional, set to "private" or "shared". Defaults to "shared"
 * "flow-control": Optional boolean whether the channel should throttle itself via flow control.
 * "send-acks": Set to "bytes" to send "ack" messages after processing each data frame


If "binary" is set to "raw" then this channel transfers binary messages.

After the command is sent, then the channel is assumed to be open. No response
is sent. If for some reason the channel shouldn't or cannot be opened, then
the recipient will respond with a "close" message.

The channel id must not already be in use by another channel.

An example of an open:

```json
{
    "command": "open",
    "channel": "a4",
    "payload": "stream",
    "host": "localhost"
}
```

This message is sent to the cockpit-bridge.

The caller can control whether to open a new bridge session or not by setting
the "session" field to "private" or "shared". A "private" session will always
start another bridge just for this channel. If this option is not specified
then typically the channel will share an already existing session. There are
certain heuristics for compatibility, where it defaults to private.

The open fields are also used with external channels. External channels are
channels that have their payload sent via a separate HTTP request or another
WebSocket. In this case the "channel" field must not be present, and an
"external" field may be present with the following arguments:

 * "content-disposition": a Content-Disposition header for GET responses
 * "content-type": a Content-Type header for GET responses
 * "protocols": an array of possible protocols for a WebSocket

Channel "group" fields can be used to group channels into groups. You should
prefix your groups with a reverse domain name so they don't conflict with
other or Cockpit's group names. Group names without any punctuation are
reserved.

One such special group name is "default", which contains all channels which
were opened without specifying a group.

The "flow-control" option controls whether a channel should attempt to throttle
itself via flow control when sending or receiving large amounts of data. The
current default (when this option is not provided) is to not do flow control.
However, this default will likely change in the future.  This only impacts data
sent by the bridge to the browser.

If "send-acks" is set to "bytes" then the bridge will send acknowledgement
messages detailing the number of payload bytes that it has received and
processed.  This mechanism is provided for senders (ie: in the browser) who
wish to throttle the data that they're sending to the bridge.

**Host values**

Because the host parameter is how cockpit maps url requests to the correct bridge,
cockpit may need to additional information to route the message correctly.
For example you want to connect to a container `my-container` running
on "my.host".

To allow this the host parameter can encode a key/value pair that will
be expanded in the open command json. The format is host+key+value. For example

```json
{
    "command": "open",
    "channel": "a4",
    "payload": "stream",
    "host": "my.host+container+my-container"
}
```

will be expanded to

```json
{
    "command": "open",
    "channel": "a4",
    "payload": "stream",
    "host": "my.host",
    "host-container": "my-container",
    "host": "my.host"
}
```

Command: close
--------------

The "close" command closes a channel.

The following fields are defined:

 * "channel": The id of the channel to close
 * "problem": A short problem code for closure, or not present for a normal close

The channel id must be set.  An example of a close:

```json
{
    "command": "close",
    "channel" : "5x",
    "problem": "access-denied"
}
```

Any protocol participant can send this message. The cockpit-bridge and cockpit-ws
backends will send this message when a channel closes whether because of an
error or a normal closure. The frontend cockpit-web will send this when it
wants to close a channel normally. Once either end has sent this message the
channel is considered closed.

See below for a list of problem codes.

When a channel closing is the result of a ssh transport closing cockpit-ws
reports on the authentication methods used while attempting to authenticate
by adding a "auth-method-results" object to the close object. This is mainly
useful to provide helpful error messages and suggestions to users.

The "auth-method-results" object contains a key for each method that cockpit-ws
is able to attempt authentication with as well as the result of the attempt.
For example:

```json
{
    "password": "denied"
}
```

This possible "result" values are:

 * "no-server-support": The target server does not support this method.
 * "not-provided": cockpit-ws doesn't have a credential to try so this method was skipped.
 * "succeeded": Authentication with this method was successful.
 * "denied": Authentication with this method was denied.
 * "partial": The server wants more authentication.
 * "error": Unexpected error occurred when using this method.
 * "not-tried": This methods was not tried, usually due to an earlier method succeeding.

Other fields may be present in a close message.

Command: ready
--------------

The "ready" command indicates that the channel implementation (usually
the bridge) is in a ready and settled state. It is not normally necessary to
listen to this control message, since it is possible to start sending payload
over a channel immediately after the open message has been sent.

The following fields are defined:

 * "channel": The id of the channel

Other fields may be present in a ready message.

Command: done
-------------

The "done" command indicates that no more messages will be sent on the channel
in the same direction as the "done" was sent.

The following fields are defined:

 * "channel": The id of the channel

Either or both endpoints of a channel can send this message. It may only be
sent once.

After it is sent no more messages may be sent in that direction. It is an error
to send further messages, or send another "done" message.


Command: options
----------------

The "options" command sends further channel options on the fly. The contents of
the message depends on the channel payload.

The following fields are defined:

 * "channel": The id of the channel


Command: ping
-------------

The "ping" command is simply a keep alive.

No additional fields are defined, but any are allowed. If a "channel"
is set this "ping" will be forwarded. Otherwise it be limited to a single hop.

An example of a ping:

```json
{
"command": "ping"
}
```

Any protocol participant can send a "ping". It is responded to by sending
a "pong" with identical options as a reply. If a "ping" is sent with a
"channel" field set to a channel that is not currently open, the "ping"
will be ignored as expected, and no "pong" will be sent.

Command: authorize
------------------

The "authorize" command is for communication of authentication and
authorization challenges and responses between cockpit-bridge and
cockpit-ws. Despite the name this is also used for authentication.

For challenge/response authentication, the following fields are defined:

 * "cookie": a unique string sent with a challenge that must be
   present in the corresponding response.
 * "challenge": a challenge string present in messages from a bridge
   to cockpit-ws
 * "response": a response string present in messages from cockpit-ws
   to a bridge

Example authorize challenge and response messages:

```json
{
    "command": "authorize",
    "cookie": "555",
    "challenge": "Basic c2NydWZmeTp6ZXJvZw=="
}
```

```json
{
    "command": "authorize",
    "cookie": "555",
    "challenge": "Basic c2NydWZmeTp6ZXJvZw=="
}
```

Authorize messages are used during authentication by authentication
commands like `cockpit-session` to obtain the users credentials
from cockpit-ws. An authentication command can send a authorize message
with a response but no cookie. For example

```json
{
    "command": "authorize",
    "response": "Basic ..."
}
```

In that case cockpit-ws will store the response and use it in a reply
to a subsequent challenge.

For credential cache authorization, the following fields are defined:

 * "credential": Empty or "password"

When set to "password" then a "password" field should also be present
which represents a new password to cache, which replaces any current
passwords. If no password field is present then the passwords are cleared.


Command: kill
-------------

The "kill" command terminates a whole set of channels. It is sent by the frontend
and processed by cockpit-ws.

The following fields are defined:

 * "host": optional string kills channels with the given host
 * "group": optional string to select only channels opened with the given "group"

If no fields are specified then all channels are terminated.

Command: logout
---------------

The "logout" command is sent by the shell to cockpit-ws. It
disconnects the user.

In older versions, the "logout" command was broadcast to all bridges
and used to terminate privileged ones.  Privileged bridges are now
terminated by closing their transport.

Command: hint
-------------

The "hint" command provides hints to other components about the state of things
or what's going to happen next. The remainder of the fields are extensible.


Payload: null
-------------

A channel opened with this payload type will never send data, and will
ignore all data it receives.

Payload: echo
-------------

A channel opened with this payload type will send back all data that
it receives. It sends an "done" when it receives one.


Payload: dbus-json3
-------------------

DBus messages are encoded in JSON payloads by cockpit-web, and decoded in
cockpit-bridge. The 'dbus-json1' and 'dbus-json2' protocols are no
longer supported.

Additional "open" command options are needed to open a channel of this
type:

 * "bus": The DBus bus to connect to either "session", "system" or "none".
   Defaults to "system" if not present. If set to "none" you must also
   provide the address parameter.
 * "name": A service name of the DBus service to communicate with by default.
   Always omit this field if "bus" is "none". If omitted and "bus" is not
   "none" then a "name" field must be specified explicitly on other messages.
 * "address": A dbus supported address to connect to. This option is only
   used when bus is set to "none". Accepts any valid DBus address or
   "internal" to communicate with the internal bridge DBus connection.

The DBus bus name is started on the bus if it is not already running. If it
could not be started the channel is closed with a "not-found". If the DBus
connection closes, the channel closes with "disconnected".

DBus messages are encoded as JSON objects. If an unrecognized JSON object
is received, then the channel is closed with a "protocol-error".

Method calls are a JSON object with a "call" field, whose value is an array,
with parameters in this order: path, interface, method, in arguments.

```json
{
    "call": [ "/path", "org.Interface", "Method", [ "arg0", 1, "arg2" ] ],
    "id": "cookie"
}
```

All the various parameters must be valid for their use. arguments may be
null if no DBus method call body is expected. If a "type" field is specified
then it is expected to be the DBus method type signature (no tuple). If a
"flags" field is a string, then this includes message flags. The default flags
option has the "i" flag set:

 * "i": Allow interactive authentication.

If no "name" field was specified in the "open" call, indicating which DBus
service to talk to, then one must be specified here along with the "call".

If a DBus method call fails an "error" message will be sent back. An error
will also be sent back in parameters or arguments in the "call" message are
invalid.

An optional "id" field indicates that a response is desired, which will be
sent back in a "reply" or "error" message with the same "id" field.

If an interface has been exported, then the bridge will send "call" messages
in its direction for calls received for on that interface.

Method reply messages are JSON objects with a "reply" field whose value is
an array, the array contains another array of out arguments, or null if
the DBus reply had no body.

```json
{
    "reply": [ [ "arg0", 1, 2 ] ],
    "id": "cookie"
}
```

If the call had a "type" field, then the reply will have one too containing
the DBus type signature of the arguments. If a "flags" field was present on
the call, then "flags" will also be present on the reply. Valid out flags
are:

 * `>`: Big endian message
 * `<`: Little endian message

An error message is JSON object with an "error" field whose value is an
array. The array contains: error name, error arguments

```json
{
    "error": [ "org.Error", [ "Usually a message" ] ],
    "id": "cookie"
}
```

To receive signals you must subscribe to them. This is done by sending a
"add-match" message. It contains various fields to match on. If a field
is missing then it is treated as a wildcard.

"path" limits the signals to those sent by that DBus object path, it must
be a valid object path according to the specification. "path_namespace"
limits signals to the subtree of DBus object paths. It must be a valid
object path according to the specification, and may not be specified
together with "path".

"interface" limits the signals to those sent on the given DBus interface,
and must be a valid interface name. "member" limits the signal to those
of that signal name. "arg0" limits the signals to those that have the
given string as their first argument. If any of the values are not
valid according to the dbus specification, the channel will close with
a "protocol-error".

```json
{
    "add-match": {
        "name": "org.the.Name",
        "path": "/the/path",
        "interface": "org.Interface",
        "member": "SignalName",
        "arg0": "first argument"
    }
}
```

If the "name" field is omitted, it will be populated from the "open" message.
If no "name" was specified in the "open" message, then DBus messages from any
bus name will be matched.

To unsubscribe from DBus signals, use the "remove-match" message. If a
match was added more than once, it must be removed the same number of
times before the signals are actually unsubscribed.

The form of "remove-match" is identical to "add-match".

```json
{
    "remove-match": {
        "name": "org.the.Name",
        "path": "/the/path",
        "interface": "org.Interface",
        "member": "SignalName",
        "arg0": "first argument"
    }
}
```

Signals are sent in JSON objects that have a "signal" field, which is an
array of parameters: path, interface, signal name, and arguments. arguments
may be null if the DBus signal had no body.

```json
{
    "signal": [ "/the/path", "org.Interface", "SignalName", [ "arg0", 1, 2 ] ]
}
```

If a signal message is sent to the bridge, the signal will be emitted.
In addition a "destination" field may be present to indicate whether
the signal should be broadcast or not.

```json
{
    "signal": [ "/the/path", "org.Interface", "SignalName", [ "arg0", 1, 2 ] ]
}
```

If the bus name of the sender of the signal does not match the "name" field of
the "open" message, then a "name" field will be included with the "signal" message.

Properties can be watched with the "watch" request. Either a "path" or
"path_namespace" can be watched. Property changes are listened for with
DBus PropertiesChanged signals.  If a "path_namespace" is watched and
path is a DBus ObjectManager, then it is used to watch for new DBus
interfaces, otherwise DBus introspection is used. The "id" field is
optional, if present a "reply" will be sent with this same "id" when
the watch has sent "notify" messages about the things being watched.

```json
{
    "watch": {
        "name": "org.the.Name",
        "path": "/the/path/to/watch",
        "interface": "org.Interface"
    },
    "id": 5
}
```

To remove a watch, pass the identical parameters with an "unwatch"
request.

```json
{
    "unwatch": {
        "path": "/the/path/to/watch"
    }
}
```

If the "name" field is omitted, it will be populated from the "open" message.
Either a "name" field must be specified here or in the "open" message.

Property changes will be sent using a "notify" message. This includes
addition of interfaces without properties, which will be an empty
interface object, or interfaces removed, which will be null. Only the
changes since the last "notify" message will be sent.

```json
{
    "notify": {
        "/a/path": {
            "org.Interface1": {
                "Prop1": "x",
                "Prop2": 1
            },
            "org.Interface2": { }
        },
        "/another/path": {
            "org.Removed": null
        }
    }
}
```

If the bus name of the sender of the signal does not match the "name" field of
the "open" message, then a "name" field will be included with the "notify" message.

Interface introspection data is sent using "meta" message. Before the
first time an interface is sent using a "notify" message, a "meta"
will be sent with that interface introspection info. Additional fields
will be defined here, but this is it for now.

```json
{
    "meta": {
        "org.Interface": {
            "methods": {
                "Method1": { },
                "Method2": { }
            },
            "properties": {
                "Prop1": { "flags": "rw" },
                "Prop2": { "flags": "r" }
            }
        }
    }
}
```

If the bus name of the sender of the signal does not match the "name" field of
the "open" message, then a "name" field will be included with the "meta" message.
Such meta information can also be sent to the bridge, in order to populate the
cache of introspection data for the channel.

When the owner of the DBus "name" (specified in the open message) changes an "owner"
message is sent. The owner value will be the id of the owner or null if the name
is unowned.

```json
{
    "owner": "1:"
}
```

A "publish" message can be used to export DBus interfaces on the bus. The bridge
will then send "call" messages back to the frontend for each method invocation
on the published interface and object path. The interface must first be described
with DBus meta information. If a cookie is specified then a reply will be sent
when the interface is published. If the interface is already published at the given
path, it will be replaced.

```json
{
    "publish": [ "/a/path", "org.Interface" ],
    "id": "cookie"
}
```

An "unpublish" message will unexport a DBus interface on the bus. It is not an
error if no such interface has been published.

```json
{
    "unpublish": [ "/a/path", "org.Interface" ]
}
```

DBus types are encoded in various places in these messages, such as the
arguments. These types are encoded as follows:

 * byte, int16, uint16, int32, uint32, int64, uint64, double: encoded
   as a JSON number
 * boolean: encoded as a JSON boolean
 * string, object-path, signature: encoded as JSON strings
 * array of bytes: encoded as a base64 string
 * other arrays, struct/tuple: encoded as JSON arrays
 * dict: encoded as a JSON object, if the dict has string keys then those
   are used as property names directly, otherwise the keys are JSON encoded and
   the resulting string is used as a property name.
 * variant: encoded as a JSON object with a "v" field containing a value
   and a "t" field containing a DBus type signature.

```json
{
    "v": "value",
    "t": "s"
}
```

Payload: http-stream2
---------------------

This channel represents a single HTTP request and response. The request
HTTP headers are sent in the "open" message, and response headers are
returned in a "response" control message.

The remaining messages make up the HTTP request and response bodies.
When the channel is in text mode, the response body is automatically
coerced to UTF-8.

The following HTTP methods are not permitted:

 * 'CONNECT'

The following headers are not permitted on requests, some of these
are added automatically as appropriate, and others are stripped from
the response, in particular 'Content-Length':

 * 'Accept-Charset'
 * 'Accept-Ranges'
 * 'Connection'
 * 'Content-Length'
 * 'Content-MD5'
 * 'Content-Range'
 * 'Range'
 * 'TE'
 * 'Trailer'
 * 'Transfer-Encoding'
 * 'Upgrade'

The following are not accepted on non-binary channels:

 * 'Accept-Encoding'
 * 'Content-Encoding'

Additional "open" command options are needed to open a channel of this
payload type:

 * "unix": Open a channel with the given unix socket.
 * "port": Open a channel with the given TCP port on localhost.
 * "address": Open a channel that communicates with the given
   address instead of localhost. This can be an IP address or a valid
   host name. To use this option you must also specify a port.
   This option should be used to communicate with external
   apis and not as a synonym for the host parameter.
 * "method": "GET", "POST", or other valid HTTP method

You may also specify these options:

 * "headers": JSON object with additional request headers
 * "tls": Set to a object to use an https connection.

The TLS object can have the following options:

 * "authority": Certificate authority(s) to expect as signers of peer.
 * "validate": Validate the peer's certificate.

The "authority" is an object with either of the following fields:

 * "file": String with a file name
 * "data": String with PEM encoded data

Any data to be sent should be sent via the channel, and then the channel
should be closed without a problem.

Payload: websocket-stream1
--------------------------

This channel payload implements a WebSocket client. The data in the
channel is the message frames.

Most of same options for http-stream2 apply here.

The response headers are send back in a "response" control message.

Payload: stream
---------------

Raw data is sent back and forth to a socket. See cockpitstream.c. The
boundaries of the messages are arbitrary, and depend on how the kernel
and socket buffer things.

If the channel is not binary, then non-UTF-8 data is forced into UTF-8
with a replacement character.

Additional "open" command options should be specified with a channel of
this payload type:

 * "unix": Open a channel with the given unix socket.
 * "port": Open a channel with the given TCP port on localhost.
 * "address": Open a channel that communicates with the given
    address instead of localhost. This can be an IP address or a valid
    host name. To use this option you must also specify a port.
 * "batch": Batches data coming from the stream in blocks of at least this
   size. This is not a guarantee. After a short timeout the data will be
   sent even if the data doesn't match the batch size. Defaults to zero.
 * "latency": The timeout for flushing any cached data in milliseconds.
 * "spawn": Spawn a process and connect standard input and standard output
   to the channel. Should be an array of strings which is the process
   file path and arguments.

You can't specify both "unix" and "spawn" together. When "spawn" is set the
following options can be specified:

 * "directory": The directory to spawn the process in.
 * "err": If "spawn" is set, and "err" is set to "out", then stderr
   is included in the payload data. If "err" is set to "ignore" then, the
   stderr output will be discarded. If "err" is set to "message" then it will
   be included in the close message. If "pty" is set then stderr is always
   included.
 * "environ": This is a list of additional environment variables for the new
   spawned process. The variables are in the form of "NAME=VALUE". The default
   environment is inherited from cockpit-bridge.
 * "pty": Execute the command as a terminal pty.
 * "window": An object containing "rows" and "cols" properties, which set the
   size of the terminal window. Values must be integers between 0 and 0xffff.
   This option is only valid if "pty" is true.

For type "spawn", the `ready` message contains a "pid" field with the spawned
process ID.

If an "done" is sent to the bridge on this channel, then the socket and/or pipe
input is shutdown. The channel will send an "done" when the output of the socket
or pipe is done.

If "spawn" is set and the channel is closed with a "problem" code, then the
process will be sent a SIGTERM signal.

Additionally, a "options" control message may be sent in this channel
to change the "batch", "latency", and "window" options.

Payload: packet
---------------

Raw data is sent back and forth to as messages to a socket. The message
boundaries correspond exactly to the messages sent over the socket.

If the channel is not binary, then non-UTF-8 data is forced into UTF-8
with a replacement character.

Additional "open" command options should be specified with a channel of
this payload type:

 * "unix": Open a channel with the given unix socket.
 * "max-size": The maximum possible size of a message. Default is 64K
   Supports up to 128K. Messages larger than this will be truncated.

If an "done" is sent to the bridge on this channel, then the socket
input is shutdown. The channel will send an "done" when the output of the socket
or pipe is done.

Additionally, a "options" control message may be sent in this channel
to change the "max-size" options.

Payload: fsinfo
----------------

This channel is used to report information about a given path, and the file or
a directory which may reside there, including the possibility of updates.  In
the case of directories, information can also be reported about the (directly)
contained entries.

Additional "open" command options should be specified with a channel of
this payload type:

 * `path` (mandatory): a string, an absolute path name.  If this ends with `/`
   then the path must refer to a directory.
 * `attrs` (mandatory): a list of strings, the requested attributes to be
   reported for the file at `path`, and (optionally) its sub-items.  See below
   for the possibilities.
 * `fnmatch`: (optional, default ''): if non-empty, this is a [`fnmatch`-style
   pattern](https://docs.python.org/3/library/fnmatch.html).  In case `path`
   refers to a readable directory, an `entries` attribute will be reported
   (described below).  Use `*` to report all files.  Can also be used to
   implement search (`searc*`) or hide temporary files (`[!.]*`).
 * `watch` (optional, default: false): if the channel should watch for changes
 * `follow` (default: true): if the trailing component of `path` is a symbolic
   link, whether we should follow it.  `follow: false` mode is not supported
   for `watch` (but is also mostly unnecessary).

A channel is created with an associated absolute pathname (`path`).  A path is
a string that you pass to the `open()` syscall.  The object returned by that
call is described by the (JSON Document) value of the channel.

Strictly speaking, the path is specified to the channel as a unicode string,
but paths on the filesystem are binary strings.  This mapping is handled using
the usual Python convention: filenames are stored in utf8 and encoded and
decoded with `errors='surrogateescape'`, which allows for handling of filenames
which are not utf8.

Conceptually, the channel represents the value of a JSON object describing what
is found at the path, or the reason for an error.  Each message on the channel
is a JSON Merge Patch ([RFC
7396](https://datatracker.ietf.org/doc/html/rfc7396)) which updates or replaces
this object.

The top-level keys in the object are:

 - `info`: the info about the reported path (described below)
 - `error`: an object with information about why `info` can't be reported.
   This is currently basically an errno formatted in several possible ways:
    - `problem`: a Cockpit "problem" code type of string like `not-found` or
      `not-directory`
    - `message`: the result of strerror().  Currently English, hopefully
      translated at some point
    - `errno`: the symbolic `errno` name like `ENOENT` or `ENOTDIR`
 - `partial`: `true` if the data is in a "partial" state.  This is used when
   sending updates in multiple pieces.

The value of the `info` attribute is a JSON object describing the attributes of
the file.  Any attribute which cannot be read (eg: due to permissions problems)
is simply not reported.

 * `type`: a string: `reg`, `dir`, `lnk`, `chr`, `blk`, `fifo`, or `sock`
 * `mode`: an integer representing the "file permission" bits
   (`S_IMODE(st_mode)`).  As per JSON, this is transmitted as a decimal value,
   but it is meant to be interpreted in octal, in the usual way.
 * `uid`: an integer, the uid of the file owner (`st_uid`)
 * `user`: a string, or an integer if the lookup failed
 * `gid`: an integer, the gid of the file group (`st_gid`)
 * `group`: a string, or an integer if the lookup failed
 * `size`: an integer, the (apparent) size of the file (`st_size`)
 * `mtime`: a float, the mtime of the file (`st_mtim`)
 * `tag`: a value type: the current 'transaction tag' of the file, with the
   same meaning as elsewhere in this document.  Ideally: this changes if the
   content of the file changes.  This is the same as the tag used in `fsread1`
   and `fsreplace1`.
 * `entries`: for directories: the contents of the directory (see below)
 * `target`: for symbolic links: the target of the link (a string)
 * `targets`: for directories: extra information about symlink targets for
   symlinks found in the directory (see below)
 * `r-ok`: boolean, whether reading is allowed. (note: checking access is expensive)
 * `w-ok`: boolean, whether writing is allowed. (note: checking access is expensive)
 * `x-ok`: boolean, whether executing is allowed. (note: checking access is expensive)

The `entries` attribute is reported iff `fnmatch` is non-empty, and `path`
refers to a readable directory.  It is an object where each key is the name of
a file in the directory, and each value is an object describing that file,
using the same attributes as above.  `entries` is not reported on entries (ie:
no recursive listing).  Listing `entries` in `attrs` is equivalent to
`fnmatch='*'`.

The `targets` attribute is reported iff `targets` is listed in `attrs`.  It
contains information that was true at some point in time about the targets of
symbolic links that were found in the current directory.  An entry is added
here only in case the entry wouldn't be found in the main `entries` dictionary.
For example, if `path` is `/usr/lib` and `fnmatch` is `*.so` and the directory
contains the links `a.so → a-impl.so`, `b.so → b.so.0` and `c.so -> ../x/c.so`
then the `targets` dictionary would include `b.so.0` (which isn't reported in
entries because it doesn't match `fnmatch`) and `../x/c.so` (which isn't
reported because it's in a different directory).  Monitoring these entries for
changes would be expensive, so it's simply not done.  Clients are expected to
use this information to flesh out details about directory entries, answering
questions like "`/bin` links to `usr/bin`, but what type of file is that?".
`targets` is incompatible with `follow: false`.

If the provided `path` is a symbolic link, it is followed, unless
`follow: false` is specified.  If symbolic links are present in the directory,
they are always reported directly (ie: not followed).  `follow: false` is not
supported in combination with `watch: true` but you probably don't need it:
watch mode doesn't only watch the final path component, but all components
leading up to it, including any symlink that have been followed.  That means
that you can watch `/etc/localtime` and find out about if the link changed, or
if the contents of the file itself changed, or was deleted or replaced, or also
if `/usr`, `/etc`, or any other directories or intermediate links were changed,
deleted, or moved out of the way.

The life-cycle of the channel works like this:

 - When opening the channel, `ready` is always immediately sent.

 - One or more patches are sent to modify the initial value of the channel
   (`null`).  The "initial state" is reached when the value is a object
   which does not have the `partial` attribute set on it.

 - If `watch` mode is false then `done` is sent immediately, and the channel is
   closed.

 - Otherwise (`watch: true`), the channel remains open.  When any changes are
   detected, they are sent as updates to the value of the channel.  This may
   involve transitions between reporting `info` and reporting `problem`.

 - The channel will stay open until the client closes it.

Payload: fsread1
----------------

Returns the contents of a file and its current 'transaction tag'.

The following options can be specified in the "open" control message:

 * "path": The path name of the file to read.
 * "max_read_size": option to limit the amount of data that is read.  If the
   file is larger than the given number of bytes, no data is read and the
   channel is closed with problem code "too-large".  The default limit is 16
   MiB.  The limit can be completely removed by setting it to -1.

The ready message contains a "size-hint" when the channel is opened
with the "binary" option set to "raw".

The channel will return the content of the file in one or more
messages.  As with "stream", the boundaries of the messages are
arbitrary.

If the file is modified while you are reading it, the channel is
closed with a "change-conflict" problem code.  If the file is
atomically replaced as with 'rename' when you are reading it, this is
not considered an error and you will get the old content with a
correct tag.

When all content has been sent or an error has occurred, the channel
will be closed.  In addition to the usual "problem" field, the "close"
control message sent by the server might have the following additional
fields:

 * "message": A string in the current locale describing the error.

 * "tag": The transaction tag for the returned file content.  The tag
   for a non-existing file is "-".

It is not permitted to send data in an fsread1 channel. This channel
sends a "done" when all file data was sent.

Payload: fsreplace1
-----------------

Replace the content of a file.

The following options can be specified in the "open" control message:

 * "path": The path name of the file to replace.

 * "size": The expected size of the file content.  If set, the file is
   allocated immediately, and the channel "open" request will fail if
   insufficient space is available.  If this option is set, it is not
   possible to delete the file (ie: sending immediate EOF will result in
   a 0 byte file) This option should always be provided if possible, to
   avoid fragmentation, but is particularly important for large files.

 * "tag": The expected transaction tag of the file.  When the actual
   transaction tag of the file is different, the write will fail.  If
   you don't set this field, the actual tag will not be checked.  To
   express that you expect the file to not exist, use "-" as the tag.

* "attrs": a JSON object containing optional file attributes to set:
  - `user`: a string, or an integer, the uid of the file owner (`st_uid`).
    If set `group` also has to be set.
  - `group`: a string, or an integer, the gid of the file group (`st_gid`)
    If set `user` also has to be set.
  - `mode`:  an integer, usually expressed in octal, but in json it is the
    equivalent value in decimal.

You should write the new content to the channel as one or more
messages.  To indicate the end of the content, send a "done" message.

If you don't send any content messages before sending "done", and no
"size" was given, the file will be removed.  To create an empty file,
send at least one content message of length zero, or set the "size" to
0.

If "size" is given, and less data is actually sent, then the file will
be truncated down to the size of the data that was actually sent. If
more data is sent, the file will grow (subject to additional
fragmentation and potential ENOSPC errors).

When the file does not have the expected tag, the channel will be
closed with a "change-conflict" problem code.

The new content will be written to a temporary file and the old
content will be replaced with a "rename" syscall when the channel is
closed without problem code.  If the channel is closed with a problem
code (by either client or server), the file will be left untouched.

If `tag` is given and no equivalent `attrs`, file owner, mode and SELinux
context are preserved (copied from the original file). Other attributes (like
ACLs) are never copied.

In addition to the usual "problem" field, the "close" control message
sent by the server might have the following additional fields:

 * "message": A string in the current locale describing the error.

 * "tag": The transaction tag of the new content.

No payload messages will be sent by this channel.

Payload: metrics1
-----------------

With this payload type, you can monitor a large number of metrics,
such as all of the metrics exposed by PCP, the Performance Copilot.

In addition to monitoring live values of the metrics, you can also
access archives of previously recorded values.

You specify which metrics to monitor in the options of the "open"
command for a new channel.

One of the options is the "source" of the metrics.  Many details of
how to specify metrics and how the channel will work in detail depend
on the type of the source, and are explained in specific sections
below.

The channel guarantees that each sample returned by it contains values
for all requested metrics.

The general open options are:

 * "source" (string): The source of the metrics:

   * "direct": PCP metrics from plugins that are loaded into the
     Cockpit bridge directly.  Use this when in doubt.

   * "pmcd": PCP metrics from the local PCP daemon.

   * A string starting with "/": PCP metrics from one or more
     archives.

     The string is either the name of an archive, or the name of a
     directory.  When it refers to a directory, the bridge will find
     all archives in that directory and merge them.  The archives must
     not overlap in time.

   * "pcp-archive": PCP metrics from the default pmlogger archive.

     This is the same as using the name of the default pmlogger
     archive directory directly, but you don't have to know where it
     is.

 * "metrics" (array): Descriptions of the metrics to use.  See below.

 * "instances" (array of strings, optional): When specified, only the
   listed instances are included in the reported samples.

 * "omit-instances" (array of strings, optional): When specified, the
   listed instances are omitted from the reported samples.  Only one
   of "instances" and "omit-instances" can be specified.

 * "interval" (number, optional): The sample interval in milliseconds.
   Defaults to 1000.

 * "timestamp" (number, optional): The desired time of the first
   sample.  This is only used when accessing archives of samples.

   This is either the number of milliseconds since the epoch, or (when
   negative) the number of milliseconds in the past.

   The first sample will be from a time not earlier than this
   timestamp, but it might be from a much later time.

 * "limit" (number, optional): The number of samples to return.  This
   is only used when accessing an archive.

   When no "limit" is specified, all samples until the end of the
   archive are delivered.

You specify the desired metrics as an array of objects, where each
object describes one metric.  For example:

```json
[
    {
        "name": "kernel.all.cpu.user",
        "units": "millisec",
        "derive": "rate"
    },
    ...
]
```

A metric description can contain the following fields:

 * "name" (string): The name of the metric.  Available metrics depend
   on the source.

 * "units" (string, optional): The units that values for this metric
   should be delivered in.  If the given metric can not be converted
   into these units, the channel is closed.  The format of the string
   depends on the source.

 * "derive" (string, optional): Optional computation.  Possible values
   are "delta" and "rate".  For "delta", the channel delivers the
   difference between the current and the previous value for a metric.
   For "rate", the channel delivers the change per millisecond of the
   metric.

   For both "delta" and "rate", the value for a metric will be "false"
   if there is no previous value to do the computation with.

Once the channel is open, it will send messages encoded as JSON.  It
will send two types of message: 'meta' messages that describe the
metrics, and 'data' messages with the actual samples.

A 'meta' message applies to all following 'data' messages until the
next 'meta' message.  The first message in a channel will always be a
'meta' message.

The 'meta' messages are JSON objects; the 'data' messages are JSON
arrays.

The 'meta' messages have at least the following fields:

 * "metrics" (array): This field provides information about the
   requested metrics.  The array has one object for each metric, in
   the same order as the "metrics" option used when opening the
   channel.

   For each metric, the corresponding object contains at least the
   following field:

   * "instances" (array of strings, optional): This field lists the
      instances for instanced metrics.  This field is not present for
      non-instanced metrics.

   * "units" (string): The units of the values for this metric.

   * "derive" (string): The post-processing mode, as specified in the
     "open" message.

 * "timestamp" (number): The point in time of the next 'data' message,
   in milliseconds since the epoch.

 * "interval" (number): The time interval between subsequent points in
   time in the 'data' messages, in milliseconds.

Depending on the source, more fields might be present in a 'meta'
message, and more fields might be present in the objects of the
"metrics" field.

The 'data' messages are nested arrays in this shape:

```
    [  // first point in time
       [
          // first metric (instanced, with two instances)
          [
             // first instance
             1234,
             // second instance
             5678
          ],
          // second metric (not instanced)
          789,
          // third metric
          543
       ],
       // next point in time
       [
          // same shape again as for the first point in time
       ]
    ]
```

Thus, a 'data' message contains data for one or more points in time
where samples have been taken.  A point in time is always one
`interval` later than the previous point in time, even when they are
reported in the same 'data' message.

For real time monitoring, you will generally only receive one point in
time per 'data' message, but when accessing archived data, the channel
might report multiple points in time in one message, to improve
efficiency.

For each point in time, there is an array with samples for each
metric, in the same order as the `metrics` option used when opening
the channel.

For non-instanced metrics, the array contains the value of the metric.
For instanced metrics, the array contains another array with samples
for each instance, in the same order as reported in the `instances`
field of the most recent 'meta' message.

In order to gain efficiency, 'data' messages are usually compressed.
This is done by only transmitting the differences from one point in
time to the next.

If a value for a metric or a instance is the same as at the previous
point in time, the channel transmits a `null` value instead.
Additionally, `null` values at the end of an array are suppressed by
transmitting a shorter array.

For example, say the samples for three points in time are

    1: [ 21354, [ 5,  5, 5 ], 100 ]
    2: [ 21354, [ 5, 15, 5 ], 100 ]
    3: [ 21354, [ 5, 15, 5 ], 100 ]

then the channel will send these array instead:

    1: [ 21354, [    5,  5, 5 ], 100 ]
    2: [  null, [ null, 15 ] ]
    3: [  null, [ ] ]

This compression only happens when the last and current value belong
to the same instance of the same metric.  Thus, the client does not
need to track layout changes when decompressing data messages.

Instead of a number of `null`, a data message can also contain
`false`.  This indicates an error of some kind, or an unavailable
value.

**PCP metric source**

Cou can use "pminfo -L" to get a list of available PCP metric names
for a "direct" source, for example.  If no metric of this name exists,
the channel is closed without delivering any message.

The format of the "units" member is the same as the one used by
"pminfo -d".

The metric information objects in the 'meta' messages for PCP sources
also contain these fields:

 * `semantics` (string): The semantics of this metric, one of
   `counter`, `instant`, or `discrete`.

Only numeric metrics are currently supported.  Non-numeric metrics
have all their samples set to `false`.

Payload: session-control
------------------------

Control aspects of the current session. Currently this is limited to
the session idle timeout.

When opening a channel with payload "session-control", the bridge that
handles this channel switches on its idle timeout handling. After
opening such a channel, you are then responsible for sending messages
to the bridge that inform it of user activity. If no such message is
received (while the channel is open) for the duration of the
configured session idle timeout, the bridge will exit.
If you close the channel, the bridge will stop its idle timeout
handling.

This might change in the future such that the idle timeout handling in
the bridge is always active and cannot be switched off.

The bridge is made responsible for implementing the session idle
timeout to make sure it happens even when JavaScript stops executing
or is severely throttled, maybe due to being stopped by the Browser,
the user, or because it has crashed to such a degree that it won't
handle the session timeout anymore.

JavaScript still has to explicitly switch on the session idle timeout
handling in the bridge however (by opening a "session-control"
channel). This might change in the future and the bridge might start
the session idle timeout unconditionally.

The following channel control messages will be send by the bridge:

```json
{ "command": "ready", "timeout": N }
```

The first message will be "ready" and it will contain the configured
session idle timeout in seconds.  A timeout value of zero means that
no timeout has been configured.

```json
{ "command": "countdown", "counter": N }
```

The bridge will countdown the last 30 seconds of the session and will
send these messages to announce that. You should show the countdown to
the user somewhere.

```json
{ "command": "logout" }
```

After the countdown, the bridge will tell you to logout with this
message. You should do a clean logout. If such a logout doesn't
happen, the bridge will forcefully exit 10 seconds later.

You can send these message to the bridge:

```json
{ "command": "active" }
```

This indicates that the user was active. The bridge will reset its
idle timer.

Problem codes
-------------

These are problem codes for errors that cockpit-web responds to. They should
be self explanatory. It's totally not interesting to arbitrarily invent new
codes. Instead the web needs to be ready to react to these problems. When in
doubt use `internal-error`.

 * `internal-error`
 * `no-cockpit`
 * `unsupported-shell`
 * `no-session`
 * `access-denied`
 * `authentication-failed`
 * `not-found`
 * `terminated`
 * `timeout`
 * `unknown-hostkey`
 * `no-forwarding`

---

### Doc Routing (`doc/routing.md`)

Flow Control
------------

Cockpit's protocol passes messages on reliable, ordered underlying
transports (eg: WebSockets, HTTP responses, stdio) between multiple
peers, arranged in in a non-rooted tree graph.

Cockpit's flow control is not about replaying messages, but avoiding
flooding any peer by sending messages along any link in the graph
too rapidly.

Cockpit's flow control operates at the level of channels in the protocol
(described in doc/protocol.md). A simple blocking of any peer's message input
would block all channels, and not have the desired effect of slowing
down a certain channel which is flooding communication.

Therefore we use a windowing flow control protocol. It is based around
per-channel "ping" control messages. In the Cockpit protocol "ping"
messages in a channel are responded to by specific leaves of the graph.
They are replied to with a "pong" containing otherwise identical content.
Other "ping" messages are used as keep alives, but these are beyond the
scope of this description.

Each channel leaf that participates in flow control sends "ping"
messages with a sequence number, and waits to see the corresponding
"pong" message with the same sequence and channel number. By checking
the "ping" sequence numbers responded to, a channel can slow input and/or
transmission to wait for the opposite leaf to catch up.

Sadly this is not enough. The leaves in of Cockpit's tree often do not
consume data themselves, but pass it on to other sockets, streams, files
and so on.

Various leaves monitor their output edge queues and hold back responding
to "ping" messages if their output edge queue is too full. This is done
by use of "pressure" signals on the output queue to indicate when pressure
is high or low.

This results in flow control that achieves its intent, but does not
make guarantees. In addition only some channels and leaves implement flow
control at this point:

 * cockpit.js: Responds to "ping" messages indicating that a given
   sequence has reached the browser.

 * CockpitChannel: Sends sequence pings, every so often in the channel.
   Is responsible for responding to channel pings, and holding back
   responses to pings if a related queue has output "pressure".
   Slows down an input pipe via a "pressure" signal when too many
   sequences are outstanding and have not been responded to.

 * CockpitStream: Can listen to "pressure" signals and pause reading its
   input stream. Can generate "pressure" signals when its output queue
   is too full.

 * CockpitPipe: Can listen to "pressure" signals and pause reading its
   input file descriptor. Can generate "pressure" signals when its
   output queue is too full.

 * CockpitWebResponse: Can generate "pressure" signals when its output
   queue is too full.

 * WebSocketConnection: Can listen to "pressure" signals and pause accepting
   incoming web socket messages. Can generate "pressure" signals when its
   output queue is too full.

 * CockpitHttpStream: Uses CockpitStream and CockpitChannel together to
   implement flow control on a HTTP client connection.

 * CockpitFsRead: Listens to "pressure" signals from the CockpitChannel
   to tell it to pause reading a file.

---

### Doc Urls (`doc/urls.md`)

Cockpit URL paths
=================

This is developer documentation about the various resource paths in Cockpit
and their characteristics. This doesn't apply to the visible URLs shown
in the main Cockpit address bar.

Cockpit URLs are based on an application. A valid application name is
either the word `cockpit` or a string that begins with `cockpit+`
for example `cockpit+application-name`. Each of the following URLs
are valid for any application, just replace `/cockpit/` with `/cockpit+application-name/`

 * `/cockpit/static` static files available without authentication. Files
   are cached for as long as possible, and names *must* change when the
   contents of the file changes. The exception to this is when the application
   refers to a different machine. In that case the user must be authenticated
   to serve those files and the cache varies on cookie.

 * `/cockpit/login` authenticates a user and sets cookie based on application
 name.

 * `/cockpit/$xxxxxxxxxxxxxxx/package/path/to/file.ext` are files which
   are cached by packages for as long as possible. The checksum changes when
   any of the packages on a system change. Only available after authentication.

 * `/cockpit/$xxxxxxxxxxxxxxx/*/path/to/file.ext` are concatenated files from
   packages, as seen above.

 * `/cockpit/@host/package/path/to/file.ext` are files from packages (on
   specific hosts) that are not cached. Only available after authentication.

 * `/cockpit/@host/*/path/to/file.ext` are concatenated files from
   packages (on specific hosts). They are served uncompressed.

 * `/cockpit/@host/manifests.json` includes a summary of all the manifest
   files from all the packages

 * `/cockpit/@host/manifests.js` includes a summary of all the manifest
   files from all the packages, as an AMD loadable module

 * `/cockpit/socket` The main web socket

 * `/cockpit/channel/csrftoken?query` External channel URLs

   These are commonly used to wrap/proxy port access for Cockpit pages, as
   their JavaScript cannot talk to them directly due to Cockpit's strict
   [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).

   See the [xhr-proxy example](../examples/xhr-proxy) how to wrap a TCP HTTP
   port, or the [gtk-broadway example](../examples/gtk-broadway) for a more
   complex wrapping of a stream socket.

When loading through cockpit-ws any URL that does not begin with an
application will be handled by the shell (shell/index.html by default)
using the default application `cockpit`.


Direct to machine urls
======================

Cockpit-ws supports logging in directly to a remote machine, without first
authenticating on the machine that cockpit-ws is running on. A `cockpit-beiboot`
processes is spawned that connects via SSH to the remote machine and all
requests are proxied via that connection.

To use this feature the application name MUST begin with an `=` for
example `/cockpit+=machine/socket` will attempt to open a socket on
`machine` `/cockpit+machine/socket` will attempt to open a socket
on localhost.

When loading through cockpit-ws any URL that does not begin with an
application will be handled by the shell (shell/index.html by default)
using the default application `cockpit`.

In addition any url that begins with `/=` will attempt to load
the shell from the specified machine. For example a URL of
`/=machine/system` will attempt to load `shell/index.html`
from `machine` using the application `cockpit+machine`.

---

### Doc Extensions README (`doc/extensions/README.adoc`)

= Antora & AsciiDoctor Extensions

We need to support AsciiDoctor for generating local HTML pages as well as manpages while packaging to various distros.

But as we also publish using Antora, there might be small differences that we need to address either on the AsciiDoctor or Antora-side, or even both.

== man-inline-macro.rb

An AsciiDoctor extension that converts manpages like cockpit(1) to links `man:cockpit[1]` for HTML pages and boldened `*cockpit(1)*` for manpages.

These manpage macros do not work for the local editing of Antora within the Cockpit repo. Within the Cockpit Docs Antora repo there is an AsciiDoctor.js extension `man-inline-replace.js` that replaces `man:` links. Future improvement here could be to move the extension to the cockpit repo and fetch it from the cockpit-docs repo, but that would require more work and doesn't provide much value at the moment.

---

### Doc Modules Guide Nav (`doc/modules/guide/nav.adoc`)

.Deployment guide
* xref::https.adoc[]
* xref::listen.adoc[]
* xref::startup.adoc[]
* xref::multi-host.adoc[]
* xref::authentication.adoc[]
* xref::sso.adoc[]
* xref::cert-authentication.adoc[]
* xref::privileges.adoc[]

.Feature Internals
* xref::feature-systemd.adoc[]
* xref::feature-journal.adoc[]
* xref::feature-networkmanager.adoc[]
* xref::feature-firewall.adoc[]
* xref::feature-storaged.adoc[]
* xref::feature-users.adoc[]
* xref::feature-realmd.adoc[]
* xref::feature-terminal.adoc[]
* xref::feature-pcp.adoc[]
* xref::feature-machines.adoc[]
* xref::feature-selinux.adoc[]
* xref::feature-tuned.adoc[]
* xref::feature-sosreport.adoc[]
* xref::feature-packagekit.adoc[]

.Developer Guide
* xref::embedding.adoc[]
* xref::packages.adoc[]
* xref::urls.adoc[]

* xref::api-base1.adoc[]
** xref::api-cockpit.adoc[]
*** xref::cockpit-cache.adoc[]
*** xref::cockpit-channels.adoc[]
*** xref::cockpit-dbus.adoc[]
*** xref::cockpit-error.adoc[]
*** xref::cockpit-file.adoc[]
*** xref::cockpit-http.adoc[]
*** xref::cockpit-locale.adoc[]
*** xref::cockpit-location.adoc[]
*** xref::cockpit-manifest.adoc[]
*** xref::cockpit-metrics.adoc[]
*** xref::cockpit-series-data.adoc[]
*** xref::cockpit-login.adoc[]
*** xref::cockpit-spawn.adoc[]
*** xref::cockpit-util.adoc[]
* xref::api-shell.adoc[]
* xref::api-system.adoc[]

---

### Doc Modules Man Nav (`doc/modules/man/nav.adoc`)

.Manual pages
* xref:man:cockpit.1.adoc[]
* xref:man:cockpit.conf.5.adoc[]
* xref:man:cockpit-ws.8.adoc[]
* xref:man:cockpit-tls.8.adoc[]
* xref:man:cockpit-desktop.1.adoc[]
* xref:man:cockpit-bridge.1.adoc[]
* xref:man:pam_ssh_add.8.adoc[]

---

### Doc Modules ROOT Attachments Schema.Json (`doc/modules/ROOT/attachments/schema.json`)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://cockpit-project.org/manifest-1/schema",
  "title": "Cockpit manifest file validation for Cockpit pages.",
  "description": "Validates Cockpit manifest files according to the standard that is parsed and set by Cockpit itself. This is used for Cockpit pages only and does not validate any other AppStream manifest types.",
  "examples": [
    {
      "url": "https://github.com/cockpit-project/cockpit/blob/0e3b78e0a7c841e7608c87e87821247e308d3d14/pkg/packagekit/manifest.json"
    }
  ],
  "type": "object",
  "properties": {
    "content-security-policy": {
      "description": "By default Cockpit serves packages using a strict Content Security Policy, which among other things does not allow inline styles or scripts. This can be overridden on a per-package basis, with this setting. \n\nIf 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 be added to the policy from the manifest.",
      "type": "string"
    },
    "name": {
      "description": "An optional string that changes the name of the package. Normally packages derive their name from the directory that they are located in. This field overrides that name.",
      "type": "string",
      "pattern": "^[A-Za-z0-9_]+$"
    },
    "priority": {
      "description": "An optional integer that specifies which package is preferred in cases where there are conflicts. For example given two packages with the same `name` a package is chosen based on its priority. A higher integer means higher priority.",
      "type": "integer"
    },
    "conditions": {
      "description": "An optional list of `{\"predicate\": \"value\"}` objects. Cockpit will only consider the package if all conditions are met. Currently supported predicates are `path-exists` and `path-not-exists`. Unknown predicates are ignored. This is preferable to using `priority`, but only available since Cockpit 286.",
      "$ref": "#/$defs/condition-types"
    },
    "requires": {
      "description": "An optional JSON object that contains a \"cockpit\" string version number. The package will only be usable if the Cockpit bridge and javascript base are equal or newer than the given version number.",
      "type": "object",
      "required": [ "cockpit" ],
      "properties": {
        "cockpit": {
          "type": "string",
          "pattern": "^\\d[\\d|\\.]*$"
        }
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "version": {
      "description": "An informational version number for the package.",
      "type": [
        "string",
        "number"
      ]
    },
    "preload": {
      "description": "A list of identifiers of the components that should be preloaded. Normally, the files of a component are loaded when the user navigates to it for the first time. The files of a preloaded component are loaded immediately after the user logs in, and the initialization code of the component is invoked. \n\nThe value of this field is an array of strings, where each string is one of the keys used in the `dashboard`, `menu`, or `tools` fields",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "parent": {
      "description": "Used when module does not have its own menu item but is a part of a different module.",
      "type": "object",
      "properties": {
        "component": {
          "description": "The name of the superordinate component.",
          "type": "string"
        },
        "docs": {
          "$ref": "#/$defs/docs"
        }
      }
    },
    "dashboard": {
      "description": "Dashboard items appear in the menu under the section Apps.",
      "$ref": "#/$defs/menu-type"
    },
    "menu": {
      "description": "These items appear in the menu under the section System. This section is roughly ordered into these categories (with their order in parentheses):\n- System Information (10) \n- Logs (20) \n- Configuring major subsystems (30-40) \n- Things running on the machine (VMs, Containers - 50-60) \n- Implementation Details (Accounts, Services - 70-100).",
      "$ref": "#/$defs/menu-type"
    },
    "tools": {
      "description": "These items appear in the menu under the section Tools.",
      "$ref": "#/$defs/menu-type"
    },
    "bridges": {
      "description": "Details for additional server-side bridges. Each item specifies how to match requests and which executable to use.",
      "type": "array",
      "items": {
        "$comment": "A privileged bridge can not have a 'match' property.",
        "type": "object",
        "if": {
          "required": [ "privileged" ],
          "properties": {
            "privileged": {
              "const": true
            }
          }
        },
        "then": {
          "properties": {
            "match": false
          }
        },
        "required": [ "spawn" ],
        "properties": {
          "environ": {
            "description": "Optional, an array of environment variables to pass to the bridge command. Dynamic values can be used with ${} syntax.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "match": {
            "type": "object",
            "required": [ "payload" ],
            "properties": {
              "payload": {
                "description": "The payload pattern to match incoming requests.",
                "type": "string"
              }
            }
          },
          "privileged": {
            "description": "If set to true, marks the bridge as a superuser bridge, to be started with escalated privileges. A privileged bridge can not have a \"match\" property.",
            "default": false,
            "type": "boolean"
          },
          "label": {
            "description": "Optional, sets a label on the bridge for selection in the UI, useful for privileged bridges.",
            "type": "string"
          },
          "problem": {
            "description": "If specified, when bridge fails to start it will close the associated channel with this problem code.",
            "type": "string"
          },
          "spawn": {
            "description": "The command and arguments to invoke the bridge. Dynamic values can be used with ${} syntax.",
            "type": "array"
          }
        }
      }
    }
  },
  "$defs": {
    "docs": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "description": "List of documentation URLs for the given page, describing where the component is located within Cockpit.",
      "type": "array",
      "items": {
        "type": "object",
        "required": [ "label", "url" ],
        "properties": {
          "label": {
            "description": "The label for the documentation page.",
            "type": "string"
          },
          "url": {
            "description": "The URL for the documentation page.",
            "type": "string"
          }
        }
      }
    },
    "menu-type": {
      "type": "object",
      "properties": {
        "index": {
          "description": "An index of menu items with labels and their order.",
          "type": "object",
          "properties": {
            "label": {
              "description": "The displayed text of the menu item.",
              "type": "string"
            },
            "order": {
              "description": "An optional order integer to place this menu item or tool. Lower integers are listed first.",
              "type": "integer"
            },
            "path": {
              "description": "The relative path to the HTML file within the package that implements the menu item or tool.",
              "type": "string"
            },
            "docs": {
              "$ref": "#/$defs/docs"
            },
            "keywords": {
              "description": "Keywords that describe the page and which are used for searching. These keywords should be lowercase. Page label is prepended as first keyword in the first keyword item.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "matches": {
                    "description": "An array of strings matching keywords in lowercase.",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[^A-Z]+$"
                    }
                  },
                  "goto": {
                    "description": "Optional path that is used for all keywords in this item. When this argument starts with slash, then it is used as pathname, otherwise it is 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` ignoring default page path.",
                    "type": "string"
                  },
                  "weight": {
                    "description": "How much keywords are prioritized over others.",
                    "default": 3,
                    "type": "integer"
                  },
                  "translate": {
                    "description": "If keywords should be localized or not",
                    "default": true,
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "predicates": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "path-exists": {
          "description": "A condition where a specific file path must exist.",
          "type": "string"
        },
        "path-not-exists": {
          "description": "A condition where a specific file path must not exist.",
          "type": "string"
        },
        "any": {
          "description": "A list of conditions that evaluate true if at least one of the predicates is met.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/predicates"
          }
        }
      }
    },
    "condition-types": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/predicates"
      }
    }
  }
}

---

### Doc Modules Guide Bundling Cockpit Guide (`doc/modules/guide/bundling/cockpit-guide.adoc`)

= Cockpit documentation
:leveloffset: +1

include::../pages/index.adoc[]

:leveloffset: +1

include::../pages/cockpit-manual.adoc[]

include::../pages/https.adoc[]

include::../pages/listen.adoc[]

include::../pages/startup.adoc[]

include::../pages/multi-host.adoc[]

include::../pages/authentication.adoc[]

include::../pages/sso.adoc[]

include::../pages/cert-authentication.adoc[]

include::../pages/privileges.adoc[]

:leveloffset: -1

include::../pages/features.adoc[]

include::../pages/development.adoc[]

:leveloffset: -1

---

### Doc Modules Guide Pages Api Base1 (`doc/modules/guide/pages/api-base1.adoc`)

= API: base1

This package contains basic support API available to other packages.

---

### Doc Modules Guide Pages Api Cockpit (`doc/modules/guide/pages/api-cockpit.adoc`)

= cockpit.js

Basic cockpit API to interact with the system

[[api-cockpit-loading]]
== Loading cockpit.js

`+cockpit.js+` should be loaded via a script tag.

....
<script src="../base1/cockpit.js">
....

---

### Doc Modules Guide Pages Api Logs Html (`doc/modules/guide/pages/api-logs-html.adoc`)

= logs.html

System log component

[[api-logs-html-description]]
== Description
....
<iframe src="http://127.0.0.1:9090/cockpit+app/@localhost/system/logs.html"
    width="600" height="400"></iframe>
....

This is a Cockpit component that brings up system log viewer, with
filtering capabilities. On systemd based systems this displays the
entries from journal.

Component URL::
  `+/cockpit+app/@localhost/system/logs.html+`
Filter by priority::
  `+#?prio=notice+` +
   +
  Filters the log to show entries below the specific priority,
  inclusive. These
  https://en.wikipedia.org/wiki/Syslog#Severity_levels[priorities are
  syslog level keywords]. Specifying `+*+` as a priority will show all
  available entries. The default priority is `+err+`.
Filter by service::
  `+#?service=sshd.service+` +
   +
  Filters the log to show entries related to the specific service. The
  format of the service is specific to the logging implementation. For
  journald these are systemd service unit names.
Filter by tag::
  `+#?tag=kernel+` +
   +
  Filters the log to show entries related to the specific syslog
  identifier.
Filter by message::
  `+#?grep=+` +
   +
  Filters the log to show entries where the `+MESSAGE=+` field matches
  the specified regular expression. PERL-compatible regular expressions
  are used. If the pattern is all lowercase, matching is case
  insensitive. Otherwise, matching is case sensitive.
Filter by any field::
  `+#?FIELD=VALUE+` +
   +
  A field is referring to the components of a structured journal entry.
  The match must be exact. Value can be comma separated list in which
  case they are automatically matched as alternatives.
Start at point::
  `+#?start=boot+` +
   +
  Filters the log to show entries after the specific point in time.
  Valid values are `+boot+` (since last boot), `+last-24h+` (last 24
  hours), `+last-week+` (last seven days) and `+previous-boot+`
  (previous boot).
Follow the journal::
  `+#?follow=true+` +
   +
  Keep listening on new entries. Valid values are `+true+` (follow) and
  `+false+` (do not follow).
Show log entry::
  `+#/6e272d82993c4e0d...+` +
   +
  To show a specific log entry, put the log entry cursor in a path after
  the hash above. Note that cursors are logging system specific, and
  journal cursors are subject to change.

---

### Doc Modules Guide Pages Api Shell Html (`doc/modules/guide/pages/api-shell-html.adoc`)

= index.html

....
<iframe src="http://127.0.0.1:9090/cockpit+app/@localhost/shell/index.html"
    width="600" height="400"></iframe>
....

This is a Cockpit component that provides an interface to configure a
single machine. It loads other components on demand to make that happen.

Component URL::
  `+/cockpit+app/@localhost/shell/index.html+`

---

### Doc Modules Guide Pages Api Shell (`doc/modules/guide/pages/api-shell.adoc`)

= API: shell

This package contains the shell that loads other components.

include::api-shell-html.adoc[leveloffset=+1]

---

### Doc Modules Guide Pages Api System (`doc/modules/guide/pages/api-system.adoc`)

= API: system
:leveloffset: +1

This package contains general components for basic control of a system.

include::api-logs-html.adoc[]

include::api-terminal-html.adoc[]

:leveloffset: -1

---

### Doc Modules Guide Pages Api Terminal Html (`doc/modules/guide/pages/api-terminal-html.adoc`)

= terminal.html

Server terminal component

[[api-terminal-html-description]]
== Description
....
<iframe src="http://127.0.0.1:9090/cockpit+app/@localhost/system/terminal.html"
    width="600" height="400"></iframe>
....

This is a Cockpit component that brings up a web-based terminal for the
logged in user.

Component URL::
  `+/cockpit+app/@localhost/system/terminal.html+`

---

### Doc Modules Guide Pages Authentication (`doc/modules/guide/pages/authentication.adoc`)

[[authentication]]
= Cockpit Authentication

While cockpit allows you to monitor and administer several servers at
the same time, there is always a primary server your browser connects to
that runs the Cockpit web service (cockpit-ws) through which connections
to additional servers are established. See
https://raw.githubusercontent.com/cockpit-project/cockpit/main/doc/cockpit-transport.png[this
diagram] for how it works.

Normally, a session is established on the primary server, and you use
the Shell UI of that session to connect to secondary servers.

However, it is also possible to instruct the `+cockpit-ws+` process on
the primary server to directly connect to a secondary server, without
opening a session on the primary server at all. This is done on the main
login page of Cockpit, by filling out the "Connect to" field.

[[initial-auth]]
== Directly logging into the primary server

The most common way to use Cockpit is to just log directly into the
server that you want to access. This can be done if you have direct
network access to port 9090 on that server.

By default the cockpit web service is installed on thec base system and
xref:listen.adoc[socket activated by systemd]. In this setup access is
controlled by a cockpit specific pam stack, generally located at
`+/etc/pam.d/cockpit+`. By default this is configured to allow you to
login with the username and password of any local account on the system.
You can also setup a xref:sso.adoc[Kerberos based SSO solution] or
xref:cert-authentication.adoc[certificate/smart card authentication].

You can also
https://github.com/cockpit-project/cockpit/blob/main/doc/authentication.md#actions[disable
authentication schemes] to enforce authentication policies, or to
suppress undesired browser GSSAPI authentication dialogs.

The web server can also be run from the
https://quay.io/cockpit/ws[cockpit/ws] container. If you are
running cockpit on a container host operating system like
https://getfedora.org/coreos/[Fedora CoreOS] this will be the only
supported mode. In this setup, cockpit establishes an SSH connection
from the container to the underlying host, meaning that it is up to your
SSH server to grant access. To login with a local account, `+sshd+` will
need to be configured to allow password based authentication.
Alternatively you can setup a xref:sso.adoc[Kerberos based SSO solution].

Like `+sshd+`, cockpit can be configured to limit the number of
concurrent login attempts allowed. This is done by adding a
`+MaxStartups+` option to the `+WebService+` section of your
`+cockpit.conf+`. Additional connections will be dropped until
authentication succeeds or the connections are closed.

[[direct-secondary-auth]]
== Directly logging into a secondary server without a primary session

It is also possible to log into a secondary server without opening a
session on the primary server. This is useful if you are not actually
interested in the primary server and would only use it because you do
not have direct network access to the secondary server.

In this case, `+cockpit-ws+` still runs on the primary server, but the
credentials from the login screen are directly used with SSH to log into
the secondary server given in the "Connect To" field of the login
screen.

Thus, the PAM configuration and accounts on the primary server don't
matter at all. Often, the only purpose of the primary server is to sit
on the boundary of your network and forward connections to internal
machines.

In this case, the login page will prompt you to verify unknown SSH keys.
Accepted keys will be remembered in the local storage of your browser.

[[secondary-auth]]
== Logging into a secondary server from the primary session

Once you have a session on the primary server, it is possible connect to
additional servers by using the host switching UI of the Cockpit Shell.
This is useful if you have direct network access to the primary server,
but not to the secondary server.

On the command line, you would log into the primary server and then use
SSH to log into the secondary one. Cockpit does just the same, and uses
SSH to log into the secondary server. Instead of running a interactive
shell there, however, it starts a `+cockpit-bridge+` process.

_Warning:_ Unlike with SSH on the command line though, this will also
load and use the Cockpit pages (i.e. JavaScript) from the remote
machine, which means that the remote machine can execute arbitrary code
on your primary and all other connected secondary machines. Hence, only
connect to _machines which you trust_.

Due to this security risk, this host switcher functionality is disabled
by default, except on long-term stable Linux distributions (Red Hat
Enterprise Linux 9, Debian 12, and Ubuntu 22.04/24.04 LTS). If you are
comfortable with the security implications, you can enable it manually
with the `+AllowMultiHost+` option in `+cockpit.conf+`.

These servers will need to be running an SSH server and be configured to
support one of the following authentication methods.

=== Password

The target server will need to have password based authentication
enabled in `+sshd+`.

=== Kerberos

The target server will need to be a member of the same domain as the
primary server and your domain must be whitelisted in your browser. See
the xref:sso.adoc[SSO documentation] for how to set this up.

=== Public key

When you successfully log into the primary server, a `+ssh-agent+` is
started and keys are loaded into it by running `+ssh-add+` without any
arguments. Any passphrase prompt is answered with the password used to
log into the primary server.

Cockpit provides a user interface for loading other keys into the agent
that could not be automatically loaded.

The target server will need to have public key authentication enabled in
`+sshd+`, and the public key you wish to use must be present in
`+~/.ssh/authorized_keys+`. Cockpit has a user interface for creating
SSH keys and for authorizing them.

[[host-keys]]
=== SSH host keys

Cockpit will prompt the user to verify unknown SSH host keys, and will
write accepted host keys into `+~/.ssh/known_hosts+`.

---

### Doc Modules Guide Pages Cert Authentication (`doc/modules/guide/pages/cert-authentication.adoc`)

[[cert-authentication]]
= Certificate/smart card authentication

Cockpit can use TLS client certificates for authenticating users.
Commonly these are provided by a smart card, but it's equally possible
to import certificates directly into the web browser.

This requires the host to be in an Identity Management domain like
https://www.freeipa.org[FreeIPA] or
https://en.wikipedia.org/wiki/Active_Directory[Active Directory], which
can associate certificates to users.

To authenticate users from a Identity Management domain, the server that
Cockpit is running on must be joined to that domain. See the
xref:sso.adoc#sso-server[SSO server requirements] for details.

[[certauth-server-cert-generation]]
== User certificate generation

Generating the certificates for users is usually done with a certificate
management system like https://pagure.io/certmonger[certmonger] or
https://www.freeipa.org/page/PKI[FreeIPA], which are not documented
here. This command generates a simple key and certificate request for
the "alice" user:

....
openssl req -new -newkey rsa:2048 -days 365 \
    -keyout alice.key -out alice.csr -subj "/CN=alice"
....

Now get this certificate request signed by the Certificate Authority of
your Identity Management domain, to get a PEM certificate. Browsers and
smart cart utilities accept PKCS#12 format for importing/transfer, so
convert the certificate/key pair; it will ask for and protect it with a
transfer password:

....
openssl pkcs12 -export -in alice.pem -inkey alice.key -out alice.p12
....

Don't forget to clean up the key file when you do not need it any more:

....
shred -u alice.key
....

You can now import `+alice.p12+` directly into your browser, with giving
the transfer password set above. Or
https://linux.die.net/man/1/pkcs15-init[put the certificate onto a smart
card]:

....
pkcs15-init --store-private-key alice.p12 --format pkcs12 --auth-id 01
....

[[certauth-server-ipa]]
== Certificate mapping with FreeIPA

The recommended method to sign a user certificate request and associate
it to a user is `+ipa cert-request+`:

....
ipa cert-request alice.csr --principal=alice --certificate-out=alice.pem
....

Alternatively, if you are using a different CA, you can use
`+ipa user-add-cert+` to associate the signed certificate to the user.
This expects PEM format, but without the `+-----BEGIN+`/`+-----END+`
markers:

....
ipa user-add-cert alice --certificate="$(grep -v ^---- alice.pem)"
....

See the
https://www.freeipa.org/page/V4/User_Certificates#Feature_Management[FreeIPA
User Certificates documentation] for details.

[[certauth-server-ms-ad]]
== Certificate mapping with Microsoft Active Directory

The domain user certificates get imported into the
`+userCertificate;binary+` LDAP attribute. The following commands
convert the PEM certificate into binary DER form, create an
https://ldap.com/ldif-the-ldap-data-interchange-format/[LDIF] file and
apply it to the LDAP server running on the domain controller
"dc.example.com":

....
openssl x509 -outform der -in alice.pem -out alice.der

cat <<EOF > alice.ldif
version: 1
dn: cn=alice,ou=users,ou=YOUR_NETBIOS_NAME,dc=example,dc=com
changetype: modify
add: userCertificate;binary
userCertificate;binary:< file://$(pwd)/alice.der
EOF

ldapmodify -H ldap://dc.example.com -f alice.ldif
....

[[certauth-server-samba-ad]]
== Certificate mapping with Samba Active Directory

At least some versions of https://www.samba.org/[Samba] do not support
the `+userCertificate;binary+` LDAP attribute, so the import has to
happen in base64 PEM form into the textual `+userCertificate+` attribute
instead. Also, Samba uses a slightly different user hierarchy:

....
cat <<EOF > alice.ldif
version: 1
dn: cn=alice,cn=users,dc=example,dc=com
changetype: modify
add: userCertificate
userCertificate: $(grep -v ^---- alice.pem | tr -d '\n')
EOF

ldapmodify -H ldap://dc.example.com  -f alice.ldif
....

As `+userCertificate+` is a text instead of binary field, you need to
set up a
https://www.mankier.com/5/sssd.conf#Certificate_Mapping_Section[certificate
mapping rule] in `+sssd.conf(5)+` in a `+[certmap/domain/rulename]+`
section, for example:

....
[certmap/example.com/adcerts]
# we match full certificates, so it is not important to check anything here
matchrule = <KU>digitalSignature
maprule = LDAP:(userCertificate={cert!base64})
....

[[certauth-server-cockpitconf]]
== Cockpit web server configuration

Set the trusted Certificate Authority of your user certificates in
`+sssd+`, either by copying the CA PEM file to
`+/etc/sssd/pki/sssd_auth_ca_db.pem+` or setting the
https://www.mankier.com/5/sssd.conf#Services_Sections-PAM_configuration_options[`+pam_cert_db_path+`]
configuration option to the path of the CA. If you use FreeIPA and its
CA:

....
cp /etc/ipa/ca.crt /etc/sssd/pki/sssd_auth_ca_db.pem
....

Certificate authentication needs to be enabled in
man:cockpit.conf[5] explicitly:

....
[WebService]
ClientCertAuthentication = yes
....

When enabling this mode,
https://github.com/cockpit-project/cockpit/blob/main/doc/authentication.md[other
authentication types] commonly get disabled, so that _only_ client
certificate authentication will be accepted. By default, after a failed
certificate authentication attempt, Cockpit's normal login page will
appear and permit other login types such as `+basic+` (passwords) or
`+negotiate+` (Kerberos). For example, password authentication gets
disabled with:

....
[basic]
action = none
....

[[certauth-server-resourcelimits]]
== Cockpit web server resource limits

When using certificate authentication, all requests with a particular
certificate will be handled by a separate and isolated instance of the
man:cockpit-ws[8] web server. This protects against
possible vulnerabilities in the web server and prevents an attacker from
impersonating another user. However, this introduces a potential Denial
of Service: Some remote attacker could create a large number of
certificates and send a large number of http requests to Cockpit with
these.

To mitigate that, all `+cockpit-ws+` instances run in a
`+system-cockpithttps.slice+`
https://www.freedesktop.org/software/systemd/man/systemd.slice.html[systemd
slice unit] which
https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html[limits
the collective resources] of these web server instances: by default,
this slice sets a limit of 200 threads (roughly 100 instances of
`+cockpit-ws+` -- in other words, a maximum of 100 parallel user
sessions with different certificates) and a 75% (soft)/90% (hard) memory
limit.

You are welcome to adjust these limits to your need through a
https://www.freedesktop.org/software/systemd/man/systemd.unit.html[drop-in].
For example:

....
# systemctl edit system-cockpithttps.slice

[Slice]
# change existing value
TasksMax=100
# add new restriction
CPUQuota=30%
....

[[certauth-forwarding]]
== Authentication to other services like sudo and ssh

Once you logged into Cockpit with a certificate, you likely need to
switch to administrative mode (root privileges through sudo), or connect
to remote machines through SSH. If your user account has a password,
that can be used for authenticating to sudo or ssh as usual.

_Supported with FreeIPA only:_ As an alternative to password
authentication, you can also declare the initial Cockpit certificate
authentication as trusted for authenticating to SSH, sudo, or other
services. For that purpose, Cockpit automatically creates an
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/bde93b0e-f3c9-4ddf-9f44-e1453be7af5a[S4U2Proxy
Kerberos ticket] in the user session:

....
$ klist
Ticket cache: FILE:/run/user/1894000001/cockpit-session-3692.ccache
Default principal: user@EXAMPLE.COM

Valid starting     Expires            Service principal
07/30/21 09:19:06  07/31/21 09:19:06  HTTP/myhost.example.com@EXAMPLE.COM
07/30/21 09:19:06  07/31/21 09:19:06  krbtgt/EXAMPLE.COM@EXAMPLE.COM
    for client HTTP/myhost.example.com@EXAMPLE.COM
....

You can set up
https://www.freeipa.org/page/V4/Service_Constraint_Delegation[constrained
delegation rules] to enumerate which hosts (including its own) that
ticket is trusted to access. For example, if the cockpit session runs on
host `+myhost.example.com+` and should be trusted to access its own host
(through sudo) and another host `+remote.example.com+` (through ssh),
create a delegation like this:

....
# a list of target machines which can be accessed by a particular rule
ipa servicedelegationtarget-add cockpit-target
ipa servicedelegationtarget-add-member cockpit-target \
  --principals=host/myhost.example.com@EXAMPLE.COM \
  --principals=host/remote.example.com@EXAMPLE.COM

# allow cockpit sessions (HTTP/ principal) to access that host list
ipa servicedelegationrule-add cockpit-delegation
ipa servicedelegationrule-add-member cockpit-delegation \
  --principals=HTTP/myhost.example.com@EXAMPLE.COM
ipa servicedelegationrule-add-target cockpit-delegation \
  --servicedelegationtargets=cockpit-target
....

In addition, you need to enable GSS (Kerberos) authentication in the
corresponding services.

* For SSH, enable `+GSSAPIAuthentication yes+` in
https://linux.die.net/man/5/sshd_config[/etc/ssh/sshd_config].
* For sudo, enable `+pam_sss_gss+` as described in the
https://www.mankier.com/8/pam_sss_gss[manpage]: In
`+/etc/sssd/sssd.conf+`: Add an entry for your domain:
+
....
[domain/example.com]
pam_gssapi_services = sudo, sudo-i
....
+
In `+/etc/pam.d/sudo+`, enable the module in the first line:
+
....
auth sufficient pam_sss_gss.so
....

_Caveat:_ The delegated S4U ticket is not yet forwarded to remote SSH
hosts when connecting to them from Cockpit, so authenticating to sudo on
the remote host with that ticket does not work. This will be provided in
a future version.

---

### Doc Modules Guide Pages Cockpit Cache (`doc/modules/guide/pages/cockpit-cache.adoc`)

= cockpit.js: Object Caching

If the same information is displayed by multiple components in Cockpit,
`+cockpit.cache()+` provides a way to share data between them. The
shared data should be simple objects, arrays, and values, and not
contain functions or other objects.

[[cockpit-cache-func]]
== cockpit.cache()

....
cache = cockpit.cache(key, provider, consumer)
....

Create a new cache object. The `+key+` should be a globally unique
string that describes the data being cached. This string must describe
the data, across all machines and all versions of cockpit. It is
customary to include a version number in the `+key+` string.

....
function provider(result, key) {
    result("myvalue");

    return {
        close: function() {
            /* closed */
        }
    };
}
....

The `+provider+` is a function that will be invoked to start retrieving
data for the cache. It will be passed a `+result+` function as its first
argument. The `+result+` should be invoked whenever new data is
available. The `+key+` argument matches the key string the cache was
created with.

The `+provider+` can return an object with a `+close+` method. This
method will be invoked when the cache no longer needs data from the
provider.

....
function consumer(value, key) {
    /* ... */
}
....

The `+consumer+` is a function that will be passed new values when they
are available, whether they come from the `+provider+` or a source in a
different component/frame.

[[cockpit-cache-close]]
== cache.close()

....
cache.close()
....

Close a cache and stop calling its `+consumer+`. If the `+provider+` was
invoked, then the `+close()+` method it returned will be invoked.

---

### Doc Modules Guide Pages Cockpit Channels (`doc/modules/guide/pages/cockpit-channels.adoc`)

= cockpit.js: Raw Channels

At a low level Cockpit communicates with the system via messages passed
through various channels. These are usually exposed via higher level
APIs, such as the `+cockpit.spawn()+` function. It is rare to use raw
channels directly.

[[cockpit-channels-channel]]
== cockpit.channel()

....
channel = cockpit.channel(options)
....

This function creates a new channel for communication with the system.
It returns a new channel object. The `+options+` argument is a plain
object. At least the `+"payload"+` option is required, and based on the
payload type, other options may be required.

`+"binary"+`::
  Set to `+true+` to transfer binary payloads. Both messages sent via
  link:#cockpit-channels-send[`+channel.send()+`] and those received via
  link:#cockpit-channels-message[`+channel.onmessage+`] should be arrays
  of bytes, either `+Uint8Array+` or `+Array+` depending on browser
  support.
`+"payload"+`::
  The payload type for the channel. Only specific payload types are
  supported.
`+"superuser"+`::
  Set to `+"require"+` to open this channel as root. If the currently
  logged in user is not permitted to become root (eg: via `+pkexec+`)
  then the `+channel+` will immediately be
  link:#cockpit-channels-close-ev[closed] with a `+"access-denied"+`
  problem code.
  +
  Set to `+"try"+` to try to open the channel as root, but if that
  fails, then fall back to an unprivileged channel.

The channel object returned has the following fields and methods and
events. You should call the `+channel.close()+` method when done with
the channel.

A valid channel will always be returned and the is ready to
`+channel.send()+`. The channel may
link:#cockpit-channels-close-ev[close shortly afterword] due to a
failure.

[[cockpit-channels-binary]]
== channel.binary

Will be `+true+` for an binary channel. Will be set to `+false+` if the
channel is textual.

[[cockpit-channels-options]]
== channel.options

The options used to open this channel. This should not be changed.

[[cockpit-channels-valid]]
== channel.valid

Will be `+true+` for an open channel. Will be set to `+false+` if the
channel closes.

[[cockpit-channels-send]]
== channel.send()

....
channel.send(data)
....

Send a message over the channel. The contents of the message depends on
the payload type of the channel. If a binary channel, then `+data+` is
expected to be an `+Array+` of bytes or a `+Uint8Array+`. If not binary,
then the `+data+` will be converted to a string if not already a string.

[[cockpit-channels-control]]
== channel.control()

....
channel.control(options)
....

Notify the channel to tune certain parameters on the fly. The
`+options+` is a plain javascript object, and the contents depend on the
`+"payload"+` of the channel.

One common operation is to set `+"command"+` to `+"done"+` in the
options field. To indicate that no further messages will be sent through
the channel.

[[cockpit-channels-wait]]
== channel.wait()

....
promise = channel.wait([callback])
....

Returns a `+promise+` that is ready when the channel is ready, or fails
if the client closes. If a `+callback+` is specified, it is attached to
the promise. The promise will be rejected or resolved with the contents
`+options+` passed to the
link:#cockpit-channels-onready[channel.onready] and
link:#cockpit-channels-close-ev[channel.onclose] events respectively.

In general it's not necessary to wait for the channel before starting to
use the channel.

[[cockpit-channels-close]]
== channel.close()

....
channel.close([options])
....

Close the channel.

If `+options+` is present it can be a plain javascript object containing
additional channel close options to send to the peer. If closing for
because of a problem, set the `+"problem"+` field to a
xref:cockpit-error.adoc#cockpit-problems[problem code]. If `+options+` is not an object it
will be treated as a `+"problem"+`.

The link:#cockpit-channels-close-ev[close event] will fire. A channel
can also be closed by a peer or if the underlying transport closes.

[[cockpit-channels-message]]
== channel.onmessage

....
channel.addEventListener("message", function(event, data) { ... })
....

An event triggered when the channel receives a message. The message is
passed as a string to the handler in the `+data+`. In the case of binary
channels `+data+` is an `+Uint8Array+` or an `+Array+` of bytes if the
former is not supported by the browser. The contents of the message
depends on the payload type of the channel.

[[cockpit-channels-oncontrol]]
== channel.oncontrol

....
channel.addEventListener("control", function(event, options) { ... })
....

An event triggered when the channel receives an control message in the
middle of the flow. One particular use is when the `+command+` is set to
`+"done"+` then no further messages will be received in the channel. The
exact form of these messages depend on the `+"payload"+` of the channel.

[[cockpit-channels-onready]]
== channel.onready

....
channel.addEventListener("ready", function(event, options) { ... })
....

An event triggered when the other end of the channel is ready to start
processing messages. This indicates the channel is completely open. It
is possible to start sending messages on the channel before this point.

[[cockpit-channels-close-ev]]
== channel.onclose

....
channel.addEventListener("close", function(event, options) { ... })
....

An event triggered when the channel closes. This can happen either
because link:#cockpit-channels-close[channel.close()] function was
called, or if the peer closed the channel, or the underlying transport
closes.

The `+options+` will contain various close information, including a
`+"problem"+` field which will be set if the channel was closed because
of a problem.

[[cockpit-transport-origin]]
== cockpit.transport.origin

....
cockpit.transport.origin
....

The HTTP origin that is being used by the underlying channel transport.
This is read-only, you should not assign a value. If the browser
supports `+window.location.origin+` then this will be identical to that
value.

[[cockpit-transport-host]]
== cockpit.transport.host

....
cockpit.transport.host
....

The host that this transport is going to talk to by default. This is
read-only, you should not assign a value. If the value is null that
means that the transport has not been setup yet.

[[cockpit-transport-csrf-token]]
== cockpit.transport.csrf_token

....
cockpit.transport.csrf_token
....

A cross site request forgery token for use with external channels. This
becomes valid once the connection is properly established.

[[cockpit-transport-options]]
== cockpit.transport.options

....
cockpit.transport.options
....

Initialization options received over the underlying channel transport.
These will be empty until connection is properly established.

[[cockpit-transport-wait]]
== cockpit.transport.wait()

....
cockpit.transport.wait(callback)
....

Call the `+callback+` function once the underlying channel transport is
initialized. This will start the initialization if not already in
progress or completed. If the channel transport is already initialized,
then `+callback+` will be called immediately.

In general it's not necessary to wait for the transport before starting
to open channels.

[[cockpit-transport-close]]
== cockpit.transport.close()

....
cockpit.transport.close([problem])
....

Close the underlying channel transport. All channels open channels will
close. The `+problem+` argument should be a problem code string. If not
specified it will default to `+"disconnected"+`.

[[cockpit-transport-filter]]
== cockpit.transport.filter()

....
cockpit.transport.filter((message, channelid, control) =>  { ... })
....

Add a filter to the underlying channel transport. All incoming messages
will be passed to each of the filter callbacks that are registered.

This function is rarely used.

Filter callbacks are called in the order they are registered. If a
filter callback returns `+false+` then the message will not be
dispatched further, whether to other filters, or to channels, etc.

The `+message+` is the string or array with the raw message including,
the framing. The `+channelid+` is the channel identifier or an empty
string for control messages. If `+control+` is set then this is a
control message,d and the `+control+` argument contains the parsed JSON
object of the control message.

[[cockpit-transport-inject]]
== cockpit.transport.inject()

....
cockpit.transport.inject(message, [out])
....

Inject a message into the underlying channel transport. The `+message+`
should be a `+string+` or an array of bytes, and should be valid
according to the Cockpit message protocol. If the `+out+` argument is
equal to `+false+` then the message will be injected as an incoming
message as if it was received on the underlying channel transport.

This function is rarely used. In general you should only `+inject()+`
messages you got from a `+filter()+`.

[[cockpit-base64-encode]]
== cockpit.base64_encode()

....
string = cockpit.base64_encode(data)
....

Encode binary data into a string using the Base64 encoding. The `+data+`
argument can either be a `+string+`, an `+Array+`, an `+ArrayBuffer+` or
a `+Uint8Array+`. The return value is a string.

[[cockpit-base64-decode]]
== cockpit.base64_decode()

....
data = cockpit.base64_decode(string, [constructor])
....

Decode binary data from a Base64 encoded string. The `+string+` argument
should be a javascript string. The returned `+data+`> will be an array
of bytes.

You can pass `+Uint8Array+`, `+Array+` or `+String+` as an alternate
`+constructor+` if you want the decoded data in an alternate form. The
default is to return an `+Array+`. Note that if you use a `+String+` for
the decoded data, then you must guarantee that the data does not contain
bytes that would be invalid for a string.

---

### Doc Modules Guide Pages Cockpit Dbus (`doc/modules/guide/pages/cockpit-dbus.adoc`)

= cockpit.js: DBus Client

Cockpit allows access to DBus services via this API.

[[cockpit-dbus-types]]
== DBus Types

DBus values are represented as javascript values and objects as follows:

`+BYTE 'y'+`::
  Javascript number.
`+BOOLEAN 'b'+`::
  Javascript boolean.
`+INT16 'n'+`::
  Javascript number.
`+UINT16 'q'+`::
  Javascript number.
`+INT32 'i'+`::
  Javascript number.
`+UINT32 'u'+`::
  Javascript number.
`+INT64 'x'+`::
  Javascript number.
`+UINT64 't'+`::
  Javascript number.
`+DOUBLE 'd'+`::
  Javascript number.
`+STRING 's'+`::
  Javascript string.
`+OBJECT_PATH 'o'+`::
  Javascript string.
`+SIGNATURE 'g'+`::
  Javascript string.
`+ARRAY of BYTE 'ay'+`::
  A string containing base64 encoded data.
`+ARRAY of DICT_ENTRY with STRING keys 'a{s?}'+`::
  A javascript plain object with the keys as property names.
`+ARRAY of DICT_ENTRY with other keys 'a{??}'+`::
  A javascript plain object each key JSON encoded into a string property
  name.
`+ARRAY of other+`::
  A javascript array.
`+VARIANT+`::
  A javascript plain object with the `+"t"+` property set to a DBus type
  string, and the `+"v"+` property set to a value.
`+HANDLE 'h'+`::
  A javascript object that describes a cockpit channel which represents
  the passed file descriptor. The `+payload+` is always set to
  `+stream+`. Pass it to
  xref:cockpit-channels.adoc#cockpit-channels-channel[cockpit.channel()] to create the
  channel and start reading or writing on it. Handles can only be
  received, not sent from within cockpit.

[[cockpit-dbus-dbus]]
== cockpit.dbus()

....
client = cockpit.dbus(name, [options])
....

Create a DBus client for the given bus `+name+` (eg: service name). Use
the following functions to make DBus method calls, watch for events,
etc. The optional `+options+` argument is a javascript plain object, and
may include:

`+"bus"+`::
  The DBus bus to connect to. Specifying `+"session"+` will connect to
  the DBus user session bus, `+"user"+` will connect to the user bus (on
  some systems this is identical to the session bus), `+"system"+` will
  connect to the DBus system bus, and `+"none"+` to the non-standard bus
  specified with the `+address+` option. This defaults to "system" if
  not present.
`+"address"+`::
  The bus address to connect to in case `+bus+` is `+"none"+`.
`+"superuser"+`::
  Set to `+"require"+` to talk to this service as root. The DBus service
  will see the DBus method calls and accesses as coming from root,
  rather than the logged in user. This is useful for talking to services
  that do not correctly use
  https://www.freedesktop.org/software/polkit[polkit] to authorize
  administrative users. If the currently logged in user is not permitted
  to become root (eg: via `+pkexec+`) then the `+client+` will
  immediately be link:#cockpit-dbus-onclose[closed] with a
  `+"access-denied"+` problem code.
  +
  Set to `+"try"+` to try to talk as root, but if that fails, fall back
  to unprivileged.
`+"track"+`::
  It is valid for a DBus service to exit, and be restarted in such a way
  that clients continue to talk to it across the restart. Some services
  are not written with this in mind. If the `+"track"+` option is set to
  `+true+` then the channel will close when the service exits and/or
  disconnects from the DBus bus.

If the `+name+` argument is null, and no options other than `+"bus"+`
are specified, then a shared DBus `+client+` is created. When using such
a client with a DBus bus, a `+"name"+` option must be specified on
various other methods in order to specify which client to talk to.

[[cockpit-dbus-wait]]
== client.wait()

....
promise = client.wait([callback])
....

Returns a `+promise+` that is ready when the client is ready, or fails
if the client closes. If a `+callback+` is specified, it is attached to
the promise.

[[cockpit-dbus-close]]
== client.close()

....
client.close([problem])
....

Close the DBus client. If `+problem+` is specified it should be a
xref:cockpit-error.adoc#cockpit-problems[problem code] string.

[[cockpit-dbus-onclose]]
== client.onclose

....
client.addEventListener("close", options => { ... })
....

An event triggered when the DBus client closes. This can happen either
because link:#cockpit-dbus-close[client.close()] function was called, or
the DBus service went away, or some other problem or disconnection.

The `+options+` will contain various close information, including a
`+"problem"+` field which will be set if the channel was closed because
of a problem.

[[cockpit-dbus-onowned]]
== client.onowner

....
client.addEventListener("owner", (event, owner) => { ... })
....

An event triggered when the owner of the DBus name changes. The owner
value will be the id of the name owner on the bus or null if the name is
unowned. The absence of an owner should not be treated as a
disconnection. However this makes it possible to take some action based
on the actual status of the service, for example disconnecting a pending
signal handler.

[[cockpit-dbus-options]]
== client.options

Set to the options used when creating the client. Will not change for
the life of the client.

[[cockpit-dbus-unique-name]]
== client.unique_name

The unique DBus name of the client. Initially null, and becomes valid
once the the client is ready.

[[cockpit-dbus-proxy]]
== client.proxy()

....
proxy = client.proxy([interface, path], [options])
....

Create proxy javascript object for a DBus `+interface+`. At the
specified DBus object `+path+`. The proxy will have properties, methods
and signals from to the DBus interface, and allows for natural
interaction. If no `+interface+` is specified then the DBus bus name of
the client is used. If no `+path+` is specified, then the DBus name of
the client is converted to a path.

If creating lots of proxies for a given `+interface+` it is more
efficient to use the link:#cockpit-dbus-proxies[`+client.proxies()+`]
function.

The proxy is loaded when the
link:#cockpit-dbus-proxy-valid[`+proxy.valid+`] field is `+true+`, and
it is set to `+false+` if the underlying `+interface+` and/or `+path+`
don't or no longer exist, or the `+client+` has closed. You can wait for
proxy to become valid by passing a callback to its
link:#cockpit-dbus-proxy-wait[`+proxy.wait()+`] function. The
link:#cockpit-dbus-proxy-onchanged[`+proxy.onchanged+`] event will also
fire when the proxy becomes valid or invalid. DBus properties and
methods on the proxy are not defined until the proxy becomes valid.

....
value = proxy.Prop1
proxy.WritableProp = value
....

All DBus properties on the `+interface+` that start with an upper case
letter (as is convention) will be automatically defined on this proxy,
and will update their values as the DBus property values change. In
addition the link:#cockpit-dbus-proxy-onchanged[`+proxy.onchanged+`]
event will fire every time the properties change.

If you assign a value to a writable property on the proxy, the proxy
will try to set that property on the DBus `+interface+` at `+path+`. The
actual proxy property value will not update until the DBus service has
notified the proxy of the change. If setting a property fails a warning
will be logged. In order to have more reliable setting of properties, or
track when they have been set, or if setting fails, use the
link:#cockpit-dbus-call[`+client.call()+`] directly. It should be noted
that DBus service implementations may also be inconsistent in their
behavior when setting a property fails.

You can access the raw property data using the
link:#cockpit-dbus-proxy-data[`+proxy.data+`] field, including data for
properties that do not start with an upper case letter.

....
proxy.Method(arg1, arg2)
    .then((retval1, retval2) => {
        ...
    })
    .catch(ex => {
        ...
    });
....

All DBus methods on the `+interface+` that start with an upper case
letter (as is convention) will be automatically defined on this proxy.
These methods are called with arguments as normal javascript arguments.
A
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete successfully when the method returns, or fail if an
error occurs. The return values from the DBus method will be passed to
the `+then+` handler function directly.

Methods that do not start with an upper case letter can be invoked by
using the usual link:#cockpit-dbus-proxy-call[`+proxy.call()+`]
directly.

....
proxy.addEventListener("signal", (event, arg1, arg2) => {
    ...
});
....

All DBus signals on the `+interface+` that start with an upper case
letter (as is convention) will be automatically emit events on this
proxy. These events will contain the signal arguments after the standard
`+event+` argument.

Signals that do not start with an upper case letter can be subscribed to
by using link:#cockpit-dbus-proxy-signal[`+proxy.onsignal+`] directly.

Usually a proxy asks the `+client+` to watch and notify it of changes to
the relevant object or path. You can pass an `+options+` argument with
the `+watch+` field set to `+false+` to prevent this.

[[cockpit-dbus-proxy-client]]
== proxy.client

Set to the DBus client of the proxy. Will not change for the life of the
proxy.

[[cockpit-dbus-proxy-path]]
== proxy.path

Set to the DBus object path of the proxy. Will not change for the life
of the proxy.

[[cockpit-dbus-proxy-iface]]
== proxy.iface

Set to the DBus interface name of the proxy. Will not change for the
life of the proxy.

[[cockpit-dbus-proxy-valid]]
== proxy.valid

Set to `+true+` when the proxy's DBus interface is present at its DBus
path, and all information for the proxy has loaded. Is set to `+false+`
while loading, and after the proxy no longer refers a DBus interface and
path. Also set to `+false+` if the `+client+` closes.

Use the by link:#cockpit-dbus-proxy-wait[`+proxy.wait()+`] function to
wait for a proxy to load. The
link:#cockpit-dbus-proxy-onchanged[`+proxy.onchanged+`] event will also
be emitted when the proxy becomes valid or invalid. DBus properties and
methods on the proxy are not defined until the proxy becomes valid.

[[cockpit-dbus-proxy-data]]
== proxy.data

A plain javascript object containing all the raw property data that this
proxy has loaded. This will be updated automatically as the proxy is
notified of property changes from the DBus service. The
link:#cockpit-dbus-proxy-onchanged[`+proxy.onchanged+`] event will be
emitted when it changes.

[[cockpit-dbus-proxy-call]]
== proxy.call()

....
invocation = proxy.call(method, args, [options])
....

Make a DBus method call on this proxy.

For DBus methods that start with an upper case letter, is usually more
convenient link:#cockpit-dbus-proxy[to call the method directly on the
proxy]. However if methods that do not follow the usual DBus convention,
or specify additional options, or the caller cannot be sure that the
method actually exists, you can use this method.

This function also works on proxies that have are still loading and have
not become valid yet.

The `+method+` should be a DBus method name, and the `+args+` should be
an array of arguments to pass to the method. The `+options+` are
link:#cockpit-dbus-call[described elsewhere].

The returned value is identical to the one returned from
link:#cockpit-dbus-call[client.call()]. It is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete successfully when the method returns, or fail if an
error occurs.

[[cockpit-dbus-proxy-wait]]
== proxy.wait()

....
promise = proxy.wait()
proxy.wait(() => {
    ...
});
....

Wait for a proxy to finish loading. This function returns a promise. If
a callback function is passed as an argument then that function will be
invoked when the proxy is ready. If this method is called after a proxy
has already loaded, then the promise will be resolved immediately, and
any callback will be invoked immediately. Use the promise or
`+proxy.valid+` to determine whether the proxy is valid.

[[cockpit-dbus-proxy-onchanged]]
== proxy.onchanged

....
proxy.addEventListener("changed", (event, data) => {
    ...
});
....

This event is emitted when the proxy's properties change.

The `+data+` has the following form, and will only include properties
that have changed:

....
{
    "Prop1": "value",
    "Prop2": 5
}
....

[[cockpit-dbus-proxy-signal]]
== proxy.onsignal

....
proxy.addEventListener("signal", (event, name, args) => {
    ...
});
....

This event is emitted when the proxy's emits an event.

For most events, that have names which start with an upper case letter,
you can just link:#cockpit-dbus-proxy[connect to that event as a signal
directly]. However if you wish to be notified when any signal is
emitted, or for signals that do not follow the usual DBus convention,
you can connect to this event.

The `+name+` is the DBus signal name, and the `+args+` is an array of
arguments that were emitted with the signal.

[[cockpit-dbus-proxies]]
== client.proxies()

....
proxies = client.proxies([interface], [path_namespace], [options])
....

Create link:#cockpit-dbus-proxy[proxy javascript objects] for a DBus
interfaces. The proxies will have properties, methods and signals from
the DBus `+interface+`, and allow for natural interaction. If no
`+interface+` is specified then the DBus bus name of the client is used.
If no `+path_namespace+` is provided then `+"/"+` will be used.

Proxies will be automatically created for instances of the `+interface+`
available at the DBus service. The optional `+path_namespace+` argument
can be used to restrict the proxies for instances that have DBus paths
which have the namespace path prefix.

....
proxy1 = proxies["/dbus/path1"];
proxy2 = proxies["/dbus/path2"];
for (proxy in proxies) {
    ...
}
....

The returned `+proxies+` object will is used as a dictionary, and will
have values containing proxies for DBus interface instances, with the
keys being the DBus paths of those instances. It is possible to
enumerate over the returned `+proxies+`.

Proxies will be automatically added and removed from the `+proxies+`
object as they appear and disappear in the service. The
link:#cockpit-dbus-proxies-onadded[`+proxies.onadded+`] and
link:#cockpit-dbus-proxies-onremoved[`+proxies.onremoved+`] events will
be emitted. DBus services may not support notifications of paths
disappearing.

Use the `+proxies.wait()+` function to be notified when the initial set
of proxies has been populated.

Usually a proxies ask the `+client+` to watch and be notified of changes
to the relevant object or path. You can pass an `+options+` argument
with the `+watch+` field set to `+false+` to prevent this.

[[cockpit-dbus-proxies-wait]]
== proxies.wait()

....
promise = proxies.wait()
proxies.wait(() => {
    ...
});
....

Wait for a `+proxies+` object to populate its initial set of proxies.
This function returns a promise. If a callback function is passed as an
argument then that function will be invoked when the proxies are ready.
If this method is called after the proxies have populated, then the
promise will be resolved immediately, and any callback will be invoked
immediately.

[[cockpit-dbus-proxies-client]]
== proxies.client

Set to the DBus client of the proxies. Will not change.

[[cockpit-dbus-proxies-iface]]
== proxies.iface

Set to the DBus interface name of the proxies. Will not change.

[[cockpit-dbus-proxies-path_namespace]]
== proxies.path_namespace

Set to the DBus path namespace used which the proxies must have as a
DBus path prefix. Will not change.

[[cockpit-dbus-proxies-onadded]]
== proxies.onadded

....
proxies.addEventListener("added", (event, proxy) => {
    ...
})
....

This event is emitted when a proxy is added to the `+proxies+` object.
The proxy will already have loaded.

[[cockpit-dbus-proxies-onchanged]]
== proxies.onchanged

....
proxies.addEventListener("changed", (event, proxy) => {
    ...
})
....

This event is emitted when one of the proxy in the `+proxies+` object
changes its properties.

[[cockpit-dbus-proxies-onremoved]]
== proxies.onremoved

....
proxies.addEventListener("removed", (event, proxy) => {
    ...
})
....

This event is emitted when a proxy is removed to the `+proxies+` object.

[[cockpit-dbus-call]]
== client.call()

....
invocation = client.call(path, interface, method, args, [options])
....

Make a DBus method call.

The `+path+` is the DBus object path to make the call on, `+interface+`
is the DBus interface for the method and `+method+` is the name of the
method to call. The `+args+` is an array of arguments to pass to the
method, each of which must be appropriate for the expected
link:#cockpit-dbus[DBus type] of that argument. The `+args+` may be
`+null+` if no arguments are to be sent.

The returned value is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete successfully when the method returns, or fail if an
error occurs.

If `+options+` is specified it should be a plain javascript object,
which may contain the following properties:

`+flags+`::
  A string containing DBus message flags. The character `+"i"+`
  indicates to the dbus service that interactive authentication is
  allowed. If the entire `+flags+` field is missing, then `+"i"+` is set
  by default.
`+type+`::
  A valid DBus type signature to use when calling the method. In the
  absence of this, the DBus service will be introspected (and the result
  cached) to ask what the method type signature is.
`+timeout+`::
  The timeout of the call in milliseconds. The call will fail with the
  `+"timeout"+` problem code. If "timeout" is not given, the call will
  never time out.

[[cockpit-dbus-then]]
== invocation.then()

....
invocation.then((args, options) => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called when the DBus method call
finishes successfully.

The `+args+` argument is an array of return values from the DBus method.
Each of them will be converted to an appropriate
link:#cockpit-dbus[javascript type].

The `+options+` argument may contain additional information about the
reply. If the `+type+` option was specified when performing the method
call, then the `+options+` in the reply here will also contain a
`+type+` field containing the DBus type signature of the output. If the
`+flags+` option was specified when performing the call then the
`+options+` in the reply here will contain message flags. Possible out
message flags are:

`+>+`::
  A big endian message.
`+<+`::
  A little endian message.

[[cockpit-dbus-catch]]
== invocation.catch()

....
invocation.catch(exception => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called when the DBus method call
fails.

The `+exception+` object passed to the handler can have the following
properties:

`+problem+`::
  A xref:cockpit-error.adoc#cockpit-problems[problem code] string when a problem occurred
  starting or communicating with the DBus service. This is `+null+` in
  the cases where an actual DBus error was occurred.
`+name+`::
  The DBus error name. This will be `+null+` in cases where the failure
  was not due to a DBus error.
`+message+`::
  A DBus error message. This will be `+null+` in cases where the failure
  was not due to a DBus error.

[[cockpit-dbus-subscribe]]
== client.subscribe()

....
subscription = client.subscribe(match, (path, interface, signal, args) => { ... })
....

Subscribe to signals. The `+match+` argument is a javascript plain
object which defines what signals to subscribe to. Each property in the
`+match+` argument restricts signals subscribed to. If a property is not
present then it is treated as a wildcard, matching anything. If an empty
object is specified as `+match+` then all signals will be subscribed to.
The `+match+` argument may contain the following properties:

`+interface+`::
  A DBus interface to match.
`+path+`::
  A DBus object path to match. May not be used together with the
  `+path_namespace+` property. It should be a valid DBus object path,
  that is, it should have no trailing slash.
`+path_namespace+`::
  A DBus object path prefix to match. Any paths in the hierarchy below
  this top path will match. May not be used together with the `+path+`
  property.
`+member+`::
  The DBus signal name to match.
`+arg0+`::
  Matches the first argument of a DBus message, which must be a string.

The handler passed as the second argument will be invoked when the
signal is received. A `+subscription+` is returned which can be used to
remove the subscription by calling its `+subscription.remove()+` method.

It is not a problem to subscribe to the same signals more than once,
with identical or slightly different `+match+` arguments.

[[cockpit-dbus-remove]]
== subscription.remove()

....
subscription.remove()
....

Unsubscribe from the DBus signal subscription.

[[cockpit-dbus-watch]]
== client.watch()

....
watch = client.watch(path)
watch = client.watch({ "path_namespace": path_namespace, "interface": interface })
....

Watch for property and interface changes on the given DBus object
`+path+` DBus `+path_namespace+`. If `+interface+` is specified only
properties on that DBus interface will be watched.

The link:#cockpit-dbus-proxy[`+client.proxy()+`] and
link:#cockpit-dbus-proxies[`+client.proxies()+`] functions and the
objects they return are high level wrappers around `+client.watch()+`.

The property and interface changes will be available in raw form on the
link:#cockpit-dbus-onnotify[`+client.onnotify+`] event.

Property and interface changes that are caused by a method call or
signal will show up before that method call reply is received, or signal
event is triggered. It should be possible to rely on this guarantee,
unless the DBus service in question behaves incorrectly. Internally
these watches work well with code that implements the
https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager[ObjectManager]
portion of the DBus specification. If no ObjectManager implementation is
available, the watch falls back to using DBus
https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-introspectable[Introspection]
along with the usual
https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties[PropertiesChanged]
signal. If the DBus service implements none of these, or implements them
in an inconsistent manner, then this function will provide inconsistent
or unexpected results.

The parameter is either a DBus `+path+` or a plain javascript object
with zero or more of the following fields. If an empty javascript object
is used as an argument, then all paths, interfaces and properties will
be watched.

`+interface+`::
  Watch properties on this DBus interface.
`+path+`::
  Watch interfaces and properties at this DBus path. May not be used
  together with the `+path_namespace+` property.
`+path_namespace+`::
  Watch interfaces and properties under this DBus path. It should be a
  valid DBus object path, that is, it should have no trailing slash. If
  an ObjectManager implementation is available at this interface, then
  it is used. May not be used together with the `+path+` property.

The returned value is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete successfully when the watch has populated its initial
set of properties and interfaces, and these have been notified via
link:#cockpit-dbus-onnotify[`+client.onnotify+`].

A watch can be removed by calling the
link:#cockpit-dbus-watch-remove[`+watch.remove()+`] method on the
returned value. If identical watches are added more than once, then they
must also be removed the same number of times before the removal takes
effect.

[[cockpit-dbus-watch-then]]
== watch.then()

....
watch.then(() => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called when the watch has populated
its initial properties and interfaces.

[[cockpit-dbus-watch-catch]]
== watch.catch()

....
watch.catch(ex => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called if the watch fails to populate
its initial properties and interfaces. Note that a watch will only fail
if the DBus client closes or is somehow disconnected. It does not fail
in the case of missing interfaces or properties.

[[cockpit-dbus-watch-remove]]
== watch.remove()

....
watch.remove()
....

Remove the watch. This may not have any immediate effect if other
watches are in place. In particular, if identical watches are added more
than once, then they must also be removed the same number of times
before the removal takes effect.

[[cockpit-dbus-onnotify]]
== client.onnotify

....
client.addEventListener("notify", data => { ... })
....

An event triggered when link:#cockpit-dbus-watch[watched] properties or
interfaces change.

The link:#cockpit-dbus-proxy[`+client.proxy()+`] and
link:#cockpit-dbus-proxies[`+client.proxies()+`] functions and the
objects they return are high level wrappers around the `+data+` provided
by this event.

The `+data+` has the following form:

....
{
    "/path1": {
        "org.Interface1": {
            "Prop1": "value",
            "Prop2": 5
        },
        "org.Interface2": null
    }
}
....

Multiple paths may be present, each of which may have multiple
interfaces, each of which may have multiple properties. The first time a
given path and interface is emitted from this signal, it will have all
its properties and interfaces. Thereafter only changes are noted. If an
interface is set to `+null+`, then that interface has disappeared.

[[cockpit-dbus-notify]]
== client.notify()

....
client.notify(data)
....

Emits a synthetic link:#cockpit-dbus-onnotify[`+notify+`] event. The
`+data+` argument should follow the same layout as described for the
`+notify+` event.

[[cockpit-dbus-onmeta]]
== client.onmeta

....
client.onmeta = (ev, data) => { ... }
....

An event triggered when the meta data about
link:#cockpit-dbus-watch[watched] interfaces is loaded.

The link:#cockpit-dbus-proxy[`+client.proxy()+`] and
link:#cockpit-dbus-proxies[`+client.proxies()+`] functions and the
objects they return are high level wrappers around the `+data+` provided
by this event.

The `+data+` has the following form:

....
  {
      "org.Interface": {
          "methods": {
              "Method1": {
                  "in": [ "s", "v" ],
                  "out": [ "i" ]
              },
              "Method2": { }
          },
          "signals": {
              "Signal": {
                  "in": [ "b", "s" ]
              }
          },
          "properties": {
              "Prop1": {
                  "flags": "rw",
                  "type": "s"
              },
              "Prop2": {
                  "flags": "r",
                  "type": "b"
              }
          }
      }
  }
....

Multiple interfaces may be present, each of which may have methods and
properties. This is emitted before the first
link:#cockpit-dbus-proxy[`+client.onnotify+`] event for the relevant
interface.

[[cockpit-dbus-variant]]
== cockpit.variant()

....
variant = cockpit.variant(type, value)
....

A DBus variant is represented as a plain javascript object with a
`+"t"+` property represesting the full DBus type of the variant, and a
`+"v"+` property containing the variant value.

This is a helper function for creating such a variant object.

---

### Doc Modules Guide Pages Cockpit Error (`doc/modules/guide/pages/cockpit-error.adoc`)

= cockpit.js: Errors
cockpit.js: Errors

Problem codes and messages

[[cockpit-problems]]
== Problem Codes

Cockpit represents problems with standardized problem string codes.

`+"access-denied"+`::
  The user is not permitted to perform the action in question.
`+"authentication-failed"+`::
  User authentication failed.
`+"internal-error"+`::
  An unexpected internal error without further info. This should not
  happen during the normal course of operations.
`+"no-cockpit"+`::
  The system does not have a compatible version of Cockpit installed or
  installed properly.
`+"no-session"+`::
  Cockpit is not logged in.
`+"not-found"+`::
  Something specifically requested was not found, such as a file,
  executable etc.
`+"terminated"+`::
  Something was terminated forcibly, such as a connection, process
  session, etc.
`+"timeout"+`::
  Something timed out.
`+"unknown-hostkey"+`::
  The remote host had an unexpected or unknown key.
`+"no-forwarding"+`::
  Could not forward authentication credentials to the remote host.

[[cockpit-messages]]
== cockpit.message()

....
message = cockpit.message(problem)
message = cockpit.message(exception)
....

Return a message for the `+exception+` or `+problem+` code passed as an
argument. If the argument is an object with a `+"message"+` property, as
is the case with most exceptions, that will be returned directly. If the
argument is an object with a `+"problem"+` property, then it will be
used as the problem code. An appropriate message will be returned for
problem codes.

---

### Doc Modules Guide Pages Cockpit File (`doc/modules/guide/pages/cockpit-file.adoc`)

= cockpit.js: File Access

The `+cockpit.file+` API lets you read, write, and watch regular files
in their entirety. It cannot efficiently do random access in a big file
or read non-regular files such as `+/dev/random+`.

....
file = cockpit.file(path,
                    { syntax: syntax_object,
                      binary: boolean,
                      max_read_size: int,
                      superuser: string,
                    })

promise = file.read()
promise
    .then((content, tag) => { ... })
    .catch(error => { ... })

promise = file.replace(content, [ expected_tag ])
promise
    .then(new_tag => { ... })
    .catch(error => { ... })

promise = file.modify(callback, [ initial_content, initial_tag ]
promise
    .then((new_content, new_tag) => { ... })
    .catch(error => { ... })

file.watch((content, tag, [error]) => { }, [ { read: boolean } ])

file.close()
....

[[cockpit-file-simple]]
== Simple reading and writing

You can read a file with code like this:

....
cockpit.file("/path/to/file").read()
    .then((content, tag) => {
        ...
    })
    .catch(error => {
        ...
    });
....

It is recommended to use absolute paths. Relative paths are resolved
against `+/+`. To work with the current user's files
xref:cockpit-login.adoc#cockpit-user[cockpit.user()] can be used to get the user's home
directory.

The `+read()+` method returns a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise].

When successful, the promise will be resolved with the content of the
file. Unless you specify options to change this (see below), the file is
assumed to be text in the UTF-8 encoding, and `+content+` will be a
string.

The tag that is passed to the `+then()+` callback is a short string that
is associated with the file and changes whenever the content of the file
changes. It is meant to be used with `+replace()+`.

It is not an error when the file does not exist. In this case, the
`+then()+` callback will be called with a `+null+` value for `+content+`
and `+tag+` is `+"-"+`.

The `+superuser+` option can be used the same way as described in the
xref:cockpit-channels.adoc#cockpit-channels-channel[cockpit.channel()] to provide a different
access level to the file.

You can use the `+max_read_size+` option to limit the amount of data
that is read. If the file is larger than the given number of bytes, no
data is read and the channel is closed with problem code `+too-large+`.
The default limit is 16 MiB. The limit can be completely removed by
setting it to -1.

To write to a file, use code like this:

....
cockpit.file("/path/to/file").replace("my new content\n")
    .then(tag => {
        ...
    })
    .catch(error => {
        ...
    });
....

The `+replace()+` method returns a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise].

When the promise is resolved, the file has been atomically replaced (via
the `+rename()+` syscall) with the new content. As with `+read()+`, by
default the new content is a string and will be written to the file as
UTF-8. The returned tag corresponds to the new content of the file.

When the promise is rejected because of an error, the file or its meta
data has not been changed in any way.

As a special case, passing the value `+null+` to `+replace()+` will
remove the file.

The `+replace()+` method can also check for conflicting changes to a
file. You can pass a tag (as returned by `+read()+` or `+replace()+`) to
`+replace()+`, and the file will only be replaced if it still has the
given tag. If the tag of the file has changed, `+replace()+` will fail
with an error object that has `+error.problem == "change-conflict"+`.
See `+modify()+` below for a convenient way to achieve transactional
updates to a file.

[[cockpit-file-format]]
== File format

By default, a file is assumed to be text encoded in UTF-8, and the
`+read()+` and `+replace()+` functions use strings to represent the
content.

By specifying the `+syntax.parser()+` and `+syntax.stringify()+`
options, you can cause `+read()+` to parse the content before passing it
back to you, and `+replace()+` to unparse it before writing.

The main idea is to be able to write `+{ syntax: JSON }+`, of course,
but you can easily pass in individual functions or make your own
parser/unparser object:

....
cockpit.file("/path/to/file.json", { syntax: JSON })

var syntax_object = {
    parse:     my_parser,
    stringify: my_unparser
};

cockpit.file("/path/to/file", { syntax: syntax_object })
....

Any exceptions thrown by the `+parse()+` and `+stringify()+` functions
are caught and reported as read or write errors.

The `+null+` value that is used to represent the content of a
non-existing file (see "Simple reading and writing", above) is not
passed through the `+parse()+` and `+stringify()+` functions.

[[cockpit-file-binary]]
== Binary files

By default the content of the file is assumed to be text encoded as
UTF-8 and it can not contain zero bytes. The content is represented as a
JavaScript string with `+read()+`, `+replace()+`, etc. By setting the
`+binary+` option to true when creating the proxy, no assumptions are
placed on the content, and it is represented as a `+Uint8Array+` in
JavaScript.

[[cockpit-file-atomic]]
== Atomic modifications

Use `+modify()+` to modify the content of the file safely. A call to
`+modify()+` will read the content of the file, call `+callback+` on the
content, and then replace the content of the file with the return value
of the callback.

The `+modify()+` method uses the `+read()+` and `+replace()+` methods
internally in the obvious way. Thus, the `+syntax.parse()+` and
`+syntax.stringify()+` options work as expected, `+null+` represents a
non-existing file, and the watch callbacks are fired.

It will do this one or more times, until no other conflicting changes
have been made to the file between reading and replacing it.

The callback is called like this

....
new_content = callback (old_content)
....

The callback is allowed to mutate `+old_content+`, but note that this
will also mutate the objects that are passed to the watch callbacks.
Returning `+undefined+` from the proxy is the same as returning
`+old_content+`.

The `+modify()+` method returns a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise].

The promise will be resolved with the new content and its tag, like so

....
function shout(old_content) {
    return old_content.toUpperCase();
}

cockpit.file("/path/to/file").modify(shout)
    .then((content, tag) => {
        ...
    })
    .catch(error => {
        ...
    });
....

If you have cached the last content and tag results of the `+read()+` or
`+modify()+` method, or the last values passed to a watch callback, you
can pass them to `+modify()+` as the second and third argument. In this
case, `+modify()+` will skip the initial read and start with the given
values.

[[cockpit-file-notify]]
== Change notifications

Calling `+watch()+` will start monitoring the file for external changes.

....
handle = file.watch(callback);

handle_no_read = file.watch(callback, { read: false });
....

Whenever a change occurs, the `+callback()+` is called with the new
content and tag of the file. This might happen because of external
changes, but also as part of calls to `+read()+`, `+replace()+`, and
`+modify()+`.

When a read error occurs, the `+callback()+` is called with an error as
a third argument. Write errors are not reported via the watch callback.

Calling `+watch()+` will also automatically call `+read()+` to get the
initial content of the file. Thus, you normally don't need to call
`+read()+` at all when using `+watch()+`.

To disable the automatic reading, e.g. for large files or unreadable
file system objects, set the `+read+` option to `+false+`. The first
`+content+` argument of the callback will then always be `+null+`.

To free the resources used for monitoring, call `+handle.remove()+`.

[[cockpit-file-path]]
== file.path

A string containing the path that was passed to the `+cockpit.file()+`
method.

[[cockpit-file-close]]
== Closing

Call the `+close()+` method on a file proxy to cancel all ongoing
operations, such as reading, writing, and monitoring. The proxy should
not be used after closing it.

---

### Doc Modules Guide Pages Cockpit Http (`doc/modules/guide/pages/cockpit-http.adoc`)

= cockpit.js: HTTP Client

Cockpit allows access to local HTTP and REST services via this API.

[[cockpit-http-constructor]]
== cockpit.http()

....
http = cockpit.http(endpoint, [options])
http = cockpit.http(options)
....

Create a new HTTP client. The `+endpoint+` can be a file path starting
with `+/+` to connect to a unix socket, or it can be a port number to
connect to. The optional `+options+` argument is a javascript plain
object, and may include:

`+"address"+`::
  Connect to an address other than localhost. Must be a valid host name
  or IP address. To use this option you also must provide a port number.
`+"port"+`::
  Port number to use with "address" option, when not given in
  `+endpoint+`.
`+"tls"+`::
  Object properties for an https connection. See
  `+http-stream2 TLS options+`.
`+"headers"+`::
  Additional HTTP headers to include with the HTTP request. This is a
  plain javascript object with each key as a header name, and each value
  as the header value.
`+"superuser"+`::
  Set to `+"require"+` to open this channel as root. If the currently
  logged in user is not permitted to become root (eg: via `+pkexec+`)
  then the `+channel+` will immediately be
  link:#cockpit-channels-close-ev[closed] with a `+"access-denied"+`
  problem code.
  +
  Set to `+"try"+` to try to make the request as root, but if that
  fails, fall back to perform an unprivileged request.
`+"tls"+`::
  If set to a plain javascript object, then the connection will be an
  HTTPS connection and include TLS encryption. The fields of the `+tls+`
  object declare various TLS configuration and data. All fields are
  optional:
  +
  * `+"authority"+`: Certificate authority(s) to expect as signers of
  the server's TLS certificate, represented as a plain javascript
  object. It should have either a `+"file"+` field containing a readable
  PEM file on the system containing authorities, or a `+"data"+` with
  PEM encoded certificate data.
  * `+"certificate"+`: A client certificate to use, represented as a
  plain javascript object. It should have either a `+"file"+` field
  containing a readable PEM file on the system to use as a certificate,
  or a `+"data"+` with PEM encoded certificate data.
  * `+"key"+`: A client key to use, represented as a plain javascript
  object. It should have either a `+"file"+` field containing a readable
  PEM file on the system to use as a key, or a `+"data"+` with PEM
  encoded key data.
  * `+"validate"+`: A boolean that describes whether to validate the
  server's TLS certificate or not. By default local connections are not
  validated, and remote connections are validated.

Here is a somewhat complex example of using most of the above
`+options+` when when calling `+cockpit.http()+`:

....
http = cockpit.http({
    "address": "localhost",
    "headers": {
        "Authorization": "Basic dXNlcjpwYXNzd29yZA=="
    },
    "port": 443,
    "tls": {
        "validate": true,
        "authority": {
            "file": "/etc/pki/tls/certs/ca-bundle.crt",
        },
        "certificate": {
            "data": "-----BEGIN CERTIFICATE-----\nMIIDsDCCA..."
        },
        "key": {
            "data": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBA..."
        }
    }
});
....

[[cockpit-http-get]]
== http.get()

....
request = http.get(path, [params, [headers]])
....

Perform an HTTP GET request for the given `+path+`. If the `+params+` is
specified it should be a plain javascript object, which will be turned
into a query string.

Optionally a plain javascript object containing headers can be included
in the `+headers+` argument.

The return value is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete if the request happens successfully, or fail if
there's a problem.

[[cockpit-http-post]]
== http.post()

....
request = http.post(path, body, [headers])
....

Perform an HTTP POST request for the given `+path+`. The `+body+` can be
a string, or a javascript plain object, which will be encoded as JSON
data. If `+body+` is `+undefined+` or `+null+` then an empty HTTP body
will be sent.

Optionally a plain javascript object containing headers can be included
in the `+headers+` argument.

The return value is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete if the request happens successfully, or fail if
there's a problem.

[[cockpit-http-request]]
== http.request()

....
request = http.request(options)
....

Perform an HTTP request. The `+options+` can contain the following:

`+"body"+`::
  The HTTP request body. If you do not specify a body, then you must
  call link:#cockpit-http-input[request.input()] to complete the body
  and allow the request to start.
`+"headers"+`::
  A javascript plain object containing HTTP headers.
`+"method"+`::
  The HTTP method. Defaults to `+"GET"+`.
`+"params"+`::
  A javascript plain object containing query string parameters.
`+"path"+`::
  The HTTP path. Defaults to `+/+`.

The return value is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
that will complete if the request happens successfully, or fail if
there's a problem.

[[cockpit-http-then]]
== request.then()

....
request.then(data => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called when the request finishes
successfully.

The `+data+` argument contains the body result of the request. If it a
string, unless the process was opened in binary mode, in which case the
`+data+` is an array of bytes. If a `+request.stream()+` handler is set
up, then any standard output data consumed by the handler will not be
included in the `+data+` argument.

[[cockpit-http-catch]]
== request.catch()

....
request.catch((exception[, data]) => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called when the request fails, or
returns an error code.

The `+exception+` object passed to the handler can have the following
fields:

`+problem+`::
  A xref:cockpit-error.adoc#cockpit-problems[problem code] string when a problem occurred
  starting or communicating with the server. This is `+null+` if the
  process exited or was terminated.
`+status+`::
  The numeric status of the response. This is `+null+` if no response
  was received.
`+reason+`::
  A string reason returned in the response. This is `+null+` if no
  response was received.
`+message+`::
  A string message returned in the response. This is `+null+` if no
  response was received.

If the request returned a response body, it will be available in the
`+data+` argument. Otherwise this argument will be `+undefined+`.

[[cockpit-http-response]]
== request.response()

....
request.response((status, headers) => { ... })
....

This sets up a handler to be called when the HTTP request gets the
initial response from the server. The `+status+` argument is the HTTP
status integer, and the `+headers+` is a plain javascript object
containing the headers of the response.

[[cockpit-http-stream]]
== request.stream()

....
request.stream(data => { ... })
....

This sets up a handler to be called when the request returns output
data. The handler will be called multiple times.

Only one handler may be registered at a time. Registering an additional
handler replaces the previous one. The handler receives either string
`+data+` or an array of binary bytes as its argument. A stream handler
may return a number, which indicates the number of characters or bytes
consumed from `+data+`. Any data not consumed will be included again the
next time the handler is called.

If a `+request.stream()+` handler is set up, then the `+request.then()+`
handlers will only get any remaining data not consumed by the stream
handler.

[[cockpit-http-input]]
== request.input()

....
request.input(data, [stream])
....

This method writes `+data+` to the HTTP request body. It is only valid
if no `+"body"+` has been specified in
link:#cockpit-http-request[http.request()] options. If `+stream+` is
`+true+` then this function can be called again to provide further data.

[[cockpit-http-close]]
== request.close()

....
request.close([problem])
....

Cancel the request. If `+problem+` is specified it should be a standard
xref:cockpit-error.adoc#cockpit-problems[problem code] string.

[[cockpit-http-close-all]]
== http.close()

....
http.close([problem])
....

Cancel all outstanding requests with the given problem code. This is
useful when you know that the server is going down soon.

---

### Doc Modules Guide Pages Cockpit Locale (`doc/modules/guide/pages/cockpit-locale.adoc`)

= cockpit.js: Localization

Cockpit provides a https://www.gnu.org/software/gettext/[`+gettext()+`]
like API for easy translation of strings.

[[cockpit-locale-language]]
== cockpit.language

The current locale language code. This is set based on the
link:#cockpit-locale-locale[`+cockpit.locale()+`] data loaded.

[[cockpit-locale-locale]]
== cockpit.locale()

....
cockpit.locale(po)
....

Load locale information for a given `+po+` data. The data should be JSON
data in the https://www.npmjs.org/package/po2json[po2json] format. The
data will be loaded globally. If `+po+` data has already been loaded,
then this will extend that loaded data with additional strings. Any
identical translations strings will be replaced with the new strings. A
`+null+` argument clears all the locale information previously loaded.

Various methods such as
link:#cockpit-locale-gettext[`+cockpit.gettext()+`] make use of the
loaded data.

[[cockpit-locale-gettext]]
== cockpit.gettext()

....
translated = cockpit.gettext([context], string)
var _ = cockpit.gettext
var C_ = cockpit.gettext
translated = _("string")
translated = C_("context", "string")
....

Lookup `+string+` for translation in the loaded locale data. The
translated string will be returned, or `+string+` will be returned if no
such translated string is present. The `+context+` argument is an
optional string used to qualify the string.

This function can be assigned to a variable called `+_+` (underscore)
which will make your code work with the typical `+_("string")+` syntax.

[[cockpit-locale-noop]]
== cockpit.noop()

....
var N_ = cockpit.noop
var NC_ = cockpit.noop
....

A noop function suitable for assigning to `+N_+` or `+NC_+` so that
gettext scanners will be able to find translatable strings. More
specifically this function returns its last argument.

[[cockpit-locale-ngettext]]
== cockpit.ngettext()

....
translated = cockpit.ngettext([context], string1, stringN, number)
....

Lookup a string appropriate for a pluralization form of the `+number+`.
Various languages have complex pluralization forms that go far between
the singular and plural forms speakers of English are familiar with. If
no such translated string is found then either one of `+string1+` or
`+stringN+` is returned according to simple pluralization rules.

The `+context+` argument is an optional string used to qualify the
string.

[[cockpit-locale-translate]]
== cockpit.translate()

....
cockpit.translate()
cockpit.translate(element, ...)
cockpit.translate(selection)
....

The document will be scanned for `+translate+` tags and they will be
translated according to the strings in loaded locale data. One or more
`+element+` arguments may be specified. These are DOM elements for
specific parts of the document to be translated. If no `+element+` is
specified then the entire document is translated.

If an array or array-like object is passed as a `+selection+` then all
DOM elements in the array will be treated as parts of the document to be
translated.

---

### Doc Modules Guide Pages Cockpit Location (`doc/modules/guide/pages/cockpit-location.adoc`)

= cockpit.js: Page Location and Jumping

Page location and navigation between components

[[cockpit-location-general]]
== Page location

....
location = cockpit.location
cockpit.location = "/path"
....

Cockpit components often have different views, without changing the HTML
file that is being viewed. These are known as pages.
`+cockpit.location+` is an object that can be used to read the current
page and to navigate to a different page location. It works by updating
`+window.location.hash+`.

The `+cockpit.location+` looks like a HTTP path with a possible query
string:

....
/path/sub/page?option=value,option2
....

The link:#cockpit-location-path[`+location.path+`] and
link:#cockpit-location-options[`+location.options+`] contain a parsed
form of the location. While the location cannot be modified in place, a
new one can be created by assigning a string to `+cockpit.location+` or
by calling the link:#cockpit-location-go[`+location.go()+`] function.

`+cockpit.location+` is designed similarly to `+window.location+` in
that the location object is preplaced whenever the current page location
changes. To be aware of when the page location changes listen for the
link:#cockpit-location-changed[`+cockpit.onlocationchanged+`] event.

Using the location object as a string will result in the
link:#cockpit-location-href[`+location.href+`].

[[cockpit-location-href]]
=== location.href

The string representation of this page location, including any options.

[[cockpit-location-path]]
=== location.path

An array of path segments, parsed and decoded appropriately. An empty
array denotes the root path.

[[cockpit-location-options]]
=== location.options

A javascript object containing the various options present in the
location.

If an option appears more than once, its value will be an array.

[[cockpit-location-go]]
=== location.go()

....
location.go(path, [options])
....

Changes the current location to the given `+path+` and `+options+`. If
the `+path+` argument is a string, it will be parsed into a path. If it
is a relative path, then the result will be relative to the current
`+location.path+`. If the `+path+` argument is an array of path
segments, it will be treated as a full parsed absolute path.

Any options found in a `+path+` will be added to those in the optional
`+options+` argument, and used in the result.

The location change will only take effect if the location has not
changed in the meantime. This can be to good effect by saving a
`+cockpit.location+` object and doing a conditional navigation, by
calling the saved `+location.go()+` method later. This will only
navigate if the user or other code has not navigated in the meantime.

[[cockpit-location-replace]]
=== location.replace()

....
location.replace(path, [options])
....

Similar to link:#cockpit-location-go[`+location.go()+`] except the
location change will not result in a navigation change in the browser's
history.

[[cockpit-location-decode]]
=== location.decode()

....
path = location.decode(href, [options])
....

Decode a cockpit href into its `+path+` array. If the `+options+`
argument is specified, then it will be populated with options found in
the href.

If href is a relative path it will be resolved relative to
`+location.href+`.

[[cockpit-location-encode]]
=== location.encode()

....
href = location.encode(path, [options])
....

Encode the given `+path+` and `+options+` into a cockpit href. The
`+path+` argument may be an array of path segments, or a string path. If
a relative path is passed, it will be resolved relative to
`+location.href+`.

[[cockpit-location-changed]]
=== cockpit.onlocationchanged

....
cockpit.addEventListener("locationchanged", function() { ... })
....

An event emitted when over the `+cockpit.location+` changes. Typically a
component reacts to this event by updating its interface to reflect the
new link:#cockpit-location-path[`+cockpit.location.path+`] and
link:#cockpit-location-options[`+cockpit.location.options+`].

This event is not triggered immediately during a `+location.go()+` or
similar call. It will be triggered asynchronously at a later time.

[[cockpit-jump]]
== Jumping between components

....
cockpit.jump("/system/log")
....

In Cockpit in there multiple components shown. In order to tell Cockpit
to jump to and show another component and a certain location within that
component, use the `+cockpit.jump()+` function. Stable component paths
are documented. Don't assume you can navigate into paths that are not
stable API.

[[cockpit-jump-jump]]
=== cockpit.jump()

....
cockpit.jump(path, [ host ])
....

Ask Cockpit to jump to another component. The location of the current
component will not be affected. The `+path+` argument can be a string
path, starting with `+/+` or an array containing the parts of a path
that will be joined to create a path. If `+host+` is not specified, then
the component on the same host as the caller will be displayed. If host
is null, then the host portion of the path will be removed, displaying
the component on the host that cockpit is connected directly to.

If the calling component is not running within Cockpit, or the calling
component is not currently displayed, then the jump will not happen, and
this function has no effect.

[[cockpit-jump-hidden]]
=== cockpit.hidden

A boolean property that indicates if the current component page is
visible or hidden. When the code or user jumps to another component, the
prior one remains loaded and initialized but is hidden. Use this
property together with the
link:#cockpit-jump-visibilitychange[`+cockpit.onvisibilitychange+`]
event to decide whether or not to perform expensive tasks to update the
interface.

This property is analogous to the `+document.hidden+` page visibility
API, but works with the document and frame implementation of Cockpit.

[[cockpit-jump-visibilitychange]]
=== cockpit.onvisibilitychange

....
cockpit.onvisibilitychange = function() { ... }
....

This event is emitted when the
link:#cockpit-jump-hidden[`+cockpit.hidden+`] property changes. This
event is similar to the `+document.onvisibilitychange+` API, but works
with the document and frame implementation of Cockpit.

---

### Doc Modules Guide Pages Cockpit Login (`doc/modules/guide/pages/cockpit-login.adoc`)

= cockpit.js: User Session

User information and login session state

[[cockpit-logout]]
== cockpit.logout()

....
cockpit.logout([reload])
....

Logout of Cockpit. Unless `+reload+` is `+false+` this will also cause
the page to be reloaded, so that the user can see the logged out state.

[[cockpit-user]]
== cockpit.user()

....
var promise = cockpit.user();
promise.then(user => { ... });
....

This object contains information about the user that's currently logged
into cockpit. The following fields are defined:

`+"id"+`::
  This is unix user id.
`+"gid"+`::
  This is unix user group id.
`+"name"+`::
  This is the unix user name like `+"root"+`.
`+"full_name"+`::
  This is a readable name for the user.
`+"groups"+`::
  This is an array of group names to which the user belongs. Since
  version 318, the first item in this list is the primary group.
`+"home"+`::
  This is user's home directory.
`+"shell"+`::
  This is unix user shell.

Returns a promise that completes once the user information is available.

[WARNING]
====
`+cockpit.user()+` is soft-deprecated since Cockpit 336, if your page
does not need to maintain compatibility with older Cockpit versions you
can use xref:cockpit-util.adoc#cockpit-info[cockpit.info] to obtain the user information.
====

[[cockpit-permission]]
== Permission lookup

Cockpit provides a mechanism for checking if the current user satisfies
a given criteria. This is meant for updating UI elements based on what
actions the user can perform. It is _not an access control mechanism_.

[[cockpit-permission-constructor]]
=== cockpit.permission()

....
permission = cockpit.permission([options])
....

Create a new permission object to check if the current user has a
particular permission specified by `+options+`:

`+admin: true+`::
  True if the session has superuser privileges, i.e. can run channels as
  root with `+{ superuser: "require" }+`.
`+group:+` _name_::
  True if the currently logged user is a member of group _name_.

The permission result is always true for the "root" user. When
`+options+` is not given, check if the current user is root.

[[cockpit-permission-allowed]]
=== permission.allowed

A boolean value which indicates if the permission is allowed or not.
This will be `+null+` if the permission is unknown, or there was an
error checking the permission or the permission data has not yet loaded.
This property will update asynchronously and if you wish to be notified
of changes connect to the
link:#cockpit-permission-changed[permission.onchanged] event.

[[cockpit-permission-changed]]
=== permission.onchanged

....
permission.addEventListener("changed", function() { ... })
....

This event is fired when the permission changes. In particular the
link:#cockpit-permission-allowed[permission.allowed] property.

[[cockpit-permission-close]]
=== permission.close()

....
permission.close()
....

Closes the permission object and tears down any registered callbacks and
dbus subscriptions.

---

### Doc Modules Guide Pages Cockpit Manifest (`doc/modules/guide/pages/cockpit-manifest.adoc`)

= cockpit.js: Manifests

[[cockpit-manifest-loading]]
== Loading Manifests

For a convenient access to all page manifests, include
`+<script src="../manifests.js"></script>+` into your page to register
the manifests at the `+cockpit.manifests+` global variable.

You can also load `+../manifests.json+` directly in your page, with
https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch[`+fetch()+`].

---

### Doc Modules Guide Pages Cockpit Manual (`doc/modules/guide/pages/cockpit-manual.adoc`)

[[cockpit-manual]]
= Manual pages
:leveloffset: +1
:man: ../../man/pages

include::{man}/cockpit.1.adoc[]

include::{man}/cockpit.conf.5.adoc[]

include::{man}/cockpit-ws.8.adoc[]

include::{man}/cockpit-tls.8.adoc[]

include::{man}/cockpit-desktop.1.adoc[]

include::{man}/cockpit-bridge.1.adoc[]

include::{man}/pam_ssh_add.8.adoc[]

:leveloffset: -1

---

### Doc Modules Guide Pages Cockpit Metrics (`doc/modules/guide/pages/cockpit-metrics.adoc`)

= cockpit.js: Metrics

Metrics about the system can be retrieved from several sources using
link:#cockpit-metrics-function[`+cockpit.metrics()+`] metrics channels. The
metrics are made available as series data, and can be used with the
xref:cockpit-series-data.adoc#cockpit-series[`+cockpit.series()+`] and
xref:cockpit-series-data.adoc#cockpit-grid[`+cockpit.grid()+`] facilities.

[[cockpit-metrics-function]]
== cockpit.metrics()

....
metrics = cockpit.metrics(interval, options, cache)
....

Opens a new metrics channel. The data retrieved will be available in the
link:#cockpit-metrics-series[`+metrics.series+`] series sink, and can be
used together with xref:cockpit-series-data.adoc#cockpit-grid[`+cockpit.grid()+`] objects.

The `+interval+` is in milliseconds, and is the granularity of the
series data retrieved. Any grids consuming the data must have the same
interval.

The `+cache+` argument is a cache identifier. If specified, then this
metrics channel will share data with other metrics channels of the same
identifier. Make sure to use a globally unique string.

The `+options+` argument is either a javascript plain object, or an
array of those. Each object can have the following fields.

`+"metrics"+`::
  An array of full metric descriptions, as javascript objects. The
  specifics of these, and how to determine which ones to use, can
  unfortunately only be found in the low-level protocol documentation.
  This option is required.
`+"source"+`::
  The source to use for real-time data. This is used by the `+follow+`
  method, see below. Set to `+"internal"+` to retrieve internal metrics
  read by the bridge. If set to `+"direct"+` or `+"pmcd"+` then data
  will be retrieved from https://pcp.io[PCP]if it is available. The
  default is `+"internal"+`.
`+"archive_source"+`::
  The source to use for retrieving historical data. This is used by the
  `+fetch+` method, see below. Set to `+"pcp-archive"+` to retrieve data
  from PCP archives. The default is not to try to retrieve historical
  data.

When the `+options+` argument is an array of javascript objects, then
the metrics channel tries to use them in order until one succeeds. This
way, you can prefer PCP as the source but fall back to internal metrics
when PCP is not available, for example. The channel gives no indication
which of the options has been used, and `+fetch+` and `+follow+` might
use different entries from the list.

[[cockpit-metrics-fetch]]
== metrics.fetch()

....
metrics.fetch(beg, end)
....

Retrieve archived metrics data between `+beg+` and `+end+`. The
arguments can either be numbers, in which case they are interval based
offsets, or they can be javascript Date objects.

[[cockpit-metrics-follow]]
== metrics.follow()

....
metrics.follow()
....

Start retrieving live metrics data as it become available.

[[cockpit-metrics-close]]
== metrics.close()

....
metrics.close()
....

Stop the retrieval of metrics and release resources.

[[cockpit-metrics-series]]
== metrics.series

The series sink where data retrieved data will be processed.

[[cockpit-metrics-meta]]
== metrics.meta

The metrics meta data last received.

[[cockpit-metrics-onchanged]]
== metrics.onchanged

....
metrics.onchanged = function() { }
....

An event triggered when one of the properties on this metrics object
changes.

---

### Doc Modules Guide Pages Cockpit Series Data (`doc/modules/guide/pages/cockpit-series-data.adoc`)

= cockpit.js: Series Data

Series data consists of values along a continuous (usually time) axis.
We can place these in grids which expose a distinct subset of these
values. These are the underlying mechanism for displaying metrics data
in graphs.

[[cockpit-grid]]
== cockpit.grid()

....
grid = cockpit.grid(interval, [beg, end])
....

Creates a grid object to contain series data.

The `+interval+` is the granularity of the grid. Usually this is a
number of milliseconds, when used with time series data. The `+beg+` and
`+end+` are the bounds of the grid. If omitted they will be set to zero
for an initially empty grid.

If `+beg+` and/or `+end+` are negative (including negative zero) then
they are interpreted in number of intervals relative to the current
time. Thus cockpit.grid(1000, -300, -0) will create a grid for the most
recent 5 minutes.

[[cockpit-grid-add]]
== grid.add()

....
row = grid.add(series, path)
row = grid.add(callback, [early])
row = grid.add()
....

Adds a row to the grid. The returned `+row+` is a Javascript array that
will contain series data. The arguments control how the row is populated
from the series data. The `+row+` is a sparse array. Its `+row.length+`
will not match the expected size of the grid, unless and until the row
has been completely filled in. The first index of the `+row+` will
contain the data from the series data at the
link:#cockpit-grid-beg[`+grid.beg+`] offset.

When no arguments are passed, an empty row is added, and it is not
populated with data.

When called with a `+series+` and `+path+` argument then the row will be
populated directly with series data. The `+series+` can either be a
link:#cockpit-series[series object] or an object that has an
`+obj.series+` property. The link:#cockpit-series-interval[series
interval] must match the interval of this grid. If `+path+` is missing
or empty, then the series data is placed into the row directly.
Otherwise `+path+` indicates which part of the series data to place in
the row. When `+path+` is an array, it is used as a set of property
names or array indexes to follow into nested series data. When `+path+`
is a dotted string, it is split and used the same way to locate the
correct value in nested series data. The exact format of the series data
depends on its producer, and relevant paths will be documented there.

If a `+callback+` function is specified, then it will be invoked to
provide series data for the row. The function is invoked as
`+callback(row, index, count)+`, where the `+row+` is the row to fill
in, the `+index+` is the index to start filling in and `+count+` is the
number of items to fill in. The `+this+` variable will be set to the
grid while invoking the `+callback+`. The callback is called after other
data rows for a given series have been filled in. Callbacks are called
in the order added, unless the `+early+` argument is set to `+true+`, in
which case the callback is called earlier than callbacks without the
`+early+` argument set.

To remove the row use the link:#cockpit-grid-remove[`+grid.remove()+`
method.]

The row will start being populated with data when the `+series+`
produces data. To make this happen right away, use the
link:#cockpit-grid-sync[`+grid.sync()+`] method.

[[cockpit-grid-remove]]
== grid.remove()

....
grid.remove(row)
....

Remove a previously added `+row+` from the grid. The row will no longer
be updated with series data.

[[cockpit-grid-sync]]
== grid.sync()

....
grid.sync()
....

Load or reload data from the series into the rows. This does not clear
the rows before populating them. Some data may be populated immediately,
others may have to wait until data can be loaded. Internally this
function calls link:#cockpit-series-load[`+series.load()+`] for each
series.

All rows with callbacks will be invoked to regenerate all the data. The
link:#cockpit-grid-onnotify[`+grid.onnotify+`] event will be triggered.
It is not necessary to call this function after a call of the
link:#cockpit-grid-move[`+grid.move()+`] method.

[[cockpit-grid-move]]
== grid.move()

....
grid.move(beg[, end])
....

Move the grid to new `+beg+` and `+end+` range. Data will be discarded
from the rows and link:#cockpit-grid-sync[`+grid.sync()+`] will be
called to load or reload series data for the new range of offsets.

If `+end+` is not specified it will be set to `+beg+`. If `+beg+` and/or
`+end+` are negative (including negative zero) then they will be set to
the number of intervals prior to the current time taken as an interval.

If `+beg+` and/or `+end+` are negative (including negative zero) then
they are interpreted in number of intervals relative to the current
time. Thus cockpit.grid(1000, -300, -0) will create a grid for the most
recent 5 minutes.

[[cockpit-grid-walk]]
== grid.walk()

....
grid.walk()
....

Move the grid forward every
link:#cockpit-grid-interval[`+grid.interval+`] milliseconds. To stop
moving forward, call link:#cockpit-grid-move[`+grid.move()+`].

[[cockpit-grid-notify]]
== grid.notify()

....
grid.notify(index, count)
....

This function is called to have rows with callbacks recalculate their
data. It is not normally necessary to call this function, as it will be
invoked automatically when new series data is available or has been
loaded. This function triggers the
link:#cockpit-grid-onnotify[`+grid.onnotify+`] event.

[[cockpit-grid-onnotify]]
== grid.onnotify

....
grid.addEventListener("notify", function(index, count) { ... });
....

An event that is triggered when some part of the series data in grid
changes. The `+index+` is the row index where things changed, and the
`+count+` is the length of the data that changed.

[[cockpit-grid-close]]
== grid.close()

....
grid.close()
....

Close the grid, and stop updating the rows.

[[cockpit-grid-interval]]
== grid.interval

The granularity of the grid. For time series data this is an interval in
milliseconds. In order to use a given link:#cockpit-grid[grid] and
link:#cockpit-series[series] together, their interval properties must
match.

[[cockpit-grid-beg]]
== grid.beg

The beginning offset of the series data in the grid. Do not set this
property directly. Use the link:#cockpit-grid-move[grid.move()] method
instead.

[[cockpit-grid-end]]
== grid.end

The ending offset of the series data in the grid. Do not set this
property directly. Use the link:#cockpit-grid-move[grid.move()] method
instead.

[[cockpit-series]]
== cockpit.series()

....
series = cockpit.series(interval, [cache, fetch])
....

Create a new sink of series data. This is usually done by producers of
series data, and it is rare to invoke this function directly.

The `+interval+` is the granularity of the series data. For time series
data this is an interval in milliseconds. If a `+cache+` string is
specified, series data will be cached across frames for series with the
same `+cache+` cache identifier to load and/or reload.

If a `+fetch+` callback is specified, then it will be invoked when grids
request certain ranges of data. The `+fetch+` callback is invoked with
`+function fetch(beg, end) { ... }+` range offsets. The
link:#cockpit-series-input[series.input()] should be called with data
retrieved, either immediately or at a later time. The callback may be
called multiple times for the same ranges of data. It is up to the
callback to determine when or whether it should retrieve the data more
than once.

A producer of series data, usually calls this function and creates
itself a `+obj.series+` property containing this series object.

[[cockpit-series-input]]
== series.input()

....
series.input(beg, items[, mapping])
....

Send series data into the series sink. Any grids that have added rows
based on this series, will have data filled in. The `+beg+` is the
beginning offset of `+items+`. The `+items+` are an array one or more
series data items.

Producers may wish to provide additional properties that can be used in
lookup paths that rows can pull from. This is done in the `+mapping+`
argument. If specified it is a tree of objects. Each sub object should
have a property with the name `+""+` empty string, which will be used as
the property name or index in place of the one used in the lookup path.

[[cockpit-series-load]]
== series.load()

....
series.load(beg, end)
....

Load data from the series into any grids that have rows based on this
series data. Any cached data will be filled in immediately. Any data not
cached, will be requested from the producer, if possible, and may arrive
at a later time.

The `+beg+` and `+end+` denote the range of data to load.

[[cockpit-series-interval]]
== series.interval

The granularity of the series. For time series data this is an interval
in milliseconds. In order to use a given link:#cockpit-grid[grid] and
link:#cockpit-series[series] together, their interval properties must
match.

[[cockpit-series-limit]]
== series.limit

The maximum number of items to cache for loading and/or reloading. You
can change this value to a different number. Having a number close to
zero will break certain usage of grids, such as
link:#cockpit-grid-walk[`+grid.walk()+`].

---

### Doc Modules Guide Pages Cockpit Spawn (`doc/modules/guide/pages/cockpit-spawn.adoc`)

= cockpit.js: Spawning Processes

This is the API for spawning a process and receiving its output, as well
as exit codes.

[[cockpit-spawn-spawn]]
== cockpit.spawn()

....
process = cockpit.spawn(args, [options])
....

Spawns a process on the system.

The `+args+` should be an array starting with the executable and
containing all the arguments to pass on the command line. If `+args+` is
a string then it is interpreted as an executable name. The optional
`+options+` argument is a javascript plain object and can contain any of
the following fields:

`+"binary"+`::
  If set to `+true+` then handle the input and output of the process as
  arrays of binary bytes.
`+"directory"+`::
  The directory to spawn the process in.
`+"err"+`::
  Controls where the standard error is sent. By default it is logged to
  the journal. If set to `+"out"+` it is included in with the output
  data. If set to `+"ignore"+` then the error output is discarded. If
  set to `+"message"+`, then it will be returned as the error message.
  When the `+"pty"+` field is set, this field has no effect.
`+"environ"+`::
  An optional array that contains strings to be used as additional
  environment variables for the new process. These are `+"NAME=VALUE"+`
  strings.
`+"pty"+`::
  Launch the process in its own PTY terminal, and send/receive terminal
  input and output.
`+"batch"+`::
  Batch data coming from the process in blocks of at least this size.
  This is not a guarantee. After a short timeout the data will be sent
  even if the data doesn't match the batch size. Defaults to zero.
`+"latency"+`::
  The timeout for flushing any cached data in milliseconds.
`+"superuser"+`::
  Set to `+"require"+` to spawn the process as root instead of the
  logged in user. If the currently logged in user is not permitted to
  become root (eg: via `+pkexec+`) then the `+client+` will immediately
  be xref:cockpit-dbus.adoc#cockpit-dbus-onclose[closed] with a `+"access-denied"+`
  problem code.
  +
  Set to `+"try"+` to try to run the process as root, but if that fails,
  fall back to an unprivileged process.

The spawned process is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[promise]
that will complete if the process exits successfully, or fail if there's
a problem. Some additional methods besides the standard promise methods
are documented below.

The standard output of the process is made available via the spawned
process object. Any non-UTF8 output from the process will be coerced
into textual form. It is highly recommended that only textual output be
produced by the command. The standard error is logged to the journal.

[[cockpit-spawn-script]]
== cockpit.script()

....
process = cockpit.script(script, [args], [options])
....

Run a shell script on the system.

This function link:#cockpit-spawn-spawn[spawns] a Bourne shell script
process. The full text of the `+/bin/sh+` shell script should be passed
in as the first argument. The `+args+` can be an array of arguments, not
including the executable, which are passed to the script as `+$1+`,
`+$2+` and so on. Shebang options are not used or respected.

The `+options+` is an optional javascript plain object and can include
any of the fields listed for the
link:#cockpit-spawn-spawn[`+cockpit.spawn()+`] function.

The spawned process is a
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[promise]
that will complete if the script exits successfully, or fail if there's
a problem. Some additional methods besides the standard promise methods
are documented below.

The standard output of the process is made available via the spawned
process object. Any non-UTF8 output from the process will be coerced
into textual form. It is highly recommended that only textual output be
produced by the command. The standard error is logged to the journal by
default.

[[cockpit-spawn-then]]
== process.then()

....
process.then((data[, message]) => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[promise]
method. It sets up a handler to be called when the process finishes
successfully.

The `+data+` argument contains the standard output of the process. If it
a string, unless the process was opened in binary mode, in which case
the `+data+` is an array of bytes. If a `+process.stream()+` handler is
set up, then any standard output data consumed by the handler will not
be included in the `+data+` argument.

If the process was spawned with the `+"err"+` option set to
`+"message"+` then the second argument will contain the standard error
output of the process.

[[cockpit-spawn-catch]]
== process.catch()

....
process.catch((exception[, data]) => { ... })
....

This is a standard
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise]
method. It sets up a handler to be called when the process fails,
terminates or exits.

The `+exception+` object passed to the handler can have the following
fields:

`+message+`::
  A message describing the exception. If the process was spawned with
  the `+"err"+` option set to `+"message"+` then the second argument
  will contain the standard error output of the process.
`+problem+`::
  A xref:cockpit-error.adoc#cockpit-problems[problem code] string when a problem occurred
  starting or communicating with the process. This is `+null+` if the
  process exited or was terminated.
`+exit_status+`::
  The numeric exit status of the process. This is `+null+` if the
  process did not exit.
`+exit_signal+`::
  A string representing a unix signal that caused the process to
  terminate. This is `+null+` if the process did not terminate because
  of a signal.

If the process actually ran and produced output before failing, it will
be available in the `+data+` argument. Otherwise this argument will be
`+undefined+`.

[[cockpit-spawn-stream]]
== process.stream()

....
process.stream(data => { ... })
....

This sets up a handler to be called when the process has standard
output. The handler will be called multiple times. The handler will be
called regardless of whether the process ends up exiting successfully or
not.

Only one handler may be registered at a time. Registering an additional
handler replaces the previous one. The handler receives either string
`+data+` or an array of binary bytes as its argument. A stream handler
may return a number, which indicates the number of characters or bytes
consumed from `+data+`. Any data not consumed will be included again the
next time the handler is called.

If a `+process.stream()+` handler is set up, then the `+process.then()+`
handlers will only get any remaining data not consumed by the stream
handler.

[[cockpit-spawn-input]]
== process.input()

....
process.input(data, [stream])
....

This method writes `+data+` to the standard input of the process. If
`+data+` is `+null+` or `+undefined+` it is not sent. The `+data+`
should be a string or an array of bytes if the process was opened in
binary mode.

If `+stream+` is set to `+true+` then this function may be called again
with further input. Otherwise the standard input of the process is
closed.

[[cockpit-spawn-close]]
== process.close()

....
process.close([problem])
....

Close the process by closing its standard input and output. If
`+problem+` is specified it should be a standard
xref:cockpit-error.adoc#cockpit-problems[problem code] string. In this case the process
will be terminated with a signal.

---

### Doc Modules Guide Pages Cockpit Util (`doc/modules/guide/pages/cockpit-util.adoc`)

= cockpit.js: Utilities

Various utility functions

[[cockpit-format]]
== cockpit.format()

....
string = cockpit.format(template, args)
string = cockpit.format(template, [arg, ...])
....

Format a string interpolating `+args+` into `+template+` using shell
like syntax. The `+args+` may be either an array or javascript object.
The `+template+` can contain fields that look like `+$name+` or
`+${name}+` or `+$0+`. Numeric fields are used with array `+args+` and
start at zero.

In the second form, multiple `+arg+` arguments may be passed directly,
and interpolated as as numeric fields in the `+template+`.

All falsy arguments except the numbers `+0+` and `+0.0+`are replaced by
an empty string.

[[cockpit-format-number]]
== cockpit.format_number()

....
string = cockpit.format_number(number, [precision])
....

Formats `+number+` into a displayable `+string+`. If the number is not
an integer, it is rounded to the given number of decimal places,
defaulting to 3. If the number is near zero, but not quite zero it is
rounded to the smallest non-zero value of the given precision; i.e.
±0.001 for default precision 3.

If `+number+` is `+null+` or `+undefined+` an empty string will be
returned.

[[cockpit-format-bytes]]
== cockpit.format_bytes()

....
string = cockpit.format_bytes(number, [options])
....

Formats `+number+` into a displayable `+string+` with a suffix, such as
_kB_ or _MB_.

By default, SI units are used. IEC units (1024-based) can be requested
by including `+base2: true+` in `+options+`.

By default, non-integer numbers will be formatted with 3 digits of
precision. This can be changed with `+options.precision+`.

If `+number+` is `+null+` or `+undefined+` an empty string will be
returned.

[[cockpit-format-bytes-per-sec]]
== cockpit.format_bytes_per_sec()

....
string = cockpit.format_bytes_per_sec(number, [options])
....

Format `+number+` of bytes into a displayable speed `+string+`.

This function is mostly equivalent to `+cockpit.format_bytes()+` but the
returned value contains a unit like _kB/s_ or _MB/s_.

[[cockpit-format-bits-per-sec]]
== cockpit.format_bits_per_sec()

....
string = cockpit.format_bits_per_sec(number, [options])
....

Format `+number+` of bits into a displayable speed `+string+`.

This function is mostly equivalent to `+cockpit.format_bytes()+` but the
returned value contains a unit like _kbps_ or _Mbps_.

This function does not support IEC units. `+base2+` may not be passed as
part of `+options+`.

[[cockpit-init]]
== cockpit.init()

....
await cockpit.init();

cockpit.init().then(() => { ... });
....

Requests initialization of the Cockpit client library. This will ensure
that the transport is connected and we are ready to create channels. It
also populates the `+cockpit.info+` field.

This function returns a promise. Initialization isn't complete until the
promise has resolved. You can either `+await+` it or call `+.then()+` on
it.

[[cockpit-info]]
== cockpit.info

....
cockpit.info.channels[payload]

cockpit.info.os_release[field]

cockpit.info.user

cockpit.info.ws.version
....

This object contains information about Cockpit itself. It is only
available after cockpit.init() has been called and awaited.

`+channels+`::
  This is a mapping of channel payload types (keys, strings) supported
  by the bridge to capabilities advertised by those channels (values,
  lists of strings). Channels are listed even if they don't advertise
  any capabilities, making this useful as a way to determine which
  channel types are supported by the bridge.
`+os_release+`::
  This is the data from the `+/etc/os-release+` or
  `+/usr/lib/os-release+` on the system that the bridge is running on.
  It is a mapping from the key names to their values. See the
  https://www.freedesktop.org/software/systemd/man/latest/os-release.html[os-release
  Specification] for information about the available keys.
`+user+`::
  Contains information about the user we're logged in as.
  `+uid+`;;
    This is unix user id as an integer.
  `+gid+`;;
    This is unix user group id as an integer.
  `+name+`;;
    This is the unix user name like `+"root"+`.
  `+fullname+`;;
    This is a readable name for the user, from the GECOS field.
  `+group+`;;
    This is the primary group name of the user.
  `+groups+`;;
    This is an array of group names to which the user belongs. The first
    item in this list is the primary group.
  `+home+`;;
    This is user's home directory.
  `+shell+`;;
    This is unix user shell.
`+ws+`::
  Contains information about the webserver Cockpit is being served with.
  `+version+`;;
    The version of the webserver.

[[cockpit-event-target]]
== cockpit.event_target

....
cockpit.event_target(object, [handlers])
....

Adds an
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget[EventTarget]
implementation to the `+object+`. Optionally store the handlers in
`+handlers+` if its specified.

---

### Doc Modules Guide Pages Development (`doc/modules/guide/pages/development.adoc`)

[[development]]
= Developer Guide
:leveloffset: +1

include::embedding.adoc[]

include::packages.adoc[]

include::urls.adoc[]

include::api-base1.adoc[]

:leveloffset: +1

include::api-cockpit.adoc[]

include::cockpit-cache.adoc[]

include::cockpit-channels.adoc[]

include::cockpit-dbus.adoc[]

include::cockpit-error.adoc[]

include::cockpit-file.adoc[]

include::cockpit-http.adoc[]

include::cockpit-locale.adoc[]

include::cockpit-location.adoc[]

include::cockpit-login.adoc[]

include::cockpit-manifest.adoc[]

include::cockpit-metrics.adoc[]

include::cockpit-series-data.adoc[]

include::cockpit-spawn.adoc[]

include::cockpit-util.adoc[]

:leveloffset: -1


include::api-shell.adoc[]

include::api-system.adoc[]

:leveloffset: -1

---

### Doc Modules Guide Pages Embedding (`doc/modules/guide/pages/embedding.adoc`)

[[embedding]]
= Embedding and Integrating Cockpit

Cockpit can be embedded in other web applications either as a whole or
specific Cockpit components can be integrated. Due to frame security
policy restrictions, this only works if Cockpit and the web application
have the _same origin_; this is commonly achieved by running both from a
common reverse proxy.

[[embedding-full]]
== Embedding the Cockpit Interface

Cockpit can be embedded into a larger web page as a frame. To embed the
entire Cockpit Window use the URI: `+https://server.example.com:9090/+`

[source,html]
----
<html>
  <head>
    <title>Embedded Cockpit</title>
  </head>
  <body>
    This is Cockpit.
    <br/>
    <iframe width="800px" height="600px"
            src="https://server.example.com:9090/"/>
  </body>
</html>
----

[[embedding-components]]
== Integrating Cockpit Components into Web Applications

Instead of embedding the entirety of Cockpit, you can integrate specific
components. Only those components explicitly documented as API should be
integrated. Other components can and will change regularly.

The component will load from the server in question and a WebSocket
connection will be established with the server to relay the component's
message stream.

Cockpit components are HTML files contained in xref:packages.adoc[packages].
These can be placed in an iframe or web browser window. Each documented
and stable component has a well-known URL and these are documented in
the xref:development.adoc[API reference]. Each component URL begins with the
string `+/cockpit/@localhost/+` followed a package name, and then the
component itself.

For example the xref:api-terminal-html.adoc[terminal.html] in the
xref:api-system.adoc[system] package, has this URL:
`+/cockpit/@localhost/system/terminal.html+`

[source,html]
----
<html>
  <head>
    <title>Embedded Terminal</title>
  </head>
  <body>
    This is a terminal.
    <br/>
    <iframe width="800px" height="600px"
      src="https://server.example.com:9090/cockpit/@localhost/system/terminal.html"/>
  </body>
</html>
----

[[embedding-deep]]
== Deep Integration

Most often link:#embedding-components[simple integration] will be used
to bring Cockpit components into web applications. However it is also
possible to do deep integration for embedders who wish to perform
non-standard authentication with the server, and relay the component's
message stream to the server themselves.

[WARNING]
====
Deep integration capability is in heavy flux and is not yet documented.
====

[[embedding-cors]]
== Pinging Cockpit

When embedding Cockpit or integrating Cockpit components, it may be
necessary to check whether Cockpit is available on a server before
proceeding.

To do this perform a `+/ping+` request to Cockpit. This is a simple HTTP
GET request. It returns the following:

....
GET: https://server.example.com:9090/ping
200 OK: { "service": "cockpit" }
....

The `+/ping+` request allows
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross Origin
Resource Sharing] headers and as such can be performed from Javascript
code with any origin. The request can also be made via plain HTTP
without SSL. It is by design that no further information is present in
the response.

A complete example of using `+/ping+` is available in the Cockpit
sources in the `+/examples/ping-server/+` directory.

---

### Doc Modules Guide Pages Feature Firewall (`doc/modules/guide/pages/feature-firewall.adoc`)

[[feature-firewall]]
= Firewall

Cockpit uses https://www.firewalld.org[firewalld] to interact with the
system's firewall. No firewall configuration UI will be shown if
firewalld is not installed.

Firewalld controls access to its APIs via PolicyKit. The user logged
into Cockpit needs to have the appropriate permissions to view or modify
the settings.

Cockpit can currently only show, add, and remove predefined firewalld
services in the default zone.

To perform similar tasks from the command line, use
https://www.firewalld.org/documentation/man-pages/firewall-cmd.html[firewall-cmd].
For example, to get the same list of allowed services that Cockpit
displays:

....
$ sudo firewall-cmd --list-services
dhcpv6-client samba-client mdns ssh cockpit
....

To enable an additional service, use:

....
$ firewall-cmd --add-service pop3
success
....

---

### Doc Modules Guide Pages Feature Journal (`doc/modules/guide/pages/feature-journal.adoc`)

[[feature-journal]]
= Journal

The systemd journal provides Cockpit with indexed log data. This log
data is found on the Journal page, as well as in various other places
when configuring services, storage, networking etc.

Cockpit accesses Journal data via the
https://www.freedesktop.org/software/systemd/man/journalctl.html[`+journalctl+`]
command. Similar tasks can be performed at the command line:

....
$ sudo journalctl -f -u docker
...
....

---

### Doc Modules Guide Pages Feature Machines (`doc/modules/guide/pages/feature-machines.adoc`)

[[feature-machines]]
= Multiple Machines

[WARNING]
====
This feature is deprecated as of Cockpit 322.
====

Cockpit can connect to multiple machines from a single Cockpit session.
These are listed in the host switcher.

These additional machines are accessed via SSH from the machine that the
first machine connected to, and are xref:authentication.adoc[authenticated]
with the logged in user's password and/or SSH keys.

SSH host keys are stored in `+/etc/ssh/ssh_known_hosts+`.

The machine data is stored in `+/etc/cockpit/machines.d/*.json+`, or
below `+$XDG_CONFIG_DIRS+` if set (see
link:./cockpit.conf.5.html[cockpit.conf]). Settings in lexicographically
later files amend or override settings in earlier ones. Cockpit itself
writes into `+99-webui.json+`; packages or admins who want to
pre-configure machines should ship files like `+05-mymachine.json+` so
that changes from the web interface override the pre-configured files.

Each JSON file contains an object that maps machine IDs to objects that
define the properties of that machine. The ID can be a human readable
name or an IP address or any other unique value, and is shown in the web
interface until connecting to it the first time, at which point the web
interface will show the machine's host name.

The following properties are recognized:

`+"address"+`::
  _(string, mandatory)_ IP address or DNS name of the machine
`+"visible"+`::
  _(boolean, optional)_ If `+true+`, the machine will be displayed and
  available for managing with Cockpit. If `+false+` (the default), it
  will not be displayed, but still taken into account for type-ahead
  search when adding new machines in the web interface.
`+"user"+`::
  _(string, optional)_ User name on the remote machine. When not given,
  Cockpit will default to the user name that was being used to log into
  Cockpit itself.
`+"port"+`::
  _(integer, optional)_ ssh port of the remote machine. When not given,
  the default port 22 is used.
`+"color"+`::
  _(string, optional)_ Color to assign to the machine label in the web
  interface. This can be either given as
  `+rgb(r_value, g_value, b_value)+` with each value being an integer
  between 0 and 255, or as a color name like `+yellow+`. When not given,
  Cockpit will assign an unused color automatically.

Example:

....
{
    "web server": {
        "address": "192.168.2.4",
        "visible": true,
        "color": "rgb(100, 200, 0)",
        "user": "admin"
    },
    "192.168.2.1": {
        "address": "192.168.2.1",
        "port": 2222,
        "visible": true,
        "color": "green"
    }
}
....

---

### Doc Modules Guide Pages Feature Networkmanager (`doc/modules/guide/pages/feature-networkmanager.adoc`)

[[feature-networkmanager]]
= NetworkManager

If available on the system, Cockpit uses
https://wiki.gnome.org/Projects/NetworkManager[NetworkManager] and the
DBus APIs it provides to interact with the system's network
configuration.

For non root users, NetworkManager controls access to its APIs via
xref:privileges.adoc[Policy Kit] and a user logged into Cockpit will have
the same permissions as they do from the command line.

To perform similar tasks from the command line, use the
https://fedoraproject.org/wiki/Networking/CLI[nmcli] command:

....
$ nmcli general status
STATE      CONNECTIVITY  WIFI-HW  WIFI     WWAN-HW  WWAN
connected  full          enabled  enabled  enabled  enabled
....

Devices marked as "not managed" with the `+NM_CONTROLLED=no+` setting
will not be displayed in the interface.

---

### Doc Modules Guide Pages Feature Packagekit (`doc/modules/guide/pages/feature-packagekit.adoc`)

[[feature-packagekit]]
= Package Updates

Cockpit uses the
https://www.freedesktop.org/software/PackageKit/gtk-doc/api-reference.html[PackageKit]
D-Bus API to get information about available package updates and to
apply them, in an Operating System independent manner.

To perform similar tasks from the command line, use the
https://www.freedesktop.org/software/PackageKit/pk-using.html[pkcon]
command:

....
$ pkcon refresh

$ pkcon get-updates
Available  sudo-1.8.20p2-1.fc26.x86_64 (updates-testing)
    Allows restricted root access for specified users
Available  vim-filesystem-2:8.0.617-1.fc26.x86_64 (updates-testing)
    VIM filesystem layout
Available  vim-minimal-2:8.0.617-1.fc26.x86_64 (updates-testing)
    A minimal version of the VIM editor

$ pkcon get-update-detail sudo
Details about the update:6.x86_64 [fedora]
 Package: sudo-1.8.20p2-1.fc26.x86_64
 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1452941
 Update text: - update to 1.8.20p2
    - added sudo package to dnf/yum protected packages

$ pkcon update
The following packages have to be updated:
 sudo-1.8.20p2-1.fc26.x86_64    Allows restricted root access for specified users
 vim-filesystem-2:8.0.617-1.fc26.x86_64 VIM filesystem layout
 vim-minimal-2:8.0.617-1.fc26.x86_64    A minimal version of the VIM editor
Proceed with changes? [N/y] y
[...]
....

Of course you can also use your Operating System specific commands for
that, such as `+dnf updateinfo info+` on Fedora or `+sudo apt upgrade+`
on Debian.

---

### Doc Modules Guide Pages Feature Pcp (`doc/modules/guide/pages/feature-pcp.adoc`)

[[feature-pcp]]
= PCP Metrics

If available, Cockpit uses the https://pcp.io/[Performance Co-Pilot]
framework to gather metrics data about the system. This data is used to
display the history graphs on the "Metrics and history" page. Cockpit
can use the PCP logging feature to display archived data about the
system from a different point in time. If PCP is not available, then
Cockpit gathers the metrics data itself, but archival features are not
available.

Whether or not metrics are archived depends on whether the
https://linux.die.net/man/1/pmlogger[`+pmlogger.service+`] systemd unit
is running or not. The "Enable PCP metrics collector" button on the
Metrics page will enable and start this service.

To see similar metrics data from the command line, you can use tools
like https://linux.die.net/man/1/pmstat[`+pmstat+`] or
https://linux.die.net/man/1/pminfo[`+pminfo+`]:

....
$ pmstat
@ Sat Sep 26 15:30:10 2015
 loadavg                      memory      swap        io    system         cpu
   1 min   swpd   free   buff  cache   pi   po   bi   bo   in   cs  us  sy  id
    4.19      0 20710m 605148  6450m    0    0    0 2548 5688  14K  19   3  76
...
....

These metrics can also be exposed to other machines on a TCP port with
https://linux.die.net/man/1/pmproxy[pmproxy] and
https://redis.io/[Redis] or https://valkey.io/[Valkey]:

....
systemctl enable --now redis pmproxy
# if you use firewalld, open port 44322:
firewall-cmd --permanent --add-service pmproxy
firewall-cmd --reload
....

This allows you to gather and visualize PCP metrics from multiple
machines with https://grafana.com/[Grafana] and the
https://grafana-pcp.readthedocs.io[PCP Grafana plugin].

---

### Doc Modules Guide Pages Feature Realmd (`doc/modules/guide/pages/feature-realmd.adoc`)

[[feature-realmd]]
= realmd

If available on the system, Cockpit uses
https://www.freedesktop.org/software/realmd/[realmd] and the DBus APIs
it provides to configure the system's Active Directory or IPA domain
membership.

Not all systems can join all kinds of domains. This depends on the
availability of the necessary client software.

For non root users, realmd controls access to its APIs via
xref:privileges.adoc[Policy Kit] and a user logged into Cockpit will have
the same permissions as they do from the command line.

To perform similar tasks from the command line, use the
https://www.freedesktop.org/software/realmd/docs/realm.html[realm]
command:

....
$ realm join example.com
Password for Administrator:
....

http://www.freedesktop.org/software/realmd/[realmd] sets up
domain-qualified user names by default, i. e. login user names look like
"`+user@example.com+`". For using unqualified names (just "`+user+`"),
set the `+fully-qualified-names+` option in
https://www.freedesktop.org/software/realmd/docs/realmd-conf.html[/etc/realmd.conf]
before joining a domain.

Cockpit requests an SSL certificate from the IPA server for
`+cockpit-ws+` with the
https://www.freeipa.org/page/Certmonger[ipa-getcert] command.

---

### Doc Modules Guide Pages Feature Selinux (`doc/modules/guide/pages/feature-selinux.adoc`)

[[feature-selinux]]
= SELinux Policy

If present on the system Cockpit can set the SELinux mode to enforcing
or permissive. It can also use `+setroubleshootd+` to show audit issues
and apply suggested fixes.

To perform similar tasks from the command line use the `+setenforce+`
and `+sealert+` tools.

To clear out all the information that `+setroubleshootd+` tracks, you
can use a commands like:

....
$ sudo killall setroubleshootd
$ sudo rm -rf /var/lib/setroubleshoot/*
....

---

### Doc Modules Guide Pages Feature Sosreport (`doc/modules/guide/pages/feature-sosreport.adoc`)

[[feature-sosreport]]
= SOS Report

If present on the system Cockpit can use `+sosreport+` to collect system
configuration and diagnostic information.

To perform similar tasks from the command line, use the `+sosreport+`
command.

---

### Doc Modules Guide Pages Feature Storaged (`doc/modules/guide/pages/feature-storaged.adoc`)

[[feature-storaged]]
= storaged

If available on the system, Cockpit uses
https://github.com/storaged-project/storaged/[`+storaged+`] to configure
and monitor storage, disks, mounts etc. on the system. This
functionality is present in the Cockpit _storaged_ package.

The `+storaged+` project is originally based on a project called
https://www.freedesktop.org/wiki/Software/udisks/[`+udisks+`] and added
support for many more features such as
https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)[LVM],
https://en.wikipedia.org/wiki/ISCSI[iSCSI],
https://en.wikipedia.org/wiki/Linux_DM_Multipath[Multipath], and
https://btrfs.wiki.kernel.org/index.php/Main_Page[BTRFS]. The same tools
and backwards compatible API are available between `+storaged+` and
`+udisks+` the projects. Cockpit can use `+udisks+` but disables many of
it's storage related features, including updating `+/etc/fstab+` and
`+/etc/crypttab+` for stability reasons.

For non root users, storaged controls access to its APIs via
xref:privileges.adoc[Policy Kit] and a user logged into Cockpit will have
the same permissions as they do from the command line.

To perform similar tasks from the command line, use the `+storagedctl+`
command:

....
$ udisksctl dump
...
....

To perform LVM tasks, you may use the various LVM commands, such as
`+vgcreate+`, `+lvresize+` and so on. Cockpit will detect such changes
made at the command line.

Cockpit recognizes devices with multiple paths and can start the
`+multipathd+` service in case it is not running. On the command line,
you can control multipath features with the `+mpathconf+`,
`+multipathd+`, and `+multipath+` commands.

To manage iSCSI initiators from the command line, you can use
`+iscsiadm+` and related tools.

---

### Doc Modules Guide Pages Feature Systemd (`doc/modules/guide/pages/feature-systemd.adoc`)

[[feature-systemd]]
= systemd

Cockpit uses https://www.freedesktop.org/wiki/Software/systemd/[systemd]
and the DBus APIs it provides to configure and monitor core aspects of
the system. Use of alternate system APIs are not currently implemented.

For non root users, systemd controls access to its APIs via
xref:privileges.adoc[Policy Kit] and a user logged into Cockpit will have
the same permissions as they do from the command line.

Cockpit retrieves information about the host and changes the hostname
via the `+hostnamed+` daemon. To perform similar tasks from the command
line use the
https://www.freedesktop.org/software/systemd/man/hostnamectl.html[`+hostnamectl+`]
command:

....
$ hostnamectl
   Static hostname: pink.example.com
   Pretty hostname: Pink
         Icon name: computer-desktop
           Chassis: desktop
        Machine ID: ef00b79be229463cbb844c3e715de96c
           Boot ID: 934983d64d34465cb5a8383b5a89ad8c
  Operating System: Fedora 22 (Twenty Two)
       CPE OS Name: cpe:/o:fedoraproject:fedora:22
            Kernel: Linux 4.0.4-301.fc22.x86_64
      Architecture: x86-64
....

Cockpit configures the system time and time zone via the `+timedated+`
daemon. To perform similar tasks from the command line use the
https://www.freedesktop.org/software/systemd/man/timedatectl.html[`+timedatectl+`]
command:

....
$ timedatectl list-timezones
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
...
....

Cockpit can manage the list of NTP servers used by `+systemd-timesyncd+`
by putting its own file into `+/etc/systemd/timesyncd.conf.d/+`. Note
that `+systemd-timesyncd+` is not always enabled, depending on the
configuration of the machine. In that case, Cockpit disabled the UI for
managing the list of NTP servers. In some cases use of `+ntpd+` can
cause the `+timedated+` daemon to behave inconsistently with regards to
time synchronization.

Cockpit reboots or powers down the machine by using the
https://www.freedesktop.org/software/systemd/man/shutdown.html[`+shutdown+`]
command. To perform similar tasks from the command line, run it
directly:

....
$ sudo shutdown +15
Shutdown scheduled for Sa 2015-09-26 15:49:40 CEST, use 'shutdown -c' to cancel.
....

Cockpit manages system services and sockets via systemd. To perform
similar tasks from the command line use the
https://www.freedesktop.org/software/systemd/man/systemctl.html[`+systemctl+`]
command:

....
$ systemctl status cockpit
● cockpit.service - Cockpit Web Service
   Loaded: loaded (/usr/lib/systemd/system/cockpit.service; static; vendor preset: disabled)
  Drop-In: /etc/systemd/system/cockpit.service.d
           └─debug.conf
   Active: active (running) since Sa 2015-09-26 13:28:02 CEST; 2h 7min ago
     Docs: man:cockpit-ws(8)
 Main PID: 6957 (cockpit-ws)
   Memory: 1.8M
   CGroup: /system.slice/cockpit.service
           ├─ 6957 /usr/libexec/cockpit-ws
           └─29598 /usr/bin/ssh-agent
....

In order to customize who can perform various actions in system,
xref:privileges.adoc#privileges-polkit[create polkit rules] with the following actions
and details:

`+org.freedesktop.systemd1.manage-units+`::
  Permission to manage system services or other units. Details
  available: `+unit+`, `+verb+`
`+org.freedesktop.systemd1.manage-unit-files+`::
  Permission to manage system services or other unit files.
`+org.freedesktop.systemd1.reload-daemon+`::
  Permission to reload the systemd state.

For example, placing the following polkit rule to
`+/etc/polkit-1/rules.d/10-http.rule+` allows all users in the
`+operators+` group start, stop, and restart the Apache HTTP service:

....
polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units") {
        if (subject.isInGroup("operators") && action.lookup("unit") == "httpd.service") {
            var verb = action.lookup("verb");
            if (verb == "start" || verb == "stop" || verb == "restart") {
                return polkit.Result.YES;
            }
        }
    }
});
....

---

### Doc Modules Guide Pages Feature Terminal (`doc/modules/guide/pages/feature-terminal.adoc`)

[[feature-terminal]]
= Terminal

Cockpit provides a standard shell in a terminal. This shell and the
processes running in it have the xref:privileges.adoc[same privileges] as if
the user had logged in via SSH.

---

### Doc Modules Guide Pages Feature Tuned (`doc/modules/guide/pages/feature-tuned.adoc`)

[[feature-tuned]]
= Tuned Profiles

If present on the system Cockpit can use Tuned and the DBUS API it
provides to set system performance profiles. To perform similar tasks
from the command line, use the `+tuned-adm+` command.

---

### Doc Modules Guide Pages Feature Users (`doc/modules/guide/pages/feature-users.adoc`)

[[feature-users]]
= User Tools

Cockpit uses the usual tools to create and modify local user accounts.
Examples are `+useradd+`, `+usermod+` and `+passwd+`. These same tools
are available for use on the command line.

---

### Doc Modules Guide Pages Features (`doc/modules/guide/pages/features.adoc`)

[[features]]
= Feature Internals
:leveloffset: +1

include::feature-systemd.adoc[]

include::feature-journal.adoc[]

include::feature-networkmanager.adoc[]

include::feature-firewall.adoc[]

include::feature-storaged.adoc[]

include::feature-users.adoc[]

include::feature-realmd.adoc[]

include::feature-terminal.adoc[]

include::feature-pcp.adoc[]

include::feature-machines.adoc[]

include::feature-selinux.adoc[]

include::feature-tuned.adoc[]

include::feature-sosreport.adoc[]

include::feature-packagekit.adoc[]

:leveloffset: -1

---

### Doc Modules Guide Pages Https (`doc/modules/guide/pages/https.adoc`)

[[https]]
= SSL/TLS Usage

Cockpit usually requires that web browsers communicate with it using
HTTPS, for security reasons.

[[https-required]]
== HTTPS Requirement

Cockpit listens for both HTTP and HTTPS connections on the same port, by
default 9090. If an HTTP connection is made, Cockpit will redirect that
connection to HTTPS. There are some exceptions:

* If an HTTP connection comes from `+localhost+` (`+127.0.0.1+` or
`+::1+`, then Cockpit will allow communication without redirecting to
HTTPS.
* Certain URLs, like `+/ping+` are not required to use HTTPS.

This behavior can be overridden by setting the `+AllowUnencrypted+`
option in `+cockpit.conf+`.

[[https-certificates]]
== Certificates

Cockpit will load a certificate from the `+/etc/cockpit/ws-certs.d+`,
directory, or below `+$XDG_CONFIG_DIRS+` if set (see
link:./cockpit.conf.5.html[cockpit.conf]). It will use the last file
with a `+.cert+` or `+.crt+` extension in alphabetical order. The file
should contain one or more OpenSSL style `+BEGIN CERTIFICATE+` blocks
for the server certificate and the intermediate certificate authorities.

The private key must be contained in a separate file with the same name
as the certificate, but with a `+.key+` suffix instead. The key must not
be encrypted.

If no certificate is found, a self-signed certificate is created and
stored in the `+0-self-signed.cert+` file. On some platforms, Cockpit
will also generate a ca.crt in that directory, which may be safely
imported into client browsers.

Cockpit will read the files as root, so they can have tight permissions.

To check which certificate `+cockpit-ws+` will use run the following
command.

....
$ sudo /usr/libexec/cockpit-certificate-ensure --check
....

Or, on Debian-based systems:

....
$ sudo /usr/lib/cockpit/cockpit-certificate-ensure --check
....

If using `+certmonger+` to manage certificates, following command can be
used to automatically prepare a certificate/key file pair:

....
getcert request -f /etc/cockpit/ws-certs.d/50-certmonger.cert \
                -k /etc/cockpit/ws-certs.d/50-certmonger.key \
                -D myhostname.example.com \
                [--ca=...]
....

This will not work on Red Hat Enterprise Linux 8 by default. Adjust the
SELinux type of the certificate directory to `+cert_t+` to allow
certmonger to write its certificates there:

....
semanage fcontext -a -t cert_t '/etc/cockpit/ws-certs\.d(/.*)?'
restorecon -v /etc/cockpit/ws-certs.d
....

---

### Doc Modules Guide Pages Index (`doc/modules/guide/pages/index.adoc`)

[[guide]]
= Deployment Guide

Documentation for Cockpit {cockpit-version}.

Latest version https://docs.cockpit-project.org/[available here].

---

### Doc Modules Guide Pages Listen (`doc/modules/guide/pages/listen.adoc`)

[[listen]]
= TCP Port and Address

Cockpit's `+cockpit-ws+` component is configured by default to accept
connections on port `+9090+`. This is the port that is documented for a
"Web-based System Manager" to listen on. It is also relatively
memorable.

However there are many reasons you may wish to change the default port.
For example other software may use port `+9090+` or you may wish to
setup Cockpit to listen on `+443+` instead. It is also possible to have
Cockpit only listen on one specific IP address.

Note that it is only required to have Cockpit listening on a TCP port on
the server that you access with your web browser. If you add multiple
servers with host switcher, Cockpit will connect to those servers via
`+ssh+`.

The systems that Cockpit runs on are typically locked down with
firewalls, SELinux, so changing the default port is not as easy as
editing a configuration file.

[[listen-systemd]]
== Cockpit systemd Socket

On servers with
https://www.freedesktop.org/wiki/Software/systemd/[`+systemd+`] Cockpit
starts on demand via socket activation. To change its port and/or
address you should place the following content in the
`+/etc/systemd/system/cockpit.socket.d/listen.conf+` file. Create the
file and directories in that path which not already exist. The
`+ListenStream+` option specifies the desired address and TCP port.

....
[Socket]
ListenStream=
ListenStream=443
....

....
[Socket]
ListenStream=
ListenStream=7777
ListenStream=192.168.1.1:443
FreeBind=yes
....

NOTE: The first line with an empty value is intentional. `+systemd+`
allows multiple `+Listen+` directives to be declared in a single socket
unit; an empty value in a drop-in file resets the list and thus disables
the default port 9090 from the original unit.

The `+FreeBind+` option is highly recommended when defining specific IP
addresses. See the
https://www.freedesktop.org/software/systemd/man/systemd.socket.html[`+systemd.socket+`
manpage] for details.

In order for the changes to take effect, run the following commands:

....
$ sudo systemctl daemon-reload
$ sudo systemctl restart cockpit.socket
....

[[listen-selinux]]
== SELinux Port

If https://selinuxproject.org/page/Main_Page[SELinux] is protecting your
server, then you will need to tell it to allow Cockpit to listen on the
new port. Run the following command to do so. The last argument
specifies the desired TCP port.

....
$ sudo semanage port -a -t websm_port_t -p tcp 9999
....

If the port is already defined by some other part of the SELinux policy,
then you will need to use the `+-m+` argument to modify the definition.
That's the case with the `+443+` SSL port, which is typically defined as
an `+http_port_t+` port.

....
$ sudo semanage port -m -t websm_port_t -p tcp 443
....

The changes should take effect immediately.

[[listen-firewalld]]
== Firewalld Port

If https://fedoraproject.org/wiki/FirewallD[Firewalld] is configured as
your firewall, then you will need to tell it to allow Cockpit to receive
connections on the new port. Run the following commands to do so. The
last options specify the desired TCP port.

....
$ sudo firewall-cmd [--zone=ZONE] --add-port=443/tcp
$ sudo firewall-cmd --permanent [--zone=ZONE] --add-port=443/tcp
....

---

### Doc Modules Guide Pages Multi Host (`doc/modules/guide/pages/multi-host.adoc`)

[[multi-host]]
= Managing multiple hosts at the same time

[WARNING]
====
This feature is deprecated as of Cockpit 322.
====

Cockpit allows you to access multiple hosts in a single session, by
establishing SSH connections to other hosts. This is quite similar to
logging into these other hosts using the `+ssh+` command on the command
line, with one very important difference:

Code from the local host and all the remote hosts run at the same time,
in the same browser context. They are not isolated from each other in
the browser. All code effectively has the same privileges as the primary
session on the local host.

Thus, _you should only only connect to remote hosts that you trust_. You
must be sure that none of the hosts that you connect to will cause
Cockpit to load malicious JavaScript code into your browser.

Therefore, Cockpit will warn you before connecting to more than one
host. It is also possible to disable multiple hosts entirely, and some
operating systems do this already by default.

You can prevent loading of JavaScript, HTML, etc from more than one host
by adding this to `+cockpit.conf+`:

....
    [WebService]
    AllowMultiHost=false

....

When you allow multiple hosts in a single Cockpit session by setting
`+AllowMultiHost+` to true, then the user will be warned once per
session, before connecting to the second host. If that is still too
much, you can switch the warning off completely by adding the following
to `+cockpit.conf+`:

....
    [Session]
    WarnBeforeConnecting=false
....

---

### Doc Modules Guide Pages Packages (`doc/modules/guide/pages/packages.adoc`)

[[packages]]
= Cockpit Packages

Cockpit is separated into various packages, each of which brings
specific features and/or code.

[WARNING]
====
In addition, any APIs or behavior not explicitly documented here is an
internal API and can be changed at any time.
====

[[package-layout]]
== Layout of Package Files

A package consists of one or more files placed in a directory or its
subdirectories. It must have a `+manifest.json+` file and follow certain
naming conventions.

The name of a package is the name of the directory.

The name of the package must be ASCII alphanumeric, and may contain an
underscore. Names of directories and files in the package must consist
of ASCII alphanumeric along with dash, underscore, dot, and comma. No
spaces are allowed.

Cockpit uses the data directories from the
https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html[XDG
Base 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 environment
variables are not set, defaults are used, according to the spec. If
cockpit 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 the
location where packages are loaded by Cockpit. If Cockpit finds a
package with the same name, in multiple data directories, then the first
one 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 for
cockpit 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 the
web browser. Other packages are cached aggressively, and are accessed
using a checksum of the files in the packages and their names.

You can use the following command to list the packages installed on a
server. You'll note that it's output may change when you run the command
as different users, if there are packages installed in the user's home
directory.

....
$ 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.json
        file.html
        some.js
....

Place or symlink packages in your `+~/.local/share/cockpit+` directory
(or appropriate `+$XDG_DATA_HOME+` location) that you would like to
modify and develop. System installed packages should not change while
Cockpit is running.

== Package Manifest

Each package has a `+manifest.json+` file. It is a JSON object. The
following fields may be present in the manifest:

content-security-policy::
  By default Cockpit serves packages using a strict
  https://en.wikipedia.org/wiki/Content_Security_Policy[Content Security
  Policy], which among other things does not allow inline styles or
  scripts. This can be overridden on a per-package basis, with this
  setting.
  +
  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 be
  added to the policy from the manifest.
name::
  An optional string that changes the name of the package. Normally
  packages derive their name from the directory that they are located
  in. This field overrides that name.
priority::
  An optional integer that specifies which package is preferred in cases
  where there are conflicts. For example given two packages with the
  same `+name+` a package is chosen based on its priority. A higher integer
  means higher priority.
conditions::
  An optional list of `+{"predicate": "value"}+` objects. Cockpit will
  only consider the package if _all_ conditions are met. This is
  preferable to using `+priority+`. +
   +
  Currently supported predicates are `+path-exists+` and `+path-not-exists+`
  (since Cockpit 286) and `+any+` (since Cockpit 348). `+any+` accepts a
  list of conditions and evaluates to true if _at least one_ of the nested
  conditions is met. Unknown predicates are ignored.

requires::
  An optional JSON object that contains a `+"cockpit"+` string version
  number. The package will only be usable if the Cockpit bridge and
  javascript 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 navigates
  to it for the first time. The files of a preloaded component are
  loaded immediately after the user logs in, and the initialization code
  of the component is invoked.
  +
  The value of this field is an array of strings, where each string is
  one 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 is
  a part of a different module. This is described by JSON object with
  properties `+component+` which takes name of the superordinate
  component and `+docs+` with list of documentation URLs for the given
  page. See below for structure of `+docs+` property.

In addition, the following keys contain information about where
components of the package should appear in Cockpit's user interface.
Each of these keys is optional and contains an object mapping unique
identifiers to menu items, which are described below. (The naming of
these fields doesn't perfectly match the current user interface for
historical reasons.)

dashboard::
  Dashboard items appear in the menu under the section _Apps_.
menu::
  These items appear in the menu under the section _System_. This
  section 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 the
following properties:

label::
  The label for the menu item or tool.
order::
  An optional order integer to place this menu item or tool. Lower
  integers are listed first.
path::
  The relative path to the HTML file within the package that implements
  the menu item or tool.
docs::
  List of documentation URLs for the given page. Each item is an object
  containing `+label+` and `+url+`.
keywords::
  Keywords that describe the page and which are used for searching.
  These keywords should be lowercase. Keywords is a list containing
  keyword items as described below. Page label is prepended as first
  keyword in the first keyword item.

Keyword items are registered using JSON objects that have the following
properties:

matches::
  List of keywords to be matched.
goto::
  Optional path that is used for all keywords in this item. When this
  argument starts with slash, then it is used as pathname, otherwise it
  is 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+` ignoring
  default 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). This
package 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 overrides

To 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 (see
link:./cockpit.conf.5.html[cockpit.conf]). To change it for a particular
user only, put the override into `+~/.config/cockpit+`.

These override the information in the manifest in the simple
https://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_ menu
item and move the _Services_ menu item to the top of the menu.

....
{
  "menu": {
    "logs": null,
    "services": {
      "order": -1
    }
  }
}
....

[[package-links]]
== Package Links and Paths

When referring to files in your package, such as in a hyperlink or a
`+<style>+` tag or `+<script>+` tag, simply use a relative path, and
refer to the files in the same directory. When you need to refer to
files in another package use a relative link.

For example here's how to include the base `+cockpit.js+` script in your
HTML from the `+latest+` package:

....
<script src="../base1/cockpit.js"></script>
....

Do not assume you can link to any file in any other package. Refer to
the xref:development.adoc[list of API packages] for those that are available
for use.

[[package-minified]]
== Content Negotiation

In order to support gzipped and/or minified data, the files in a package
are 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 undefined
preference). If neither exists, then it returns
`+mypackage/test.js.min+` or `+mypackage/test.js.min.gz+` (again in
undefined preference).

When packages are loaded from a system directory, Cockpit optimizes the
file system lookups above, by pre-listing the files. This is one of the
reasons that you should never change packages installed to a system
directory while Cockpit is running.

[[package-api]]
== Using Cockpit API

Cockpit has API available for writing packages. There is no API
available 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 API
explicitly documented should be used.

* xref:development.adoc[API Listing]

To include javascript from the API, simply load it into your HTML using
a script tag. Alternatively you can use an javascript loader.

[[package-bridges]]
== Bridges for specific tasks

On 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 of
a package's `+manifest.json+` file. Building such a bridge is a complex
tasks, and we will skip over that here. However it is useful to adjust
how these additional bridges are called, and so we'll look at how they
are 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 between
packages. The bridges are registered using JSON objects that have the
following properties:

environ::
  Optional, additional environment variables to pass to the bridge
  command.
match::
  The `+"match"+` object describes which channel open command options
  need 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 the
  privileges of a session. A privileged bridge can not have a
  `+"match"+` property.
label::
  Setting this enables selection of privileged bridges in the UI. When
  no privileged bridge has a `+label+`, then Cockpit will start the
  bridge that runs `+sudo+`. This is the case in a default Cockpit
  installation. When at least one privileged bridge has a `+label+` then
  the user can select one of them when escalating privileges. As a
  special case, if only one bridge has a `+label+`, then the step of
  selecting a bridge is omitted in the UI and that one bridge is always
  started.
  +
  Thus, if you add a privileged bridge with a `+label+` in a new
  manifest, Cockpit will use that bridge the next time a user opens the
  "Administrative access" dialog. If you want the user to choose between
  the `+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 then
  channels will be closed with this problem code. Otherwise later
  bridges 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 a
matching open command values. When a value in either the `+spawn+` or
`+environ+` array contains a named variable wrapped in `+${}+`, the
variable will be replaced with the value contained in the matching open
command. Only named variables are supported and name can only contain
letters, 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 existing
process, rather than spawning a new one. However a open command with an
tag 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 you
can escape it by prefixing it with a `+\+`

[[package-replace]]
== Replacing an existing package

If the functionality in a package replaces that of another package then
it can replace that package by claiming the same `+name+` and a higher
`+priority+`.

For example, a package in the `+/usr/share/cockpit/disks+` directory
could replace Cockpit's _storage_ package with a `+manifest.json+` like
this:

....
{
  "version": 0,
  "name": "storage",
  "priority": 10,
  "menu": {
     "index": {
        "label": "Disk Storage",
        "order": 15
     }
  }
}
....

---

### Doc Modules Guide Pages Privileges (`doc/modules/guide/pages/privileges.adoc`)

[[privileges]]
= Privileges and Permissions

When a user is logged into Cockpit, they are logged into a normal
session that has exactly the same privileges as if they logged in via
SSH or on the console.

However, Cockpit will usually try to escalate the privileges of the user
using https://www.freedesktop.org/wiki/Software/polkit/[Policy Kit] or
https://www.sudo.ws/[sudo]. If the user is able to escalate privileges
from the command line by typing in their password again (or without
typing in any password), then Cockpit will be able to escalate the
privileges of the session to "root" immediately upon login.

The user can change the privileges of a session from within that
session, via the "Administrative access" indicator in the top bar. From
that indicator, the user can drop "root" privileges and regain them. On
the next login, Cockpit will give the session the same privileges.

Usually a user needs to be in the `+wheel+` Unix user group for the user
to be able to escalate privileges in this way. However both Policy Kit
and sudo may be configured to use other criteria.

[[privileges-polkit]]
== Customizing Polkit Privileges

Services like
https://www.freedesktop.org/wiki/Software/systemd/[systemd] and
https://wiki.gnome.org/Projects/NetworkManager[NetworkManager] use
https://www.freedesktop.org/wiki/Software/polkit/[Polkit] to validate
and escalate privileges. It is possible to customize these rules with
files in `+/etc/polkit-1/rules.d+`.

Polkit rules files are
https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html[javascript
with specific methods and objects]. For example, placing the following
polkit rule to `+/etc/polkit-1/rules.d/10-operators.rule+` allows all
users in the `+operators+` group to start, stop, restart and otherwise
manage systemd services:

....
polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units") {
        if (subject.isInGroup("operators")) {
            return polkit.Result.YES;
        }
    }
});
....

In order to allow a certain group to perform any administrative action
you could add a rule like this:

....
polkit.addAdminRule(function(action, subject) {
    return ["unix-group:operators"];
});
....

---

### Doc Modules Guide Pages Sso (`doc/modules/guide/pages/sso.adoc`)

[[sso]]
= Single Sign On

Cockpit can use Kerberos for Single Sign On authentication, where users
are automatically authenticated if they have a valid Kerberos ticket.

[[sso-server]]
== Server Requirements

To authenticate users, the server that Cockpit is running on must be
joined to a domain. This can usually be accomplished using the
https://freedesktop.org/software/realmd/docs/realm.html[`+realm join example.com+`]
command.

The domain must be resolvable by DNS. For instance, the SRV records of
the kerberos server should be resolvable:

....
$ host -t SRV  _kerberos._udp.example.com
_kerberos._udp.example.com has SRV record 0 100 88 dc.example.com
....

The server running Cockpit should have a fully qualified name that ends
with the domain name.

There must be a valid Kerberos host key for the server in the
`+/etc/krb5.keytab+` file. Alternatively, if you would like to use a
different keytab, you can do so by placing it in
`+/etc/cockpit/krb5.keytab+`, or below `+$XDG_CONFIG_DIRS+` if set (see
link:./cockpit.conf.5.html[cockpit.conf]). It may be necessary to create
a kerberos service principal and update the keytab if it is not present.
Depending on your domain type different service names are required:

Active Directory::
  `+HOST/server.example.com@EXAMPLE.COM+`
IPA and MIT::
  `+HTTP/server.example.com@EXAMPLE.COM+`

When joining an IPA domain with Cockpit and the `+ipa+` command line
tool is available, both the service principal name and a
`+/etc/cockpit/krb5.keytab+` get created automatically, so that Kerberos
based single sign on into Cockpit works out of the box. If you want/need
to do this by hand or in a script, first create or modify the `+HTTP/+`
service principal:

....
$ sudo ipa service-add --ok-as-delegate=true --ok-to-auth-as-delegate=true \
    HTTP/server.example.com@EXAMPLE.COM
# or, if it already exists, just enable delegation:
$ sudo ipa service-mod --ok-as-delegate=true --ok-to-auth-as-delegate=true \
    HTTP/server.example.com@EXAMPLE.COM
....

Then generate a key for that principal:

....
$ sudo ipa-getkeytab -p HTTP/server.example.com@EXAMPLE.COM -k /etc/cockpit/krb5.keytab
....

The following command can be used to list the
`+/etc/cockpit/krb5.keytab+`:

....
$ sudo klist -k /etc/cockpit/krb5.keytab
....

Lastly accounts from the domain must be resolvable to unix accounts on
the server running Cockpit. For example:

....
$ getent passwd user@example.com
user@example.com:*:381001109:381000513:User Name:/home/user:/bin/sh
....

If you wish to delegate your kerberos credentials to Cockpit, and allow
Cockpit to then connect to other machines using those credentials, you
should enable delegation for the hosts running Cockpit, and in some
cases the `+HTTP+` service as well. When joining an IPA domain, this is
enabled by default.

Domain admins (usually the `+admins@example.com+` group) should normally
also be able to administer any joined machine. Enable sudo access for
that group with the following command on the IPA server, for version
4.7.1 and later:

....
ipa-advise enable-admins-sudo | sh -ex
....

On earlier FreeIPA versions, run these commands instead, as a domain
admin on any joined machine:

....
ipa sudorule-add --hostcat=all --cmdcat=all All
ipa sudorule-add-user --groups=admins All
....

Note that this does not change security properties; domain admins can
give this privilege to themselves, so it is safe to enable by default.

[[sso-client]]
== Client Requirements

The client side, where your web browser is running, should have a valid
kerberos ticket in the current user session. A command like this will
get one:

....
$ kinit user@EXAMPLE.COM
Password for user@EXAMPLE.COM:
....

In addition your browser must be usually be configured to allow kerberos
authentication for the domain.

Mozilla Firefox::
  Go to `+about:config+` and set the
  `+network.negotiate-auth.trusted-uris+` setting to your domain name
  preceded by a dot, ie: `+.example.com+`
Google Chrome::
  On Linux: create the file
  `+/etc/opt/chrome/policies/managed/example-com.json+` with the
  contents:

{empty}::
....
{
  "AuthServerWhitelist": "*example.com"
}
....

{empty}::
  and restart the browser. On other platforms, exit your browser
  completely, and start it with a command line like this:
  `+google-chrome --auth-server-whitelist=*example.com+`

Use a fully qualified server name (with the domain name at the end) to
access Cockpit in your web browser.

If you wish to connect from one server to another in Cockpit using
kerberos SSO, then you have to explicitly enable all sorts of things.
For starters, make sure that delegated credentials are allowed by your
domain (see above). Next when requesting your kerberos ticket make sure
that forwardable tickets are requested:

....
$ kinit -f user@EXAMPLE.COM
Password for user@EXAMPLE.COM:
....

Make sure that the forwardable flag `+F+` is present in your ticket:

....
$ klist -f
Ticket cache: KEYRING:persistent:1000:1000
Default principal: user@EXAMPLE.COM

Valid starting       Expires              Service principal
18.03.2017 05:39:23  19.03.2017 05:39:20  krbtgt/EXAMPLE.COM@EXAMPLE.COM
    Flags: FIA
....

Lastly configure your browser to allow delegated, forwardable kerberos
credentials to be sent to Cockpit:

Mozilla Firefox::
  Go to `+about:config+` and set the
  `+network.negotiate-auth.delegation-uris+` setting to your domain name
  preceded by a dot, ie: `+.example.com+`
Google Chrome::
  On Linux: create the file
  `+/etc/opt/chrome/policies/managed/example-com.json+` with the
  contents:

{empty}::
....
{
  "AuthServerWhitelist": "*example.com",
  "AuthNegotiateDelegateWhitelist": "*example.com"
}
....

{empty}::
  and restart the browser. On other platforms, exit your browser
  completely, and start it with a command line like this:
  `+google-chrome --auth-server-whitelist=*example.com --auth-negotiate-delegate-whitelist=*example.com+`

---

### Doc Modules Guide Pages Startup (`doc/modules/guide/pages/startup.adoc`)

[[startup]]
= Start up

Cockpit's `+cockpit-ws+` component is what the browser connects to and
it typically starts on demand via
https://www.freedesktop.org/wiki/Software/systemd/[`+systemd+`] socket
activation.

The actual `+cockpit.service+` and `+cockpit-ws+` process will start on
demand when a browser accesses the `+cockpit.socket+`,
xref:listen.adoc[usually on port 9090]. Once a user logs in then a
`+cockpit-bridge+` process will be started in a Linux user login
session.

Only systems that you connect to with your browser need to have the
`+cockpit.socket+` enabled. For systems that you add through host
switcher the bridge is started via SSH on demand.

[[startup-shutdown]]
== Process exit

The `+cockpit-bridge+` process will exit when the user logs out. In
addition, after 10 minutes of inactivity, the `+cockpit-ws+` process
will exit on its own. The browser will automatically disconnect if it
fails to hear from the `+cockpit-ws+` process for 30 seconds.

[[startup-boot]]
== Boot start up

To make Cockpit available by default after system boot the
`+cockpit.socket+` needs to be enabled:

....
$ sudo systemctl enable cockpit.socket
....

If you wish to not have Cockpit available by default via a browser, then
the `+cockpit.socket+` should be disabled:

....
$ sudo systemctl disable cockpit.socket
....

---

### Doc Modules Guide Pages Urls (`doc/modules/guide/pages/urls.adoc`)

[[urls]]
= Cockpit URLs

Cockpit URLs follow a specific structure, related to the components they
are loading. Various components are loaded in `+<iframe>+` tags. The
URLs for these components are described first. Further down below you
can find information about the top level bookmarkable Cockpit address
URLs.

[[urls-components]]
== Component URLs

Cockpit components are HTML documents. They are organized into
xref:packages.adoc[packages]. Each package contains information about which
HTML components are available in that package. Components should always
use relative URLs to access resources, such as images, scripts or CSS
files, even if they refer to a resource in another package.

The following are valid component URLs, each bit will be discussed
below:

....
/cockpit/@localhost/package/component.html#/hash
/cockpit/$checksum/package/component.html#/hash
/cockpit/@server.example.com/package/component.html#/hash
/cockpit+embedder/@localhost/package/component.html#/hash
....

All resource URLs are under the `+/cockpit+` namespace. In cases where a
Cockpit component is being xref:embedding.adoc[embedded] the `+/cockpit+`
may be followed by a plus sign and another `+embedder+` specific
identifier.

What follows is either a `+@host+` or `+$checksum+` which tells cockpit
where to xref:packages.adoc[find the package]. Checksums are used when more
than one host has identical packages and the resources can be cached.

The `+package+` name is next, followed by the `+component+` HTML path
inside that package. And lastly a hash allows for navigation within a
single component. The hash should follow a URL path and/or query string
form.

[WARNING]
====
Never assume that the `+@host+` or `+$checksum+` portion is predictable.
Only refer to resources in packages on the same host.
====

[[urls-visible]]
== Visible URLs

The above Component URLs are usually not visible to the user. Instead
the Cockpit Web Service wraps the components in a shell which allows
navigation, and provides bookmarkable clean URLs to the component. These
URLs do not affect xref:embedding.adoc[embedders] or
xref:packages.adoc[components] directly.

If no path is present then the Cockpit will redirect to the default page
for the server.

If the first segment of the path begins with an `+@+` sign, then the
component is being shown from a non-local host.

The next segment of the path, (or first if the component is being shown
on the local host) is the xref:packages.adoc[package name]. The remainder of
the path is a component file in the package. If no further path segments
are present, a default `+index.html+` component in the package is
loaded. An extension of `+.html+` is automatically appended.

The hash portion of the path is automatically transferred to the
component as the hash of its link:#urls-components[resource URL].

---

### Doc Modules Man Pages Cockpit Bridge.1 (`doc/modules/man/pages/cockpit-bridge.1.adoc`)

= cockpit-bridge(1)

== Name

cockpit-bridge - Cockpit Host Bridge

== Synopsis

*cockpit-bridge* [*--help*] [*--packages*]

== Description

The *cockpit-bridge* program is used by Cockpit to relay messages and
commands from the Web front end to the server. Among other things it
relays DBus, and spawns processes on behalf of the Web user interface.

This program is not routinely run by users or administrators. It is in
the *$PATH* so that Cockpit can find it when connecting between hosts.
However there are some diagnostics available when running from the
command line.

== Options

*--help*::
  Show help options.
*--interact*=_boundary_::
  Interact with the raw cockpit1 protocol. Useful for debugging and
  testing. Specify a _boundary_ which should be on an empty line
  between messages.
*--packages*::
  List all available Cockpit packages and exit. Note this includes
  packages available to the user running this command.
*--version*::
  Show Cockpit version information.

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

== See also

man:cockpit-ws[8]

---

### Doc Modules Man Pages Cockpit Desktop.1 (`doc/modules/man/pages/cockpit-desktop.1.adoc`)

= cockpit-desktop(1)

== Name

cockpit-desktop - Cockpit Desktop integration

== Synopsis

*cockpit-desktop* _URLPATH_ [_SSH_HOST_]

== Description

The *cockpit-desktop* program provides secure access to Cockpit pages
in an already running desktop session. It starts a web server
(*cockpit-ws*) and a web browser in an isolated network namespace, and
a man:cockpit-bridge[1] in the running user session.

This avoids having to log into Cockpit, and having to enable
*cockpit.socket* system-wide. The network isolation ensures that no
other user, and not even other processes in the user's session, can
access this local web server.

*URLPATH* is the Cockpit page to open, i. e. the path component of
Cockpit URLs. It is highly recommended to only open a
https://docs.cockpit-project.org/cockpit-guide/latest/guide/embedding.html[particular page frame], not the entire Cockpit navigation and menu. For example, the
path `+/cockpit/@localhost/storage/index.html+` will open the Storage
page. It is also possible to give abbreviated forms of urls, such as
"`+/storage+`" or "`+/network/firewall+`".

_SSH_HOST_ is an optional SSH remote host specification (_hostname_
or _username@hostname_). If given, *cockpit-bridge* will be started
on the remote host through *ssh(1)* instead, i. e. the Cockpit web
browser will show that remote host. Note that this is more of an
experimental/demo feature.

== Environment

The *BROWSER* environment variable specifies the browser command (and
possibly options) that will be used to open the requested Cockpit page.
If not set, *cockpit-desktop* attempts to use an internal minimalistic
WebKit browser, and failing that, will attempt to detect some reasonable
alternatives.

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

== See also

man:cockpit-ws[8], man:cockpit-bridge[1]

---

### Doc Modules Man Pages Cockpit Tls.8 (`doc/modules/man/pages/cockpit-tls.8.adoc`)

= cockpit-tls(8)

== Name

cockpit-tls - TLS proxy for Cockpit web service

== Synopsis

*cockpit-tls* [*--help*] [*--port* _PORT_] [*--no-tls*] [*--idle-timeout* _SECONDS_]

== Description

The *cockpit-tls* program is a TLS terminating HTTP proxy for
man:cockpit-ws[8]. It manages a set of isolated cockpit-ws instances,
one per TLS client certificate, plus one for TLS without a client
certificate, and one for unencrypted HTTP. With that, one session cannot
tamper with another one through possible security vulnerability
exploits.

Users or administrators should never need to start this program as it
automatically started by man:systemd[1] via socket activation.

== Transport security

To specify the TLS certificate the web service should use, simply drop a
file with the extension *.cert* in the */etc/cockpit/ws-certs.d*
directory, or below *$XDG_CONFIG_DIRS* if set (see
man:cockpit.conf[5]). If there are multiple files
in this directory, then the highest priority one is chosen after
sorting.

The *.cert* file should contain at least two OpenSSL style PEM blocks.
First one or more `+BEGIN CERTIFICATE+` blocks for the server
certificate and intermediate certificate authorities and a second one
containing a `+BEGIN PRIVATE KEY+` or similar. The key must not be
encrypted.

If there is no TLS certificate, a self-signed certificate is
automatically generated using *sscg* (if available) or *openssl* and
stored in the *0-self-signed.cert* file.

When enrolling into a FreeIPA domain, an SSL certificate is requested
from the IPA server and stored in *10-ipa.cert*.

To check which certificate *cockpit-ws* will use, run the following
command.

....
$ sudo /usr/libexec/cockpit-certificate-ensure --check
....

Or, on Debian-based systems:

....
$ sudo /usr/lib/cockpit/cockpit-certificate-ensure --check
....

If using *certmonger* to manage certificates, following command can be
used to generate a certificate/key pair:

....
CERT_FILE=/etc/cockpit/ws-certs.d/50-certmonger.crt
KEY_FILE=/etc/cockpit/ws-certs.d/50-certmonger.key

getcert request -f ${CERT_FILE} -k ${KEY_FILE} -D $(hostname --fqdn)
....

== Options

*--help*::
  Show help options.
*--port* _PORT_::
  Serve HTTP requests on _PORT_ instead of port 9090. Usually Cockpit is
  started on demand by *systemd* socket activation, and this option
  has no effect. Update the *ListenStream* directive
  *cockpit.socket* file in the usual *systemd* manner.
*--no-tls*::
  Don't use TLS. Certificates will not be read, and https connections
  denied. Then *cockpit-tls* will only manage a single cockpit-ws
  instance, and thus not do anything different than running
  *cockpit-ws --no-tls* directly. Only use this for debugging or
  testing.
*--idle-timeout* _SECONDS_::
  If greater than 0, exit if no connections have happened for the given
  number of seconds, i. e. the server is idle. If not given, the default
  is 90.

== Environment

The *cockpit-tls* program expects the *RUNTIME_DIRECTORY*
environment variable to be set to an empty directory (preferably in
*/run/*) that is only accessible by the system user under which it is
running. This contains the Unix sockets for communicating with the
*cockpit-ws* instances, and in the future, state information about
client certificates. This variable is normally set by the
*cockpit.service* systemd unit.

In addition, *cockpit-tls* will use the *XDG_CONFIG_DIRS*
environment variable from the
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html[XDG
basedir spec] to find its certificates and the man:cockpit.conf[5]
configuration file.

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

== See also

man:cockpit-ws[8] , man:cockpit.conf[5] , man:systemd[1]

---

### Doc Modules Man Pages Cockpit Ws.8 (`doc/modules/man/pages/cockpit-ws.8.adoc`)

= cockpit-ws(8)

== Name

cockpit-ws - Cockpit web service

== Synopsis

*cockpit-ws* [*--help*] [*--port* _PORT_] [*--address* _ADDRESS_] [*--no-tls*]
			 [*--for-tls-proxy*] [*--local-ssh*] [*--local-session* _BRIDGE_]

== Description

The *cockpit-ws* program is the web service component used for
communication between the browser application and various configuration
tools and services like man:cockpit-bridge[1].

Users or administrators should never need to start this program as it
automatically started by man:systemd[1] on boot up, through
man:cockpit-tls[8].

== Transport security

*cockpit-ws* is normally run behind the *cockpit-tls* TLS
terminating proxy, and only deals with unencrypted HTTP by itself. But
for backwards compatibility it can also handle TLS connections by itself
when being run directly. For details how to configure certificates,
please refer to the man:cockpit-tls[8] documentation.

== Timeout

When started via man:systemd[1] then *cockpit-ws* will exit after 90
seconds if nobody logs in, or after the last user is disconnected.

== Options

*--help*::
  Show help options.
*--port* _PORT_::
  Serve HTTP requests _PORT_ instead of port *9090*. Usually Cockpit is
  started on demand by *systemd* socket activation, and this option
  has no effect. Update the *ListenStream* directive
  *cockpit.socket* file in the usual *systemd* manner.
*--address* _ADDRESS_::
  Bind to address _ADDRESS_ instead of binding to all available
  addresses. Usually Cockpit is started on demand by *systemd* socket
  activation, and this option has no effect. In that case, update the
  *ListenStream* directive in the *cockpit.socket* file in the usual
  *systemd* manner.
*--no-tls*::
  Disable http to https redirection.
*--for-tls-proxy*::
  Tell *cockpit-ws* that it is running behind a local reverse proxy
  that does the TLS termination. Then Cockpit puts https:// URLs into
  the default *Content-Security-Policy*, and accepts only https://
  origins, instead of http: ones by default. However, if *Origins* is
  set in the man:cockpit.conf[5] configuration file, it will override
  this default.
*--local-ssh*::
  Normally *cockpit-ws* uses *cockpit-session* and PAM to
  authenticate the user and start a user session. With this option
  enabled, it will instead authenticate via SSH at *127.0.0.1* port
  *22*.
*--local-session* _BRIDGE_::
  Skip all authentication and *cockpit-session*, and launch the
  *cockpit-bridge* specified in _BRIDGE_ in the local session. If the
  _BRIDGE_ is specified as `+-+` then expect an already running bridge
  that is connected to stdin and stdout of this *cockpit-ws* process.
  This allows the web server to run as any unprivileged user in an
  already running session. +
   +
  This mode implies *--no-tls*, thus you need to use http:// URLs with
  this.
[WARNING]
====
With *--local-session* _BRIDGE_, you _have to isolate the opened TCP port_ somehow
(for example in a network namespace), otherwise all other users (or
even remote machines if the port is not just listening on localhost)
can access the session!
====

== Environment

The *cockpit-ws* process will use the *XDG_CONFIG_DIRS* environment
variable from the
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html[XDG
basedir spec] to find its man:cockpit.conf[5] configuration file.

In addition the *XDG_DATA_DIRS* environment variable from the
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html[XDG
basedir spec] can be used to override the location to serve static files
from. These are the files that are served to a non-logged in user.

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

== See also

man:cockpit-tls[8] , man:cockpit.conf[5] , man:systemd[1]

---

### Doc Modules Man Pages Cockpit.1 (`doc/modules/man/pages/cockpit.1.adoc`)

= cockpit(1)

== Name

cockpit - Cockpit

== Description

Cockpit is a web accessible interactive admin interface for Linux
machines. Cockpit can usually be accessed on port `9090` of the
machine it's installed on. Cockpit starts on demand. Use your system
credentials to log in.

== Components

The *cockpit-ws* web service listens on port `9090` and is started
on demand by *systemd*. The Cockpit web service authenticates the
user, loads Cockpit into the browser, and starts *cockpit-bridge* in a
Linux user session.

The *cockpit-bridge* provides Cockpit in the web browser with access
to the system APIs. It does this over its standard in and standard out.
The bridge is started like a shell once per Linux user session.

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

== See also

man:cockpit-tls[8] , man:cockpit-bridge[1] , man:systemd[1], *https://docs.cockpit-project.org/cockpit-guide/latest[Cockpit guide]*

---

### Doc Modules Man Pages Cockpit.Conf.5 (`doc/modules/man/pages/cockpit.conf.5.adoc`)

= cockpit.conf(5)

== Name

cockpit.conf - Cockpit configuration file

== Description

Cockpit can be configured via /etc/cockpit/cockpit.conf. If
*$XDG_CONFIG_DIRS* is set, then the first path containing a
*../cockpit/cockpit.conf* is used instead. Other configuration files
and directories are searched for in the same way.

This file is not required and may need to be created manually. The file
has a INI file syntax and thus contains key / value pairs, grouped into
topical groups. See the examples below for details.

Note: The port that cockpit listens on cannot be changed in this file.
To change the port change the systemd *cockpit.socket* file.

== WebService

*Origins*::
  By default cockpit will not accept crossdomain websocket connections.
  Use this setting to allow access from alternate domains. Origins
  should include scheme, host and port, if necessary. Wildcards and glob
  expressions are permitted. IPv6 addresses must have their brackets escaped
  with backslashes (e.g. `+\[::1\]+`) as they are matched using `+fnmatch()+`.
   +
[source,ini]
----
[WebService]
Origins = https://somedomain1.com https://somedomain2.com:9090 https://*.somedomain3.com https://\[::1\]:9090
----
*ProtocolHeader*::
  Configure cockpit to look at the contents of this header to determine
  if a connection is using tls. This should only be used when cockpit is
  behind a reverse proxy, and care should be taken to make sure that
  incoming requests cannot set this header.
   +
[source,ini]
----
[WebService]
ProtocolHeader = X-Forwarded-Proto
----
*ForwardedForHeader*::
  Configure cockpit to look at the contents of this header to determine
  the real origin of a connection. This should only be used when cockpit
  is behind a reverse proxy, and care should be taken to make sure that
  incoming requests cannot set this header.
   +
[source,ini]
----
[WebService]
ForwardedForHeader = X-Forwarded-For
----
*LoginTitle*::
  Set the browser title for the login screen.
*LoginTo*::
  When set to `+true+` the _Connect to_ option on the login screen is
  visible and allows logging into another server. When set to `+false+`,
  direct remote logins are disallowed. If this option is not specified
  then it will be automatically detected based on whether the
  *cockpit-bridge* package is installed and the *ssh* program is
  available. +
   +
  If cockpit-ws is exposed to the public internet, and also has access
  to a private internal network, it is recommended to explicitly set
  *LoginTo=false*. This prevents unauthenticated remote attackers from
  scanning the internal network for existing machines and open ports.
*RequireHost*::
  When set to `+true+` cockpit will require users to use the _Connect
  to_ option to specify the host to log into.
*AllowMultiHost*::
  When set to `+true+`, cockpit will allow users to connect to multiple
  hosts in one session. The default is OS specific. +
   +
  When connecting to multiple servers, JavaScript runs without
  isolation. All systems will be vulnerable to potential attacks from
  other connected hosts. Enable this option _only_ when all hosts are
  trusted.
*MaxStartups*::
  Same as the *sshd* configuration option by the same name. Specifies
  the maximum number of concurrent login attempts allowed. Additional
  connections will be dropped until authentication succeeds or the
  connections are closed. Defaults to 10. +
   +
  Alternatively, random early drop can be enabled by specifying the
  three colon separated values *start:rate:full* (e.g. "10:30:60").
  Cockpit will start refusing authentication attempts with a probability
  of *rate/100* (30%) if there are currently *start* (10)
  unauthenticated connections. The probability increases linearly and
  all connection attempts are refused if the number of unauthenticated
  connections reaches *full* (60).
*MaxConcurrentConnections*::
  Limits the number of concurrent HTTP connections on the Cockpit port.
  Connection attempts that exceed this limit are rejected early on
  without reading a single byte. This can be used during brute-force
  attacks.  It differs from *MaxStartups*, which protects against too
  many concurrent authentication attempts. Defaults to 500.
*AllowUnencrypted*::
  If true, cockpit will accept unencrypted HTTP connections. Otherwise,
  it redirects all HTTP connections to HTTPS. Exceptions are connections
  from localhost and for certain URLs (like */ping*). Defaults to
  false.
*UrlRoot*::
  The root URL where you will be serving cockpit. When provided cockpit
  will expect all requests to be prefixed with the given url. This is
  mostly useful when you are using cockpit behind a reverse proxy, such
  as nginx. `+/cockpit/+` and `+/cockpit++` are reserved and should not
  be used. For example `+/cockpit-new/+` is ok. `+/cockpit/+` and
  `+/cockpit+new/+` are not.
*ClientCertAuthentication*::
  If true, enable TLS client certificates for authenticating users.
  Commonly these are provided by a smart card, but it's equally possible
  to import certificates directly into the web browser. Please see the
  https://docs.cockpit-project.org/cockpit-guide/latest/guide/cert-authentication.html[Certificate/smart
  card authentication] section in the Cockpit guide for details.
*Shell*::
  The relative URL to top level component to display in Cockpit once
  logged in. Defaults to `+/shell/index.html+`
*CustomLoginPage*::
  Load the login page from an alternative directory. The directory must
  contain a `+login.html+` and optionally a `+po.js+` file for translations.
  When not set, the hardcoded `+$prefix/share/cockpit/static+` directory is used.

== Log

*Fatal*::
  The kind of log messages in the bridge to treat as fatal. Separate
  multiple values with spaces. Relevant values are: *criticals* and
  *warnings*.

== OAuth

Cockpit can be configured to support the
https://tools.ietf.org/html/rfc6749#section-4.2[implicit grant] OAuth
authorization flow. When successful the resulting oauth token will be
passed to cockpit-ws using the *Bearer* auth-scheme. For a login to be
successful, cockpit will also need a to be configured to verify and
allow *Bearer* tokens.

*URL*::
  This is the url that cockpit will redirect the users browser to when
  it needs to obtain an oauth token. Cockpit will add a redirect_uri
  parameter to the url with the location of where the oauth provider
  should redirect to once a token has been obtained.
*ErrorParam*::
  When a oauth provider redirects a user back to cockpit, look for this
  parameter in the querystring or fragment portion of the url to find a
  error message. When not provided it will default to
  *error_description*
*TokenParam*::
  When a oauth provider redirects a user back to cockpit, look for this
  parameter in the querystring or fragment portion of the url to find
  the access token. When not provided it will default to
  *access_token*

== Session

*Banner*::
  The contents of the specified file (commonly */etc/issue*) are shown
  on the login page. By default, no banner is displayed.
*IdleTimeout*::
  Time in minutes after which session expires and user is logged out if
  no user action has been performed in the given time. This idle timeout
  only applies to interactive password logins. With non-interactive
  authentication methods like Kerberos, OAuth, or certificate login, the
  browser cannot forget credentials, and thus automatic logouts are not
  useful for protecting credentials of forgotten sessions. Set to *0*
  to disable session timeout.
   +
[source,ini]
----
[Session]
IdleTimeout=15
----

{empty}::
  When not specified, there is no idle timeout by default.

*WarnBeforeConnecting*::
  Whether to warn before connecting to remote hosts from the Shell.
  Defaults to true.
   +
[source,ini]
----
[Session]
WarnBeforeConnecting=false
----

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

== See also

man:cockpit-ws[8], man:cockpit-tls[8]

---

### Doc Modules Man Pages Pam Ssh Add.8 (`doc/modules/man/pages/pam_ssh_add.8.adoc`)

= pam_ssh_add(8)

== Name

pam_ssh_add - PAM module to auto load ssh keys into an agent

== Description

*pam_ssh_add* provides authentication and session modules that allow users
to start their session with a running ssh-agent with as many ssh keys
loaded as possible.

If used, the authentication module simply stores the authentication
token for later use by the session module. Because this module performs
no actual authentication it returns PAM_CRED_INSUFFICIENT on success and
should always be accompanied by an actual authentication module in your
pam configuration.

By default the session module will start a new ssh-agent and run
ssh-add, loading any keys that exist in the default path for the newly
logged in user. If any keys prompt for a password, and a authentication
token was successfully stored, that token will be provided as the
password.

== Options

*debug*::
  This option will turn on debug logging to syslog.

== Examples

....
        auth     required  pam_unix.so
        auth     optional  pam_ssh_add.so
        session  optional  pam_ssh_add.so

....

== Bugs

include::../partials/bugs.adoc[]

== Author

include::../partials/author.adoc[]

---

### Doc Modules Man Partials Author (`doc/modules/man/partials/author.adoc`)

Cockpit has been written by many
https://github.com/cockpit-project/cockpit[contributors].

---

### Doc Modules Man Partials Bugs (`doc/modules/man/partials/bugs.adoc`)

Please send bug reports to either the distribution bug tracker or the https://github.com/cockpit-project/cockpit/issues/new[upstream bug tracker].

---

--- METRICS ---
- Files Extracted: 82
- Estimated Token Budget: ~85218 tokens
- Canonical Reference: https://codewiki.google/github.com/cockpit-project/cockpit
