> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboarding options

> Every option the ten onboarding steps ask for: what it installs or writes, its default, when you can set it, and the API route and MCP tool behind it.

This page lists every option in **Platform Onboarding**, in stepper order. [Platform onboarding](https://docs.celum.io/get-started/platform-onboarding.md) explains why the steps come in this order and what gates each one. Read that page first.

Each table gives the label you see in the dashboard and, in brackets, the field in the request body. Each section ends with the API routes and MCP tools that do the same thing. If no MCP tool exists, the section says so.

> [!NOTE]
>
> A route written as `.../x` means `/api/supervisors/{name}/x`. Several routes replace the whole configuration with the body you send, so a field you leave out goes back to its default. The sections say which routes do this. For those routes, read `desired` from the step's status route, change what you need, and send the whole body back. [Permissions](https://docs.celum.io/reference/permissions.md) lists every IAM action named below.

## Source & Bootstrap

This step records where the supervisor pulls charts and images from. It then installs Flux and the cluster agent from that source. [Registries](https://docs.celum.io/gitops/registries.md) covers registering a registry. [Registry mirroring in depth](https://docs.celum.io/gitops/registry-mirroring.md) shows where each declaration reaches.

The three declarations below are written to the supervisor's `NuviraClusterConfig` named `default`. To see them, run `kubectl get nuviraclusterconfig default -o yaml`. Writing any of them needs `cilium:Apply` on the supervisor, not `supervisor:Onboard`.

### Container image registry mirror

This section makes the nodes' containerd pull public registries through a local Harbor proxy cache. Celum writes the declaration into an Omni ConfigPatch that Celum owns. Talos then reloads containerd without a reboot.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Local Harbor host** (`mirrorHost`) | Sets the registry host that every upstream row points at. Pick a registered registry from the list, or type a host. Celum looks up the pull credential and CA by matching this host against the registered registries. If no registered registry matches, pulls are anonymous. | Empty. On a Celum appliance, the appliance's Harbor | Always. The list of registered registries appears only if you may read them (`registry:List`) |
| **Upstream registry** → **Harbor proxy-cache project** (`upstreams[].registry`, `upstreams[].proxyProject`) | Each row sends one public registry to `https://` + host + `/v2/` + project, with `overridePath`, so image references stay the same. Clear a project to drop its row. | Seven rows: `docker.io` → `proxy-dockerhub`, `ghcr.io` → `proxy-ghcr`, `quay.io` → `proxy-quay`, `registry.k8s.io` → `proxy-k8s`, `public.ecr.aws` → `proxy-ecr`, `mirror.gcr.io` → `proxy-dockerhub`, `us-central1-docker.pkg.dev` → `proxy-gar`. On an appliance, only the rows its Harbor serves | Always. The dashboard has no control to add a row. To add one, send more `upstreams` through the API |
| **Strict air-gap** (`skipFallback`) | Makes a node fail a pull that the mirror cannot serve, instead of fetching the image from the internet. The pod then shows an `ImagePullBackOff` that names the image. It applies only to the upstreams listed above. A registry with no row still goes direct. Guest clusters get the same setting through their ClusterClass, from `capi-clusterclasses` 0.6.14, for machines built after the ClusterClasses are next applied | Off. On an appliance deployed with air-gap mode `strict`, on, unless Flux already runs on the supervisor | Always. It takes effect only with a **Local Harbor host**. Turn it on only after the host serves every project listed. Until then, every image from a missing project stops |
| **Save & apply** | Saves the declaration, then applies the Omni ConfigPatch. If the patch fails (no Omni, or Omni unreachable), the declaration is still saved and the section says why the nodes did not get it | — | Disabled while **Local Harbor host** is empty |

> [!WARNING]
>
> The `PUT` replaces the whole declaration. A body without `skipFallback` turns strict air-gap off. A body without an upstream drops that upstream. Send every field you mean to keep.

To remove the mirror, send a `PUT` with an empty `mirrorHost` and no `upstreams`. The dashboard cannot do this, because **Save & apply** stays disabled while the host is empty.

**On a Celum appliance.** When this section loads for a supervisor with no declaration, Celum declares the mirror from the appliance. The host is the appliance's Harbor, and the rows are only the proxy projects that Harbor serves. Registering the supervisor triggers the same attempt. Celum does this once per supervisor and never overwrites a declaration you saved. After your first save, or after you clear the mirror, Celum does not declare it again. If the appliance runs with air-gap mode `strict` and your declaration is not strict, the section shows a warning and leaves your declaration unchanged. You set the appliance's air-gap mode when you deploy it. See [Deploy the appliance](https://docs.celum.io/get-started/deploy-the-appliance.md) and [Appliance configuration](https://docs.celum.io/reference/appliance-configuration.md).

**API:** `PUT /api/supervisors/{name}/onboarding/image-registry` (`cilium:Apply`). Read it with `GET` on the same path (`cilium:GetDefaults`). `POST .../onboarding/image-registry/from-appliance` (`cilium:Apply`) declares the mirror from the appliance. `GET /api/appliance/posture` (`registry:List`) reads the appliance's posture. **MCP:** `set_onboarding_image_registry`, `get_onboarding_image_registry`, `declare_onboarding_image_registry_from_appliance`, `get_appliance_posture`.

### Platform Registry Sources

This section chooses the registered registry that serves this supervisor's platform charts. Only the registry's name is stored on the cluster. Its credentials stay in Celum's catalogue.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Helm charts** (`charts`) | Sets the registry for third-party platform charts, such as Cilium, Rook and cert-manager. The line under the list shows the source that installs actually use. It also warns when a declared name no longer matches a registered registry | **Follow the estate-wide default** (empty) | When at least one registry is registered. With none, the section says **No registries are registered yet.** and links to **Manage registries** |
| **Nuvira charts & images** (`images`) | Sets the registry for Celum's own charts, such as the cluster agent. If unset, it uses the **Helm charts** choice. Its host is also added to the hosts the nodes trust. The platform's own images still follow the estate-wide default | **Follow the estate-wide default** (empty) | As above |
| **Save & apply** | Saves both names, then applies the nodes' registry trust again through Omni. Celum refuses a name that is not registered | — | Always. While either choice is unsaved, the step's install button stays disabled |

With nothing declared, a supervisor uses its site's default registry. Next it uses the estate-wide default, and then each chart's public source. [Charts & the mirror](https://docs.celum.io/gitops/charts.md) gives the full order. The cluster agent chart has no public source. If no registry resolves for Celum's own charts, Celum uses a registry address built into Celum. So register the registry that holds Celum's charts before you install this step.

A saved choice changes where later installs pull from. It does not move a release that is already installed. To move one, use **Re-point** under **Registry provenance (audit)**, below.

### Additional registries

This section declares registries that your own workloads pull from, typically a private registry for application images. It does not change where the platform installs from.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| One toggle per registered registry (`additional[]`) | Makes the nodes trust the registry's CA, through the Omni ConfigPatch. Puts the registry's credentials in every project namespace as a pull secret named `celum-pull-` + registry name, and attaches it to that namespace's `default` ServiceAccount. Guest clusters get the same credential, and the cluster agent in each guest copies it to the guest's namespaces. A pod that sets its own `serviceAccountName` inherits nothing and must reference the secret itself | Off for every registry | When at least one registry is registered. A declared name that is no longer registered shows a warning |
| **Save & apply** | The same write as in **Platform Registry Sources** | — | Only when the selection changed |

**API:** `PUT /api/supervisors/{name}/onboarding/registry-sources` (`cilium:Apply`) with `charts`, `images` and `additional`. Read it with `GET` on the same path (`cilium:GetDefaults`). **MCP:** `set_onboarding_registry_sources`, `get_onboarding_registry_sources`.

> [!WARNING]
>
> The `PUT` replaces all three fields. A body without `additional` removes every additional registry. An empty `charts` or `images` returns that role to the default. Always send all three fields together.

### Where credentials will be stored

This is a read-only table. For each of the eight credentials that onboarding asks for, it compares two places: a central secret store, and this cluster only. See [Secret Store](#secret-store).

### Flux and the cluster agent

The step card is titled **Platform Prerequisites**. Its one action installs both components, in order.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Install Source & Bootstrap** (or **Re-apply Source & Bootstrap**) | Applies the Flux controllers that Celum ships: source-, helm- and kustomize-controller, on the host network. The backend waits up to five minutes for them to report Ready, and the dashboard then waits up to two minutes more. When Flux is Ready, it installs the cluster agent as the HelmRelease `kube-system/cluster-agent`. The chart comes from an OCI HelmRepository on the registry chosen for Celum's own charts. If Flux is not Ready in time, the agent is not installed and the step tells you to re-apply | — | Disabled until both status reads have answered, and while a registry source is unsaved. Needs `supervisor:Onboard` for Flux and `supervisor:Bootstrap` for the agent |
| Cluster agent **Target Chart Version** (`chartVersion`) | Before the first install, the version **Install Source & Bootstrap** installs. After it, the panel's **Upgrade to** button moves the agent's chart to the version you picked, and **Re-apply** with the installed version keeps it. A re-apply keeps every setting that later steps added to the agent | First install: Celum's pinned version, which the picker shows. Over the API with no version: the newest cluster-agent chart in that registry, or the pinned version when the registry cannot be read | The panel's button appears only after the agent is installed. Disabled while a registry source is unsaved |
| Flux **Re-apply** | Applies the Flux controllers again. You cannot choose a Flux version | — | Only after Flux is installed |

**API:** `POST /api/supervisors/{name}/flux/install` (`supervisor:Onboard`), then `POST .../cluster-agent/install/helm` (`supervisor:Bootstrap`) with an optional `chartVersion`. **MCP:** no tool installs Flux, so use the API. `get_flux_version` names the Flux release that Celum installs. For the agent, use `install_cluster_agent_helm`. `upgrade_supervisor_cluster_agent` (`POST .../cluster-agent/upgrade`) moves both the agent's chart and its image.

> [!NOTE]
>
> `POST .../cluster-agent/install` (MCP `install_cluster_agent`) is a different route. It applies a bare agent without Flux, to diagnose a supervisor that has no Flux. The wizard does not use it.

### Registry provenance (audit)

This panel reads what the nodes and the Flux objects actually use, not what you declared. It offers three repairs.

| Action | What it does | When it is available | API · MCP |
| - | - | - | - |
| **Re-apply to nodes** | Writes the Omni ConfigPatch again, with the registries' CA and credentials from the current declaration. Nothing else changes. A cluster template sync in Omni deletes this patch | When Omni is available and the applied patch is missing or differs from the declaration | `POST .../onboarding/registry-trust/apply` (`cilium:Apply`) · `reapply_onboarding_registry_trust` |
| **Re-point** | Moves one installed HelmRelease to the chart source that resolves for this supervisor now. The values stay the same | On each release that pulls from a public upstream | `POST .../onboarding/chart-repoint` (`cilium:Apply`) · `repoint_onboarding_chart` |
| **Re-apply ClusterClasses** | Writes again the registry trust that new guest machines are built with. Running nodes keep the old trust until they are replaced | When the guest trust is out of date | `POST .../capi/clusterclasses/install` (`capi:Install`) · none, use the API |

To read the panel, use `GET .../onboarding/registry-provenance` (`cilium:GetDefaults`) or `get_onboarding_registry_provenance`.

## Networking

The step has two halves. First you declare three things: what each node interface is for, how the supervisor's address block splits into bands, and how the physical network reaches that block. Then one button installs Cilium, then FRR-K8s if you peer over BGP, then Envoy Gateway as the North-South gateway.

The declarations are written to the supervisor's `NuviraClusterConfig`. They work before Flux and Cilium exist. **Install Networking** stays disabled until all three are saved: the interface roles, an address plan with a block plus the **Project LB**, **VM identity** and **Egress** bands, and a routing plan. The footer names the ones still missing. [Networking overview](https://docs.celum.io/networking/overview.md) explains the engine model.

### Declare interface roles

This table shows every L3 interface the nodes report, with one column per role. Detection fills in the table, but nothing is declared until you press **Save interface roles**. The `siderolink` row is the management tunnel to Omni and cannot take a role. If no interfaces are listed, install the cluster agent first (in Source & Bootstrap) or wait for topology discovery.

One interface can carry several roles. Detection pre-ticks roles as follows:

* The interface with the node InternalIP or the default route gets **primary** and **BGP**.
* An interface already in Cilium's `devices` pattern gets **pod-overlay**.
* An interface with an MTU of 8000 or more, and no other role, gets **storage**.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **primary** (`interfaceRoles`) | Adds the interface to Cilium's `devices` set. If exactly one interface is primary, Cilium pins it as `nodePort.directRoutingDevice`. With two or more, Cilium detects the device itself. If no interface is tagged **BGP**, BGP peering addresses come from this one | Pre-ticked on the node-IP or default-route interface | Always |
| **BGP** | Names the interface that BGP sessions leave from. The routing table below reads each node's peering address from this interface first | Pre-ticked with **primary** | Always |
| **storage** | Names the storage network. The Storage step uses its subnet to pre-fill Ceph's public and cluster networks and to check the NFS server address. It also attaches the NFS route to this interface. LINSTOR gets each node's address on it | Pre-ticked on jumbo-MTU interfaces | Always. A warning appears when its MTU is below 9000 |
| **pod-overlay** | Adds the interface to Cilium's `devices` set. In tunnel mode, the overlay uses the interface that carries the node InternalIP. So the Cilium install refuses a **pod-overlay** declaration that names no node-IP interface | Pre-ticked on interfaces in the current Cilium `devices` pattern | Always. A warning appears on an interface that does not carry the node IP |
| **mgmt** | Recorded on the supervisor. No onboarding step reads it | Off | Always |
| **uplink** | Recorded on the supervisor. No onboarding step reads it | Off | Always |

The node-IP interface is always in Cilium's `devices` set, whether you tag it or not. Saving an empty map clears the declaration, and later steps go back to inferring.

**API:** `PUT /api/supervisors/{name}/onboarding/interface-roles` (`cilium:Apply`), with a body such as `{"interfaceRoles": {"eth0": ["primary", "bgp"], "eth1": ["storage"]}}`. Read it with `GET` on the same path (`cilium:GetDefaults`). **MCP:** `set_onboarding_interface_roles`. To read it, use `get_onboarding_prerequisites` with `view=interface-roles`.

### Required networks

The panel shows two detected values that you cannot edit:

* **Cluster Pod CIDR** comes from the Talos configuration through Omni, or from the pod CIDRs assigned to the nodes.
* **Service CIDR** is the ClusterIP range, fixed when the cluster was created.

A warning appears when the running pod network differs from `spec.pools.podSupercidr` in the `NuviraClusterConfig`. The address plan does not set the pod network, because Cilium owns it.

Under **Product address plan**, you declare one block and four bands inside it. The cluster checks three rules: every band sits inside the block, no two bands overlap, and the slice masks fit. It refuses a plan that breaks a rule. [Projects](https://docs.celum.io/concepts/projects.md) explains how projects draw from these bands. [Pools & IPAM](https://docs.celum.io/networking/pools-and-ipam.md) shows what is allocated from them.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Block** (`block`) | This supervisor's whole slice of the site's address space. Every band must sit inside it. The **Routing** section below tells you to route this destination | — | Always. **Install Networking** needs it |
| Band size sliders (**Platform LB**, **Project LB**, **VM identity**, **Egress**) | Set each band's prefix length, from the block's own prefix to `/30`. The bands are laid out largest first and stay CIDR-aligned. Addresses that no band claims show as `free`. If a set of sizes does not fit the block, the panel refuses it with a message and keeps the last values that fit | Half of the block for **Project LB**, a quarter for **VM identity**, an eighth each for **Egress** and **Platform LB** | Once **Block** is a valid CIDR |
| **Platform LB** (`platformLb`) | The platform's own LoadBalancer range. The shared gateway VIP and every cluster-level Service draw from it. When Cilium first becomes Ready, Celum creates the `service-pool` `CiliumLoadBalancerIPPool` from it. Takes a CIDR or a start-stop range. With a block declared, it must be a CIDR inside the block that overlaps no other band | The last eighth of the block. If you never declare it, Celum takes the last `/25` of the block that overlaps no other band | Always. **live** or **saved** appears next to the field when a pool or a saved value exists |
| **Project LB** (`lbSupercidr`) | The range that each project's LoadBalancer pool is carved from | The first half of the block | Always. **Install Networking** needs it |
| Project LB slice mask (`lbSliceMask`) | The prefix length of one project's LoadBalancer pool. The panel shows how many projects fit. The mask must be longer than the **Project LB** prefix, and between `/22` and `/30` | `/29` | Always |
| **VM identity** (`vmIdentitySupercidr`) | The range that per-VM identity addresses are carved from. A VM identity address follows the VM through live migration. See [VM identity pools](https://docs.celum.io/networking/vm-identity-pools.md) | The third quarter of the block | Always. **Install Networking** needs it |
| VM identity slice mask (`vmIdentitySliceMask`) | The prefix length of one project's VM identity pool. The mask must be longer than the **VM identity** prefix, and between `/24` and `/29` | `/29` | Always |
| **Egress** (`egressPool`) | The range that each project's egress `/32` comes from. When you save, the first address of the band is reserved for the DNS honeypot, even if the honeypot is off. See [DNS](https://docs.celum.io/networking/dns.md) | The seventh eighth of the block | Always. **Save plan** stays disabled until it is a valid CIDR |

When you type a block between `/12` and `/26`, the panel proposes all four bands and both slice masks, always within the bounds above; the panel flags a mask you type outside them before you save. It stops proposing as soon as you edit a band, or when a saved plan loads. For example, for the block `198.51.100.0/24` the proposal is:

* **Project LB** `198.51.100.0/25` at `/29` (16 projects)
* **VM identity** `198.51.100.128/26` at `/29` (8 slices)
* **Egress** `198.51.100.192/27`
* **Platform LB** `198.51.100.224/27`

The honeypot then takes `198.51.100.192`.

> [!WARNING]
>
> On a supervisor that already runs the pool, changing **Platform LB** moves the live `service-pool`. It also re-pins every LoadBalancer Service that was pinned to the old band, including the shared gateway VIP. Each address keeps its offset inside the band. After the move, a DNS record that points at the old VIP is wrong. Moving the band needs `cilium:Apply`, plus `gateway:Create` when a Service is re-pinned. Without `cilium:Apply`, the wizard saves the rest of the plan and tells you that the band did not move.

Under the plan, **DNS records this platform will need** lists the names that Grafana, Prometheus and ClickHouse answer on. Each name needs an A record that points at the shared gateway VIP. See [Service DNS records](#service-dns-records).

**API:** `PUT /api/supervisors/{name}/projects/config` (`project:Configure`). To move the band, send `platformLb` in the same body. Read the plan with `GET` on the same path (`project:GetConfig`). **MCP:** `configure_project_networking`. To read it, use `get_project_networking_config`. To see free space, use `get_address_plan`.

### Routing

Declare how the physical network reaches the block. Nothing is pre-selected, and **Save routing plan** stays disabled until you choose a type. Route the whole block as one destination, with one equal-cost next hop per node. **Project LB**, **Platform LB** and **VM identity** addresses are advertised from every node. Each egress address is advertised from exactly one node, and its more specific route wins.

Each node peers from its address on the interface tagged **BGP**. With no **BGP** tag, it uses the **primary** interface. Celum falls back to the node InternalIP only when no interface roles are declared.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Static routes** (`type: static`) | You program static routes on the upstream router, and Celum installs no BGP engine. The step's BGP engine section disappears. **Program on the physical device** lists one route per node: the block via each node's peering address. With no block declared, it lists the **Platform LB** band instead. Saving **Static routes** clears every BGP field | Not selected | Always |
| **BGP (FRR)** (`type: bgp`) | **Install Networking** installs FRR-K8s. When FRR-K8s is Ready, the wizard creates the `FRRConfiguration` `onboarding-routing` in `frr-k8s-system`. This configuration has one router at **Our AS** and one neighbour at **Infra AS** for each infra peer, with a keepalive of 3 s and a hold time of 9 s. It advertises every prefix that the cluster agent injects. If another `FRRConfiguration` already peers with one of the same routers, Celum does not create a second one | Not selected | Always |
| **Our AS** (`ourAS`) | The ASN that the nodes use for BGP | `65100` | **BGP (FRR)** only. Must be a positive number |
| **Infra AS** (`infraAS`) | The ASN of the upstream routers. One value covers every peer | — | **BGP (FRR)** only. Required |
| **Infra peer address(es)** (`infraPeers`) | The router addresses that every node peers with, one per line. Declare two for router redundancy | — | **BGP (FRR)** only. 1 to 4 IPv4 addresses, no duplicates |
| **Bidirectional Forwarding Detection (BFD)** (`bfd`) | Attaches the BFD profile `onboarding-routing-bfd` to every peer: 50 ms transmit, 50 ms receive, detect multiplier 3. Enable BFD on the routers too | Off | **BGP (FRR)** only |
| **Inbound soft reconfiguration** (`softReconfig`) | Adds `soft-reconfiguration inbound` for every peer. FRR then keeps each peer's received routes before it applies policy, and you can read them under **Networking → BGP**. Memory use grows with the number of prefixes the peers send | Off | **BGP (FRR)** only |
| **TCP MD5 authentication** (`md5`) | Authenticates every peer session with one shared password. The routers must use the same password | Off | **BGP (FRR)** only |
| **MD5 password** (`md5Password`) | Write-only. Celum never writes it into the routing plan or the `FRRConfiguration`. The neighbours reference a `kubernetes.io/basic-auth` Secret, `onboarding-routing-md5`, in `frr-k8s-system`. To keep the stored password on a later save, leave the field blank | — | When **TCP MD5 authentication** is on |

Where the MD5 password goes depends on the [Secret Store](#secret-store) step:

* If this supervisor's secret store is connected, the password goes to the store when you save. An ExternalSecret then creates the Secret when the BGP configuration is applied.
* If not, Celum keeps the password in a Secret in `kube-system` and moves it into `frr-k8s-system` when it applies the configuration. The password then exists only on this cluster.
* If a store becomes available later, the next BGP apply moves the password into it. Peering drops for a few seconds while the Secret is replaced.
* If MD5 is on and there is neither a store nor a stored password, the apply answers `409`.

> [!NOTE]
>
> The wizard creates `onboarding-routing` from the browser, while the Networking step is open. The backend's Networking apply installs FRR-K8s but does not create this configuration. If you onboard a supervisor through the API, call `POST /api/supervisors/{name}/networking/routing-plan/apply-bgp` after FRR-K8s is Ready.

**API:** `PUT /api/supervisors/{name}/networking/routing-plan` (`cilium:Apply`) saves the plan to `spec.bgp` in the `NuviraClusterConfig`. `GET` on the same path (`cilium:GetState`) returns the plan with the device table. `POST .../networking/routing-plan/apply-bgp` (`cilium:Apply`) creates the `FRRConfiguration`. **MCP:** no tool for the routing plan, so use the API. `update_frr_k8s_config` writes an `FRRConfiguration` directly. That is the [BGP & Egress](https://docs.celum.io/networking/bgp-and-egress.md) path, not this declaration.

### Cilium CNI

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Target Chart Version** (`chartVersion`) | Sets the Cilium chart version of the HelmRelease `cilium` in `kube-system`. Versions before 1.20 have no per-pod opt-out from Source IP Verification. For those versions, Celum turns Source IP Verification off for the whole cluster | The installed version. On a first install, the version Celum pins | Always. Once the release exists, the panel also offers **Re-apply chart**, or **Upgrade to** the version you picked. That button applies Cilium alone |
| **Cluster Name** (`clusterName`) | Sets Cilium's `cluster.name` | The supervisor's name | Read-only |
| **Cluster ID** (`clusterID`) | Sets Cilium's `cluster.id`. Cluster Mesh uses it to tell this cluster apart from its peers | The lowest number from 1 to 255 that no other supervisor in this Celum uses. An installed Cilium keeps its current value | Always. The field takes 1 to 255. Celum does not refuse a duplicate |
| **Cluster Mesh** | Turns on `clustermesh-apiserver`, behind a `LoadBalancer` Service, in the running Cilium release. The change applies at once and does not wait for **Install Networking**. Turning it off sets `clustermesh.useAPIServer` to `false` | Off | Disabled until Cilium reports Ready, because it patches the live HelmRelease. Disabled if you do not have `cilium:Apply` |
| **North-South gateway** | States which engine owns the Gateway API. It is information, not a control | Envoy Gateway | Always. A supervisor that already runs Cilium's built-in Gateway API keeps it, because the form sends back what is deployed |

Under the table, the card shows the **routing device**. This is the one interface you declared **primary**, which Celum pins as Cilium's direct-routing device. If no interface is declared primary, the install fails with `unable to determine direct routing device`. To fix this, change **Declare interface roles** above, not this card.

Celum also derives Cilium's `devices` set; you do not type it. The set holds every interface tagged **primary** or **pod-overlay**, plus every interface that carries a node IP. The install refuses a `devices` set that leaves out an interface carrying a node IP.

> [!NOTE]
>
> Celum does not join Cluster Mesh peers. No screen, API route or MCP tool writes a peer: the toggle leaves `clustermesh.config.clusters` empty. To connect this cluster to another one, you work outside Celum.

When Cilium reports Ready, Celum creates the platform LoadBalancer pool `service-pool`. Once a GatewayClass exists, it also creates the shared Gateway `shared-gateway`. It creates each one only if it is absent. See [Gateways & Routes](https://docs.celum.io/networking/gateways-and-routes.md).

**API:** `POST /api/supervisors/{name}/cilium/install` (`cilium:Install`) is the Cilium card's own **Re-apply chart** button. Cluster Mesh is `POST .../networking/cilium-feature` with `{"feature": "clusterMesh", "enabled": true}` (`cilium:Apply`). **MCP:** no tool installs Cilium. `set_cilium_feature` turns Cluster Mesh on or off. To see what an install would use, use `get_cilium_config` with `view=install-defaults`, or `get_cilium_install_preview`.

### Cilium values the form does not show

These values come from the platform defaults. You can change them only through the API, in `values` in the Cilium request body. A re-apply keeps the live `egressGateway`, `clustermesh`, `hubble`, `localRedirectPolicies`, `gatewayAPI` and `ingressController` blocks of the running release. So it does not undo a feature that was switched since.

| Value | Default | Notes |
| - | - | - |
| `routingMode`, `tunnelProtocol` | `tunnel`, `geneve` | `native` is accepted. `vxlan` is refused together with `bpfLbMode` `dsr` or `hybrid` |
| `kubeProxyReplacement` | `true` | |
| `ipamMode` | `multi-pool` | Seeds the `default` pod IP pool from the detected cluster pod CIDR |
| `bpfLbMode` | `hybrid` | `snat`, `dsr` or `hybrid` |
| `enableBpfMasquerade`, `enableEndpointRoutes` | `true` | VM identity addresses need endpoint routes |
| `egressGatewayEnabled` | `true` | Changes no traffic until an egress policy exists |
| `hubbleEnabled`, `hubbleRelayEnabled` | `true` | Relay serves the cluster-wide flow queries behind the Security views |
| `hubbleUiEnabled` | `false` | |
| `enableGatewayAPI` | `false` | Refused with `409` while Envoy Gateway owns the Gateway API CRDs |
| `ingressControllerEnabled` | `false` | |
| `bgpControlPlane` | `false` | Cilium's own BGP. The platform peers through FRR-K8s instead |
| `enableSCTP` | `true` | Every install forces it on |

Every Cilium install also labels `kube-system` for privileged pods.

`POST .../cilium/install/validate` (`cilium:GetDefaults`) checks a Cilium body without applying it. It checks the routing mode, the tunnel protocol, the LB mode, the IPAM mode, the API server host and port, and the pod CIDR. It does not check the Cluster ID. The wizard has no button for it, and no MCP tool calls it.

### BGP engine

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **FRR-K8s (BGP)** or **No BGP (static routes)** | **FRR-K8s (BGP)** installs `metallb/frr-k8s` as a HelmRelease in `frr-k8s-system`, a namespace labelled for privileged pods. **No BGP (static routes)** installs nothing | Follows the routing plan: `bgp` installs FRR-K8s, and `static` installs no engine | The two cards appear only while no routing plan is saved. With a `bgp` plan, the section names FRR-K8s and offers no choice. With a `static` plan, the whole section is hidden |
| **Target Chart Version** (`chartVersion`) | Sets the FRR-K8s chart version | The installed version. On a first install, the version Celum pins | When the engine is FRR-K8s. Once the release exists, **Re-apply chart** or **Upgrade to** applies FRR-K8s alone |

The install also turns on the cluster agent's bridge, which writes the advertised prefixes into the FRRConfigurations. If you install FRR-K8s before the **Monitoring** step, Prometheus does not scrape it. Re-apply FRR-K8s after Monitoring to add the scrape. To add peers beyond the routing plan, see [BGP & Egress](https://docs.celum.io/networking/bgp-and-egress.md).

**API:** `POST /api/supervisors/{name}/frr-k8s/install` (`cilium:Install`), with the body `{"chartVersion": "..."}`. **MCP:** `install_frr_k8s`.

### Gateway (North-South)

This section appears when Envoy Gateway is the North-South engine. It is hidden on a supervisor that runs Cilium's built-in Gateway API. If both engines are active, the section reports a conflict.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Default data-plane datapath** (`proxyMode`): **Shared · single replica** (`cluster`) or **Per-node · source IP + HA** (`daemonset`) | Writes the GatewayClass default `EnvoyProxy`, `shared-gw-proxy`, which every Envoy gateway inherits. **Shared** runs one Envoy proxy Deployment. **Per-node** runs an Envoy proxy on every schedulable node, as a DaemonSet. Both modes set the gateway Service to `externalTrafficPolicy: Cluster`. You can override the mode for one gateway later, under [Datapath modes](https://docs.celum.io/networking/gateways-and-routes.md#datapath-modes) | **Per-node**. The API also uses `daemonset` unless the body says `cluster`. On an installed supervisor, the form shows the mode in use | When Envoy Gateway is the engine. If the deployed datapath cannot be read, the field is disabled and the apply is refused, so the form never sends its default over a running datapath |
| **Target Chart Version** (`chartVersion`) | Sets the version of the `gateway-helm` chart, released as `envoy-gateway` in `envoy-gateway-system` | The installed version. On a first install, the version Celum pins | When Envoy Gateway is the engine. Once the release exists, **Re-apply chart** or **Upgrade to** applies Envoy Gateway alone |

The Envoy Gateway install answers `409` while Cilium's built-in Gateway API is on. Otherwise it runs these steps, in order:

1. Applies the Gateway API CRD bundle.
2. Removes the `safe-upgrades.gateway.networking.k8s.io` ValidatingAdmissionPolicy and its binding, because that policy blocks the experimental-channel CRDs that Envoy Gateway needs.
3. Creates the `envoy-gateway` GatewayClass.

**API:** `POST /api/supervisors/{name}/gateway/envoy/install` (`gateway:Create`), with the body `{"chartVersion": "...", "proxyMode": "daemonset"}`. **MCP:** no tool installs Envoy Gateway on a supervisor, so use the API. `install_guest_envoy_gateway` installs it in a tenant cluster, not on the supervisor.

### Service DNS records

After Cilium reports Ready, a read-only box, **Service DNS records (read-only)**, lists the platform-service host names to publish as A records. Each name points at the shared gateway's address. Until the gateway is programmed, that address is predicted from the platform LoadBalancer pool. After that, it is the live address. The box also says whether each name resolves from the Celum backend. The **DNS Publishing** step can create the records, or you can create them by hand. To read the box, use `GET .../onboarding/required-dns` (`cilium:GetDefaults`), or `get_onboarding_prerequisites` with `view=required-dns`.

### What Install Networking does

**Install Networking** (or **Re-apply Networking** once the Cilium release exists) sends one request, `POST /api/supervisors/{name}/onboarding/networking/apply`. The request carries the body of each install it runs. An engine left out of the body is not installed.

```json theme={null}
{
  "cilium": { "profileId": "custom", "chartSource": "upstream", "chartVersion": "", "values": { "clusterID": 1 } },
  "frr":    { "chartVersion": "" },
  "envoy":  { "chartVersion": "", "proxyMode": "daemonset" }
}
```

1. Celum applies Cilium at once. If Cilium is refused, the refusal comes back to the click and nothing else runs.
2. The rest runs as a job on the backend. The job waits up to 15 minutes for the Cilium HelmRelease to be Ready for its latest generation. After 15 minutes it continues anyway, and Flux keeps reconciling. It then creates the platform pool and the shared gateway, installs FRR-K8s, and installs Envoy Gateway. It stops at the first failure and marks the later stages as not attempted.
3. After every stage, the job writes its state to the ConfigMap `kube-system/celum-networking-apply`, so a page reload shows the same progress. After a backend restart, Celum resumes a job that was last updated within 30 minutes and marks an older one as abandoned.
4. A second request for the same supervisor while a job runs gets `409`.
5. Each stage writes its own audit entry, `networking-apply:` followed by the stage name, under the identity of the person who clicked.

The request needs `cilium:Install`. If it carries `envoy`, it also needs `gateway:Create` on `supervisor:{name}:gateway:*`. Celum checks this before it applies anything. To read the job, use `GET .../onboarding/networking/apply` (`cilium:GetState`).

| Action | Dashboard | API | MCP |
| - | - | - | - |
| Install Cilium, FRR-K8s and Envoy Gateway in order | **Install Networking** | `POST .../onboarding/networking/apply` | none |
| Follow that job | the step footer | `GET .../onboarding/networking/apply` | none |
| Re-apply or upgrade Cilium alone | **Re-apply chart** / **Upgrade to** on the Cilium card | `POST .../cilium/install` | none |
| Turn Cluster Mesh on or off | **Cluster Mesh** | `POST .../networking/cilium-feature` | `set_cilium_feature` |
| Install or upgrade FRR-K8s alone | **Re-apply chart** / **Upgrade to** on the BGP engine card | `POST .../frr-k8s/install` | `install_frr_k8s` |
| Install or upgrade Envoy Gateway alone | **Re-apply chart** / **Upgrade to** on the Gateway card | `POST .../gateway/envoy/install` | none |
| Check a Cilium body before applying | none | `POST .../cilium/install/validate` | none |
| Read install state | the section badges | `GET .../cilium/install/status`, `GET .../frr-k8s/status`, `GET .../gateway/envoy/install/status`, `GET .../networking/north-south/status` | `get_component_status` (`cilium-install`, `envoy-gateway`, `north-south`), `get_frr_bgp_config` (`view=status`), `get_north_south_status` |

## Secret Store

This step installs External Secrets Operator (ESO) and connects it to Vault. Credentials that later steps ask for are then stored once in Vault, not only on the cluster. The step is optional. Without it, every credential is written as a plain Secret on the cluster. If you connect a store later, Celum moves those credentials to Vault on its own. The step stays locked until Cilium reports Ready.

### External Secrets Operator

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Target Chart Version** (`chartVersion`) | Selects the ESO chart version that the button below installs | The dashboard pre-selects the newest version in the chart index. Through the API, an empty `chartVersion` installs Celum's pinned version instead | Once the step is unlocked |
| **Install Secret Store** (or **Re-apply Secret Store**) | Creates the `external-secrets` namespace, a HelmRepository resolved for this supervisor, and the HelmRelease `external-secrets`, then asks Flux to reconcile. It takes no credential | — | Disabled while the step is locked |

After the release reports Ready, the panel shows **CRDs served** and **admission webhook callable** until ESO can accept a store. The webhook comes up some minutes after the release.

**API:** `POST /api/supervisors/{name}/external-secrets/install` (`secrets:Install`). **MCP:** `install_external_secrets`.

### Connect to Vault

You paste a bootstrap AppRole. Celum uses it for one call and never stores it. The audit log redacts `secretId`.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **roleId** (`roleId`) | The role ID of an AppRole that carries the `celum-bootstrap` policy. The section shows the Vault console command that prints it | Empty | Always |
| **secretId** (`secretId`) | A secret ID for that AppRole. The command shown in the section creates one that expires after five minutes, so create it just before you connect | Empty | Always |
| **Connect to Vault** | In Vault: enables the auth mount `kubernetes-` + supervisor name. Writes the policy `celum-eso-` + supervisor name, with read access to `<kv-mount>/data/clusters/<supervisor>/*` and to any platform secret bound to this supervisor. Binds the role `celum-eso` to the `external-secrets` ServiceAccount. On the cluster: grants that ServiceAccount `system:auth-delegator`, and creates the ClusterSecretStore `vault-celum` and a canary ExternalSecret. Then moves into Vault any credential kept as a plain Secret, such as a BGP MD5 password | — | Disabled until ESO is installed and serving, and until both fields are filled. Also disabled while the supervisor's kubeconfig has no cluster CA, and without `secrets:Configure` |
| **Do it by hand instead** | Shows the same Vault commands with this supervisor's values filled in, with **Copy** and **Download CA** (the cluster CA, as `ca.crt`). Use it if you do not want to paste a bootstrap credential into a web form | Collapsed | Always |

`<kv-mount>` is `VAULT_KV_MOUNT`, which is `celum` by default. The backend must have `VAULT_ADDR` set, or Celum refuses the connection.

Two checks then run on their own. **Vault authentication** checks that ESO logged in to Vault. **Secret read** checks that ESO read the canary under this supervisor's prefix. The step is done when both checks pass.

> [!NOTE]
>
> If the backend holds no Vault credential, the step says **The backend cannot write to Vault yet**, and **Secret read** cannot pass. The step then stops blocking Monitoring. See [Environment variables](https://docs.celum.io/reference/environment-variables.md#secret-store) for `SECRET_BACKEND`, `VAULT_ROLE_ID` and `VAULT_SECRET_ID`.

**API:** `POST /api/supervisors/{name}/external-secrets/vault-bootstrap-ephemeral` with `roleId` and `secretId`, then `POST .../external-secrets/configure` (both `secrets:Configure`). The bootstrap body also takes `vaultAddr`, which the dashboard does not offer. `POST .../external-secrets/vault-bootstrap` makes the same Vault writes with a bootstrap credential kept on the backend (`VAULT_BOOTSTRAP_ROLE_ID`). The dashboard does not use it. **MCP:** `bootstrap_vault_ephemeral`, `configure_external_secrets` and `bootstrap_vault`. To run the checks again now, use `reconcile_external_secrets`. To read the runbook, use `get_external_secrets` with `view=vault-setup` or `view=ca-cert`.

## Storage

The step installs one storage backend from a catalogue, plus the StorageClass that makes it usable. [Storage overview](https://docs.celum.io/storage/overview.md) explains how to choose a backend. [Rook-Ceph](https://docs.celum.io/storage/rook-ceph.md), [LINSTOR](https://docs.celum.io/storage/linstor.md) and [NFS & Fibre Channel](https://docs.celum.io/storage/nfs-and-fibre-channel.md) cover each backend in depth. This section lists only what those pages do not.

### Storage backend

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Software-defined**, **Arrays**, **NFS** (tabs) | Narrows the backend cards to one family | **Software-defined**. If NFS or Rook-Ceph is already installed, the step opens on that family | Always |
| **Storage backend** (card) | Opens the form for that backend. **Rook-Ceph** and **NFS server** open their own panels. Every other card shows the parameters that its catalogue row declares | No card selected | You cannot select a card marked **Preview**: no cluster has installed it through Celum yet, and the API refuses it with `409`. You cannot select a card marked **Unsupported here** either: a probe found that a node is missing a required kernel module |

You can install these cards today:

| Family | Card | Installs |
| - | - | - |
| Software-defined | **Rook-Ceph** | The Rook operator and cluster charts. See [Rook-Ceph](https://docs.celum.io/storage/rook-ceph.md) |
| Software-defined | **LINSTOR + DRBD (replicated)** | Piraeus and LINSTOR, with DRBD replication. Every node needs the `drbd` and `dm_thin_pool` kernel modules loaded |
| Software-defined | **LINSTOR without DRBD (node-local)** | Piraeus and LINSTOR, with plain LVM volumes and no replication. Needs no kernel module |
| Arrays | **HPE Alletra 5000/6000 · Nimble** | The HPE CSI driver, which runs HPE's CSP for that array. Every node needs the `dm_multipath` and `dm_round_robin` kernel modules loaded; on Talos, the iscsi-tools and multipath-tools system extensions and a multipath configuration |
| Arrays | **HPE Alletra MP B10000 · 9000 · Primera · 3PAR** | The HPE CSI driver, which runs HPE's CSP for that array. Every node needs the `dm_multipath` and `dm_round_robin` kernel modules loaded; on Talos, the multipath-tools system extension and a multipath configuration |
| NFS | **NFS server** | `csi-driver-nfs` and one StorageClass. See [NFS & Fibre Channel](https://docs.celum.io/storage/nfs-and-fibre-channel.md#nfs) |

Every other card is **Preview**. To get the current catalogue, with `preview` and `unavailable` for each row, use `GET .../storage/csi/backends` or `list_storage_backends`.

> [!NOTE]
>
> Celum reads loaded kernel modules through Omni. If Omni is not configured, or a node does not answer, the card stays selectable and shows the requirement as a warning.

### Catalogue backend form

These rules apply to every card except **Rook-Ceph** and **NFS server**.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| Parameters of the row | Each parameter that the row declares, as a text field, a choice, or a disk picker. A required parameter has a `*` after its label | The row's own default, shown as a placeholder | When a card is selected. When you open the step again, the form is filled from the installed release |
| Credential parameters (for example **Array password**) | Celum writes the value to the secret store. External Secrets then syncs it into the `<backend>-credentials` Secret. The value never goes into a chart value or a StorageClass | — | **Install** is disabled, with the status **secret store not ready**, until the Secret Store step is connected. The API answers `409` for the same reason |
| **Install** \<backend> | Installs the driver. For an array row, it also creates a StorageClass with `reclaimPolicy: Delete` and `volumeBindingMode: Immediate`, and with volume expansion set to what the array supports. For a row that supports snapshots, it installs the CSI snapshot controller if absent, and adds a `<class>-snap` VolumeSnapshotClass | — | Disabled for a row with credentials when no secret store works |

The two array cards ask for these parameters:

| Card | Option | What it does | Default |
| - | - | - | - |
| **HPE Alletra** (both cards) | **Array management IP** (`backend`) \* | The array's management address. Written to the `<backend>-credentials` Secret, where the driver reads it, but shown in clear | — |
| | **Array user** (`username`) \* | Written to the `<backend>-credentials` Secret, shown in clear | — |
| | **Array password** (`password`) \* | Stored as a credential | — |

Each card names its class after its catalogue key, for example `hpe-csi-alletra-nimble`. Both cards install the driver as one HelmRelease, `hpe-csi-driver`, so a supervisor can have both. On a supervisor with Talos nodes the install uses Celum's Talos build of the driver and accepts only chart 3.3.0. A Hitachi VSP has no card: its LUNs go under LINSTOR or Ceph, as [NFS & Fibre Channel](https://docs.celum.io/storage/nfs-and-fibre-channel.md#fibre-channel) describes.

[LINSTOR](https://docs.celum.io/storage/linstor.md#installing-it) covers **LINSTOR storage pool**, **Disks**, **Replicas**, **Shared array LUN** and **Default StorageClass**. The form also asks for these parameters:

| Card | Option | What it does | Default |
| - | - | - | - |
| **LINSTOR + DRBD** | **Allow remote access** (`allowRemoteVolumeAccess`) | Lets a pod use a volume whose data is on another node. RWX block volumes and KubeVirt live migration need it | `true` |
| | **Shared pool name** (`sharedPool`) | Names the LINSTOR pool, the LVM volume group on the shared LUN, and the StorageClass `linstor-<name>` | `san` |
| | **Default StorageClass** (`defaultClass`) | One of **None**, **Local (replicated NVMe)** and **Shared (array LUN)**. **Shared** is hidden until every node can see a Fibre Channel LUN | **None** |
| **LINSTOR without DRBD** | **Layers** (`layerList`) \* | `storage` alone gives a plain LVM volume with no DRBD device | `storage` |

**Disks** and **Shared array LUN** are pickers over the live disk inventory. The **Shared array LUN** picker lists only LUNs that every node can see. A LUN that some nodes cannot see appears, but you cannot select it.

> [!WARNING]
>
> LINSTOR does not erase disks when it installs. If a selected disk still has a filesystem or an old signature on it, press **Erase now** in the disk picker before **Install**. To erase every selected disk, turn on **Also erase disks that look empty**. Erasing cannot be undone. It needs `storage:InstallDriver`, the same permission as the install.

**API:** `GET /api/supervisors/{name}/storage/csi/backends` (`storage:GetStatus`) lists the catalogue. `POST .../storage/csi/{backend}/install` (`storage:InstallDriver`) installs a row, with the body `{"params": {...}}`. `POST .../storage/disks/wipe` (`storage:InstallDriver`) erases disks. **MCP:** `list_storage_backends`, `install_storage_backend`, `get_storage_backend_status`, `get_storage_disks`. No tool erases disks.

### Rook-Ceph

[Rook-Ceph](https://docs.celum.io/storage/rook-ceph.md#installing-on-local-disks) covers the disk inventory, the wipe, the SAN device class and the cluster shape. The panel also has these options:

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Monitor Count** (`monCount`) | Size of the Ceph monitor quorum, from 1 to 7. It cannot be more than the number of OSD hosts | `3` | Always |
| **Replica Count** (`replicaSize`) | Copies of each object in the block pool, from 1 to 5 | `3` | Always |
| **Failure Domain** (`crushFailureDomain`) | `host`, `rack` or `zone` | `host` | Always |
| **Public Network**, **Cluster Network** (`publicNetworkCidr`, `clusterNetworkCidr`) | Where Ceph client traffic and OSD replication run. The pickers list the nodes' layer-3 networks | On a new install, the subnet of the interface you tagged **storage** in the Networking step | Always. If no networks are detected, these are text fields for a CIDR |
| **CPU Request**, **Memory Request**, **Memory Limit** (`osdCpuRequest`, `osdMemoryRequest`, `osdMemoryLimit`) | Resources for each OSD | `2`, `8Gi`, `10Gi` | Always |
| **Ceph image** (`cephImage`) | The Ceph container image. Rook applies a change as an upgrade, and refuses a downgrade | Empty. An existing cluster keeps the image it runs. A new cluster gets the image Celum pins | Always |
| **ceph.conf settings** (`cephConfig`), **Add setting** | Rows of section, key and value, as `ceph config set` takes them. They are written to the CephCluster, so they survive a rebuild. Celum refuses `public_network`, `cluster_network`, `osd_memory_target`, `osd_crush_chooseleaf_type`, `fsid`, `mon_host` and `mon_initial_members`, because a field above or Rook already sets them | None | Always |
| **Block Storage (RBD)** (`createRBD`) | Creates the `ceph-block` StorageClass | On | Always |
| **Shared Filesystem (CephFS)** (`createCephFS`) | Creates the `ceph-filesystem` StorageClass, for ReadWriteMany volumes | On | Always |
| **Object Storage (S3)** (`createObjectStore`) | Creates an S3-compatible object gateway and the `ceph-bucket` StorageClass | Off | Always |
| **SAN Block Pool** (`createSANPool`) | Creates the `ceph-san-block` StorageClass on the selected Fibre Channel LUNs | On when SAN LUNs are selected | Only when SAN LUNs are selected or the pool exists |
| **SAN Replica Count** (`sanReplicaSize`) | Copies of each object in the SAN pool, from 1 to 3 | `2` | With **SAN Block Pool** |
| **Ceph Dashboard** (`enableDashboard`) | Enables the Ceph management dashboard. It does not expose it | On | Always |
| **Prometheus Monitoring** (`enableMonitoring`) | Exposes Ceph metrics to the platform's monitoring stack | On | Always |
| **Automatic Device Discovery** (`enableDiscovery`) | Runs Rook's discovery daemon, which keeps watching for device changes on the nodes | On | Always |
| **Also erase disks that look empty** | Erases every selected disk during the apply, not only disks with a detected signature | Off | When at least one disk is selected |

The step's apply stays disabled until you select at least one OSD disk. On a re-apply, Celum asks you to confirm any change that reduces capacity or redundancy. These changes are: turning off a pool, lowering **Replica Count**, changing **Failure Domain**, or changing a network.

**API:** `POST /api/supervisors/{name}/storage/rook/install` (`storage:InstallDriver`). **MCP:** no tool installs Rook-Ceph, so use the API. `get_storage_disks` and `get_ceph_discovery` read the disk inventory.

### NFS server

[NFS & Fibre Channel](https://docs.celum.io/storage/nfs-and-fibre-channel.md#nfs) covers the fields. These details differ from what that page says or implies:

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Default StorageClass** (`isDefault`) | Marks the class as the cluster default | On for a supervisor with no NFS configuration. When you open the step again, it shows the saved value | Always |
| Mount options (`mountOptions`) | The dashboard does not let you edit them. It shows them as **Managed mount options** and always sends `nfsvers=3`, `nolock`, `hard`, `noatime`, `nconnect=4`. The API accepts another list | The five options above | API only |
| **Storage Gateway (next-hop)** (`gateway`) | The next hop for a `/32` route from the nodes to the NFS server, over the storage interface | The first host of the storage subnet | Only when an interface is tagged **storage** and the server is outside that subnet. Celum applies the route through Omni, so without Omni it is skipped |
| **Run smoke test** | Creates a 1 GiB PVC on the class, waits for it to bind, writes to it, and deletes it. If the test fails, it shows the claim's events | — | Only when the NFS driver is installed and Ready, and the class has a name |

> [!NOTE]
>
> The **NFS server** card does not install the CSI snapshot controller. Rook-Ceph and the catalogue backends do. On a supervisor whose only storage is NFS, install the controller with `POST .../snapshot/install` (`virt:Install`). No dashboard control or MCP tool does this.

**API:** `POST /api/supervisors/{name}/storage/nfs/install` (`storage:InstallDriver`) and `POST .../storage/nfs/test-pvc` (`storage:Test`). **MCP:** no tool installs the NFS driver, so use the API. `test_storage_class` tests any class.

### Test a class from any backend

Only the **NFS server** card has a test button. For any other backend, use the [test-PVC diagnostic](https://docs.celum.io/storage/classes-and-profiles.md#the-test-pvc-diagnostic): `POST .../storage/test-pvc` (`storage:Test`), or `test_storage_class`.

## Certificates

The step installs cert-manager and one issuer with a single apply. [Certificates](https://docs.celum.io/platform-health/certificates.md) explains which issuer signs a certificate and how to manage issuers later.

### Issuer type

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Let's Encrypt**, **Self-signed**, **CA (import)**, **AD CS**, **Appliance CA** (`issuerType`: `letsencrypt`, `selfsigned`, `ca`, `adcs`, `appliance`) | Selects the issuer that the apply creates, and the fields below | **Let's Encrypt**. On a supervisor with no issuer, the step opens on **Appliance CA** if two things are true: the secret store can sign under the appliance CA, and Celum may grant this supervisor a signing role | Always. **Appliance CA** moves to the first tab when the secret store can sign |

### cert-manager controller

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Gateway API support** (`enableGatewayAPI`) | Sets `config.enableGatewayAPI` in the cert-manager chart. cert-manager then creates certificates for Gateway listeners annotated `cert-manager.io/cluster-issuer`. The shared gateway needs it | On | Always |
| **Target Chart Version** (`certManagerChartVersion`) | The cert-manager chart version. Empty means the version Celum pins | The pinned version | Always. **AD CS** adds a panel for `adcsIssuerChartVersion`, and **GoDaddy** adds one for `godaddyWebhookChartVersion` |

### Let's Encrypt

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **ACME account email** (`acmeEmail`) | The contact address registered with Let's Encrypt. Expiry notices go to it | — | Required on the first install |
| **Certificate domain** | Shows `*.<supervisor domain>`, read from **Settings → Domains**. Gateway routes use the same domain | Derived | Read-only. The first install is refused if the supervisor has no domain |
| **Override the certificate domain** (`certDomainOverride`), **Subdomain**, **Base domain** (sent as `certDomain`) | Issues certificates for another zone. **Base domain** lists the domains in **Settings → Domains**. Host names outside the certificate's zone are not served over TLS | Off | **Subdomain** and **Base domain** appear only when the override is on |
| **ACME server** (`acmeServer`) | `prod` or `staging`. Use `staging` while you test | `prod` | Always |
| **Provider** (`dnsProvider`) | Selects which DNS API answers the DNS-01 challenge. **GoDaddy** installs the `cert-manager-webhook-godaddy` chart. **Cloudflare** uses cert-manager's built-in solver | **GoDaddy** | Always |
| **Webhook group name** | Shows `acme.<certificate domain>`. Celum uses it for both the webhook and the issuer | Derived | Read-only. **GoDaddy** only |
| **GoDaddy API token** (`godaddyApiToken`) | The GoDaddy API key and secret, as `KEY:SECRET` | — | **GoDaddy** only. Required on the first install. On a re-apply, leave it empty to keep the stored token |
| **Cloudflare API token** (`cloudflareApiToken`) | An API token with **Zone → DNS → Edit** permission on the certificate domain's zone | — | **Cloudflare** only. Required on the first install. On a re-apply, leave it empty to keep the stored token |

### Self-signed

This tab has no fields. The apply creates three things:

* a bootstrap ClusterIssuer, `selfsigned-bootstrap`;
* a root CA certificate, valid for 10 years;
* a CA ClusterIssuer that signs from that root.

The root's common name is the supervisor domain. If the supervisor has no domain, it is `k8s-gate self-signed root`. The key pair is stored in the `cert-manager-ca-keypair` Secret.

### CA (import)

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **CA certificate (PEM)** (`caCert`) | Your CA certificate | — | Required on the first install. On a re-apply, leave it empty to keep the stored CA |
| **CA private key (PEM)** (`caKey`) | Your CA's private key. The form never reads it back | — | As above |

The pair is stored as the `cert-manager-ca-keypair` TLS Secret.

### AD CS

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Enrollment URL** (`adcsUrl`) | The AD CS Web Enrollment endpoint, for example `https://ca.example.com/certsrv` | — | Required on the first install |
| **Certificate template** (`adcsTemplate`) | The AD CS template to enroll against | `WebServer` | Always |
| **Username** (`adcsUsername`) | The NTLM user, as `DOMAIN\user` or `user@domain` | — | Required on the first install. On a re-apply, leave it empty to keep the stored credential |
| **Password** (`adcsPassword`) | The NTLM password | — | As **Username** |
| **CA bundle (PEM)** (`adcsCaBundle`) | Verifies the TLS certificate of the enrollment endpoint | — | Optional |
| **Skip TLS verification** (`adcsInsecure`) | Turns off verification of the enrollment endpoint's TLS certificate | Off | Disabled while **CA bundle (PEM)** has a value |

The apply installs the `adcs-issuer` chart and creates a `ClusterAdcsIssuer`.

### Appliance CA

This tab has no input fields. It shows the names this supervisor may get certificates for: its domain and the zone declared in the DNS Publishing step, each with its subdomains. It also shows the chain that every certificate carries. cert-manager sends each request to the appliance's OpenBao, which signs it. The CA key never enters the cluster.

The apply needs all of the following. If one is missing, the apply fails with an error that names it.

* The Secret Store step is done. cert-manager signs in through the auth mount that step creates.
* `VAULT_ADDR` is set on the backend.
* The bootstrap credential is set: `VAULT_BOOTSTRAP_ROLE_ID` and `VAULT_BOOTSTRAP_SECRET_ID`.
* `VAULT_BOOTSTRAP_ALLOW` covers this supervisor.
* The supervisor has a domain, or a zone declared in the DNS Publishing step.

In OpenBao, the apply writes a PKI role, a sign-only policy and a Kubernetes-auth role. Certificates are valid for at most 90 days. In the cluster, the apply creates the `celum-pki-issuer` ServiceAccount, the RBAC it needs, and the ClusterIssuer. The PKI mount is `VAULT_PKI_CLUSTERS_MOUNT`, which is `pki-clusters` by default. See [Environment variables](https://docs.celum.io/reference/environment-variables.md#secret-store).

### ClusterIssuer

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Issuer name** (`issuerName`) | The name of the ClusterIssuer. Gateway and Ingress listeners refer to it | `letsencrypt-prod` (Let's Encrypt), `ca-issuer` (Self-signed and CA import), `adcs-cluster-issuer` (AD CS), `celum-appliance` (Appliance CA) | Always. It goes back to the type's default when you switch tabs |

If the Secret Store step is connected, Celum writes the DNS tokens, the imported CA and the AD CS credential to the secret store. If not, it writes them as Secrets in the `cert-manager` namespace.

On a re-apply, Celum asks you to confirm some changes before it sends them: another issuer type, another ACME email, another ACME server, another DNS provider, or a new issuer name. Certificates that are already issued keep serving. Their renewal moves to the new issuer. With a new issuer name, renewal stops.

**API:** `POST /api/supervisors/{name}/certs/install` (`certs:Install`) installs cert-manager and the selected issuer in one call. Later, `POST .../certs/issuers` (`certs:Install`) adds more issuers. **MCP:** no tool installs cert-manager or an issuer, so use the API. `list_cluster_issuers` and `get_certificates` read the result. `reissue_certificates` moves existing certificates to the default issuer.

## DNS Publishing

The step installs external-dns into the `external-dns` namespace as a Flux HelmRelease. external-dns reads two fixed sources: the supervisor's Gateway API HTTPRoutes, and Services that carry the external-dns host name annotation. Routes published from tenant clusters get their records through the second source. The step is optional. [Publish it on a public name](https://docs.celum.io/get-started/publish-it.md) shows what a missing record looks like. Under [Settings → Domains](https://docs.celum.io/administration/settings.md#domains), you register a PowerDNS server once, for every supervisor.

### DNS backend

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **provider** (`provider`) | Selects where external-dns writes records. `coredns` is CoreDNS with the etcd plugin. `rfc2136` is BIND or Windows DNS. `pdns` is PowerDNS. `other` is any other external-dns provider, configured through **Advanced values** | `coredns`, or the provider in the supervisor's declared zone | Always |
| **ETCD\_URLS** (`etcdUrls`) | Comma-separated etcd endpoints of the CoreDNS etcd plugin. With `coredns`, external-dns runs with `registry: noop` and writes no ownership TXT records | — | **provider** is `coredns` |
| **Server host**, **Port**, **Zone** (`rfc2136Host`, `rfc2136Port`, `rfc2136Zone`) | The server that accepts dynamic updates, and the zone to update | Port `53` | **provider** is `rfc2136` |
| **Unsecured dynamic update (no TSIG)** (`rfc2136Insecure`) | Sends updates without a TSIG signature. Use it only on a zone that accepts unauthenticated updates | Off | **provider** is `rfc2136` |
| **TSIG key name**, **TSIG algorithm** (`rfc2136TsigKeyname`, `rfc2136TsigAlg`) | The key that signs each update. The algorithm is `hmac-sha256`, `hmac-sha512` or `hmac-sha1` | `hmac-sha256` | **provider** is `rfc2136` and TSIG is on |
| **TSIG secret** (`rfc2136TsigSecret`) | Write-only. Celum stores it in the Secret `external-dns-provider-credentials`, never in the HelmRelease values. If the Secret Store step is connected, the Secret comes from Vault. To keep the stored secret, leave the field empty | — | **provider** is `rfc2136` and TSIG is on. Required on the first install |
| **PowerDNS server** (`dnsProviderName`) | Binds this supervisor to a PowerDNS server registered for the platform. The key stays in the secret store at `platform/dns/<name>`, and this supervisor's External Secrets syncs it into `external-dns-provider-credentials`. To type a server URL and key instead, pick **This supervisor only** | The provider named in the declared zone, else **This supervisor only** | **provider** is `pdns` and at least one server is registered. Binding needs `dnsprovider:Use` on that provider and a connected Secret Store |
| **PowerDNS API URL** (`pdnsServer`) | The API root that the clusters can reach. Celum adds `/api/v1/servers/localhost`. Celum refuses a URL that has a path, a query, a fragment or `user:password@`. It also refuses a loopback or `localhost` host, and a single-label host name | The server in the declared zone | **provider** is `pdns` with **This supervisor only** |
| **API key** (`pdnsApiKey`) | Write-only. Celum checks the key against the server before it writes anything, then stores it like the TSIG secret. To keep the stored key, leave the field empty | — | **provider** is `pdns` with **This supervisor only**. Required on the first install |
| **Private CA (optional)** (`pdnsCaCert`) | The PEM of a private CA that signed the PowerDNS certificate, for when Celum does not trust that CA. external-dns then verifies the server with this CA alone, from the ConfigMap `external-dns-pdns-ca`. To drop a CA you entered before, enter `auto` | Empty. external-dns then gets the CA that Celum verified the server with (for example, an appliance's own CA). For a public certificate or plain HTTP, it gets no CA | **provider** is `pdns` |
| **Provider name** (`provider`) | Any external-dns provider name, for example `aws` or `cloudflare`. Supply its settings and credentials in **Advanced values** | — | **provider** is `other` |
| **policy** (`policy`) | `sync` deletes a record when its route goes away. `upsert-only` creates and updates records, and never deletes one | `sync` | Always |

#### When a zone already holds another owner's records

With PowerDNS, the check lists each zone that holds external-dns records from a different owner, usually a deleted cluster. external-dns does not overwrite those records, so the names keep their old addresses. The first time the step finds such records, it stops and asks you to choose one of the options below. To continue with your choice, press the action again.

| Option | What it does |
| - | - |
| **Overwrite conflicting records** (`pdnsZoneMode: overwrite`) | Deletes only the other owner's external-dns records: their TXT record and the A, AAAA or CNAME record that goes with it. This supervisor then creates them again. Other records stay. Pre-selected when conflicts are found |
| **Full reset** (`pdnsZoneMode: reset`) | Deletes the zone and creates it again. Every record in the zone is lost, not only the external-dns records |
| **Leave as-is** (`pdnsZoneMode: keep`) | Changes nothing. This is the default |

### Zones (domain filters)

| Option | What it does | Default | When it is available |
| - | - | - | - |
| Zone chips (`domainFilters`) | external-dns writes only names under the zones you select. The chips offer the declared zone, this supervisor's own zone (`<subdomain>.<domain>`), the registered domains, and any zone already selected. With PowerDNS, Celum creates each selected zone that does not exist yet, on every apply, because the PowerDNS provider does not create zones | The declared zone, else this supervisor's own zone | Always |
| **Add a zone or subdomain** | Adds a zone that is not a registered domain, for example `apps.example.com` | — | Always |

> [!WARNING]
>
> An empty list lets external-dns write into every zone it can reach. If the list does not cover the host names you publish, the release is Ready but writes no records.

The records carry an owner ID (`txtOwnerId`). The step shows it, but you cannot change it there. Over the API, an empty `txtOwnerId` takes the owner from the declared zone. If there is none, it takes the owner from the running release, and then the supervisor name. A new owner ID orphans every record written under the old one.

### Advanced values (optional)

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **values.yaml overlay** (`extraValues`) | Raw Helm values, deep-merged over the values Celum renders. Each leaf you set wins. Use it for a provider without typed fields, or for extra arguments. Celum refuses YAML that it cannot parse | Empty | Always |
| Chart version (`chartVersion`) | The external-dns chart version, from the release panel | The version Celum pins | Always |

### Run the step

**Test connection** checks a PowerDNS server: the URL rules, the key, and the zone conflicts. It changes nothing. For any other provider, it reports that there is nothing to probe. The step's action runs the same check, then applies these objects in order: the `external-dns` namespace, the provider Secret, the CA ConfigMap (PowerDNS only), the HelmRepository, and the HelmRelease. Celum then creates any missing PowerDNS zones. Last, it writes the zone, owner and server it used back to the supervisor's `NuviraClusterConfig` (`spec.dns`).

**API:** `POST /api/supervisors/{name}/external-dns/validate` (`dns:GetStatus`), then `POST .../external-dns/install` (`dns:Install`). The install body replaces the release values. So read `desired` from `GET .../external-dns/status`, change it, and send it back. **MCP:** `validate_external_dns`, `install_external_dns`, `get_external_dns_status`. The MCP tool leaves out `pdnsZoneMode`, because reclaiming a zone is a decision for the dashboard.

### Declare the zone without installing

Before the step runs, you can declare which zone a supervisor owns and which server is authoritative for it. An install fills every empty field from this declaration. The step shows the declared zone, but has no field to change it.

**API:** `PUT /api/supervisors/{name}/onboarding/dns-zone` (`dns:Install`). **MCP:** `set_onboarding_dns_zone`. To read it, use `get_onboarding_prerequisites` with `view=dns-zone`. **Dashboard:** none.

### Remove the records

When you delete a supervisor under **Settings → Kubeconfigs**, the dialog offers two choices. **Remove PowerDNS records** deletes the records owned by the supervisor's name in each filtered zone. **Delete zone** deletes those zones. Neither one does anything on a supervisor that did not publish through PowerDNS.

**API:** `POST /api/supervisors/{name}/external-dns/purge?mode=records` or `?mode=zone` (`dns:Install`). **MCP:** none. This is deliberate: no MCP tool deletes data.

## Monitoring

The step installs kube-prometheus-stack (Prometheus, Alertmanager and Grafana) as a Flux HelmRelease in the `monitoring` namespace. With it come the platform ServiceMonitors, the Celum Grafana dashboards, and routes for Grafana, ClickHouse and the Prometheus push path on the shared Gateway. The same step also installs Loki, Alloy and the ClickHouse flow store. Each one has its own toggle, and each is on by default. The step waits for Networking, Storage and Certificates, and for the Secret Store gate. [Monitoring](https://docs.celum.io/platform-health/monitoring.md) and [Logs](https://docs.celum.io/platform-health/logs.md) cover the running stack.

### Storage and retention

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **StorageClass** (`storageClassName`) | The class for the Prometheus and Grafana PVCs | The supervisor's default StorageClass, else its first one | Always |
| **Prometheus storage (GiB)** (`prometheusStorage`) | Size of the Prometheus PVC, from 1 to 2000 | `20` | Always |
| **Grafana storage (GiB)** (`grafanaStorage`) | Size of the Grafana PVC, from 1 to 500 | `5` | Always |
| **Retention (days)** (`retentionDays`) | How long Prometheus keeps samples, from 1 to 365 | `7` | Always |
| Chart version (`chartVersion`) | The kube-prometheus-stack chart version, from the release panel | The version Celum pins | Always |

### Alerts

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Send this supervisor's alerts to Slack** (`alertRouting`) | On: Celum writes the Secret `celum-alert-webhook` into `monitoring` and points the supervisor's Alertmanager at this Celum. Celum posts warning and critical alerts to the Slack channel, plus `KubeNodePressure`, `KubeNodeEviction` and `KubeletTooManyPods`. `Watchdog`, `InfoInhibitor` and every other `info` alert go nowhere. Off: Alertmanager sends every alert to a null receiver | The supervisor's current routing. Celum writes nothing until you change the toggle | Disabled unless this Celum has both `ALERT_WEBHOOK_URL` and `ALERT_WEBHOOK_TOKEN` set |

Alertmanager groups alerts by cluster, namespace and alert name. It waits 30 seconds before the first message, and repeats an unresolved group every 12 hours. A critical alert suppresses the warning and info alerts of the same name. A crash-looping pod suppresses its not-ready and container-waiting alerts.

> [!NOTE]
>
> Set `ALERT_WEBHOOK_URL` on only one Celum for each set of supervisors. If two Celum installations both set it, each one takes the other's alerts. Celum posts through its Slack app, so you must configure [Slack notifications](https://docs.celum.io/platform-health/slack-notifications.md). Without Slack, Celum accepts the alerts and drops them. See [Environment variables](https://docs.celum.io/reference/environment-variables.md#integrations).

### Grafana SSO

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Enable SSO** (`enableSso`) | Turns on Grafana's OIDC login (`auth.generic_oauth`). The login button reads **Celum SSO**, or **Celum SSO (Dex)** when it goes through Dex. Off: Grafana keeps only its own login | Off | Always |
| **Sign in through** (`ssoProvider`) | **Platform client** (`platform`): Celum's identity provider, with Grafana's own client, named `grafana`. **Dex broker** (`dex`): the Dex at `DEX_ISSUER_URL`, with the static client `grafana`. **Backend's client** (`direct`): Celum's identity provider, with Celum's own client (`OIDC_CLIENT_ID`). For this mode, the provider must accept Grafana's redirect for that client | The first mode this Celum can serve, in this order: Platform client, Dex broker, Backend's client | When SSO is on. **Platform client** needs Celum's OIDC and the platform `grafana` client secret in Settings → SSO. **Dex broker** needs `DEX_ISSUER_URL` and the Dex secret in Settings → SSO. **Backend's client** needs Celum's OIDC |
| **Platform `grafana` client secret**, **Dex `grafana` client secret** or **OIDC\_CLIENT\_SECRET** (`oidcClientSecret`) | Write-only. Celum writes it, with the client ID, to the Secret `oidc-client-credentials` in `monitoring`. If the Secret Store is connected, the Secret comes from Vault. Leave it empty to use the secret from [Settings → SSO](https://docs.celum.io/administration/settings.md#sso) for that mode, or to keep the stored Secret | — | When SSO is on. The step refuses to apply if the field is empty, the supervisor holds no secret, and Settings → SSO has none for the mode |
| **Grafana root URL** (`grafanaRootUrl`) | Grafana's public URL. The identity provider must accept `<root>/login/generic_oauth` as a redirect URI | `https://grafana.<subdomain>.<domain>` of the supervisor | When SSO is on |
| **Admin group name** or **Admin group ID** (`adminGroupId`) | Members of this group sign in as Grafana Admin. Everyone else signs in as Viewer. Platform client and Dex broker match group names. Backend's client matches the group IDs the provider sends, such as Entra object IDs | Empty: everyone is Viewer | When SSO is on |
| **Allowed group names** or **Allowed Entra groups** (`allowedGroups`) | Comma-separated groups whose members may sign in | Empty: any user the provider authenticates | When SSO is on |

With **Platform client** or **Backend's client**, if a private CA signed the identity provider's certificate, Celum gives Grafana that provider's root CA, in the Secret `grafana-oidc-ca`.

### Logs

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Enable centralized logs** (`enableLoki`) | On: the step installs Loki (SingleBinary, filesystem storage) and adds it to Grafana as a datasource. Celum also publishes Loki's push path, `/loki/api/v1/push`, on the shared Gateway, which guest clusters need to ship logs. Off: the next apply removes the datasource, but does not uninstall Loki | On | Always |
| **StorageClass** (`storageClassName`) | The class for the Loki PVC | The supervisor's default StorageClass, else its first one | When logs are on |
| **PVC size (GiB)** (`storageSize`) | Size of the Loki PVC, from 5 to 2000 | `50` | When logs are on |
| **Retention period** (`retentionPeriod`) | Not applied. Loki keeps every project's and cluster's logs until its volume is full, and deletes only the logs from before it was multi-tenant (tenant `fake`), after 24 hours. A value other than `0s` adds a `Loki retention` row to the apply that says so | `168h` in the form. The step reads back `0s` once Loki has been applied | When logs are on |
| **Ship pod logs to Loki** | On: the step installs Grafana Alloy as a DaemonSet. Alloy reads every pod's container output and pushes it to Loki. A pod in a project namespace goes to that project's Loki tenant, every other pod to the supervisor's | On | Disabled while **Enable centralized logs** is off |
| **Include namespaces** (`includeNamespaces`) | Comma-separated namespaces to collect from | Empty: every namespace | When Alloy is on |
| **Exclude namespaces** (`excludeNamespaces`) | Comma-separated namespaces to skip. Your list replaces the default list | `kube-system,flux-system,cilium-secrets,tetragon-system` | When Alloy is on |
| **Loki push URL (override)** (`lokiEndpoint`) | Another Loki to push to. Celum adds `/loki/api/v1/push`. That Loki is multi-tenant too, and its own Celum and Grafana do not read the tenants this Alloy writes under | `http://loki.monitoring.svc.cluster.local:3100` | When Alloy is on |
| Chart versions (`chartVersion`) | The Loki and Alloy chart versions, from each release panel | The versions Celum pins | When logs are on, and when Alloy is on |

Loki is always multi-tenant, with a tenant for each project and one for the supervisor; [Logs](https://docs.celum.io/platform-health/logs.md#tenants) lists where each writer's logs land. The Loki install no longer takes `auth`. On a supervisor that still runs Loki's retired NGINX auth, a Loki apply turns that gateway off, deletes the Secret `monitoring/loki-credentials` and each namespace's `alloy-loki-auth` delivery of it, and points the Loki route at the `loki` Service. The route then requires each project's push credential once every guest sends it; see [The push credential](https://docs.celum.io/platform-health/monitoring.md#the-push-credential).

> [!WARNING]
>
> The first Loki apply on a supervisor deletes, for good, the logs Loki kept from before it was multi-tenant (tenant `fake`). See [Logs from before tenancy](https://docs.celum.io/platform-health/logs.md#logs-from-before-tenancy).

### ClickHouse (flow observability storage)

This is the store that every Security view reads, and [Flow Telemetry](#flow-telemetry) needs it. With **Enable ClickHouse** on, the Monitoring apply installs the Altinity clickhouse-operator into `clickhouse-system`. When the operator is Ready, the step applies the ClickHouseInstallation into the `security` namespace, without another click. The Monitoring apply also publishes ClickHouse on the shared gateway as `clickhouse.<your domain>`. It skips that route, with a note in the install log, if the supervisor has no domain, no shared gateway or no ClusterIssuer.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Enable ClickHouse** | Includes ClickHouse in the Monitoring apply | On | Always. While the operator's release exists, the toggle reads as on |
| **clickhouse-operator** version (`chartVersion`) | Sets the Altinity operator chart version | The version Celum pins | When ClickHouse is on |
| **Storage class** (`storageClass`) | Where each replica's data volume is created. The apply refuses a class that the supervisor does not have | The supervisor's default class | When ClickHouse is on |
| **Storage size (GiB)** (`storageSizeGi`) | The size of each replica's data volume, from 5 to 10240. Flow rows are kept for 14 days | `100` | When ClickHouse is on |
| **Replicas per shard** (`replicas`) | Full copies of the data. `1` has no high availability | `1` | When ClickHouse is on. The form offers `1` to `3`. The API accepts `1` to `5` |
| **CHI name** (`chiName`) | The name of the ClickHouseInstallation | `hubble-flows` | When ClickHouse is on. Keep `hubble-flows`: Flow Telemetry writes to `clickhouse-hubble-flows.security.svc`, and the published route looks for that Service |
| **Enable /metrics on :9363** (`metricsEnabled`) | Turns on ClickHouse's Prometheus endpoint on port `9363` | On in the form. Off when the API request leaves it out | When ClickHouse is on |
| **Create ServiceMonitor** (`serviceMonitor`) | Creates the ServiceMonitor for the operator's metrics exporter (port `ch-metrics`, :8888), which collects the ClickHouse servers' metrics. Turning it off removes nothing. The servers' own :9363 is scraped by the Flow Telemetry apply | On | When ClickHouse is on |

The API also takes `shards`, from `1` to `8`, default `1`. The form does not show it.

### Advanced (Prometheus and ServiceMonitors)

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Grafana admin password** (`adminPassword`) | Sets the password of Grafana's `admin` user. Leave it empty to keep the current password | A random password. With the Secret Store connected, Celum generates a 24-character password on the first install, keeps it in Vault, and gives it to Grafana as the Secret `grafana-admin` | Always |
| **Prometheus scrape interval** (`scrapeInterval`) | Prometheus's global scrape interval | `30s` | Always |
| Platform ServiceMonitors (`enabledServiceMonitors`) | The ServiceMonitors that this step applies: Ceph mgr, Ceph exporter, Cilium agent, Cilium operator, Hubble, cluster-agent, SMART (disks), NVIDIA DCGM, LINSTOR / DRBD. Ticking **SMART (disks)** or **NVIDIA DCGM** also installs that exporter through Flux. An apply removes each unticked ServiceMonitor that Celum created | Every row except **SMART (disks)** and **NVIDIA DCGM** | A row is greyed out when its component is not installed |

Components installed after this step (KubeVirt, Cluster API, Kamaji, Tetragon) apply their own ServiceMonitors.

### Run the step

The step's action applies kube-prometheus-stack only if one of its settings or the alert toggle changed. It then applies Loki, Alloy and ClickHouse together, each one only if it is on. If the kube-prometheus-stack apply fails, the rest does not run. The kube-prometheus-stack apply removes the `prometheus-write` NodePort Service (`31090`) that earlier releases wrote, and ends with the push-credential rows: every project's telemetry push credential, the push routes' requirement of it, the policy that leaves those routes the only way to Prometheus and Loki from outside `monitoring`, and the closing of the Loki port `8080` in project policies. When kube-prometheus-stack has nothing to change, the action runs the NodePort removal and those rows on their own, last. [Monitoring](https://docs.celum.io/platform-health/monitoring.md#the-push-credential) describes them.

**API:** `POST /api/supervisors/{name}/monitoring/install`, `.../monitoring/loki/install`, `.../monitoring/alloy/install`, `.../clickhouse/install`, then `.../clickhouse/chi/apply` when the operator is Ready. `.../monitoring/telemetry-credentials` runs only the Prometheus route, the NodePort removal and the push-credential rows. All of them need `monitoring:Install`. Each install body replaces that release's values. Read `desired` from the matching status first, change it, and send it back. `alertRouting` is the exception: leave it out to keep the current routing. **MCP:** `install_monitoring`, `install_loki`, `install_alloy`, `install_service_monitors`, `install_exporter`, and `get_component_status` to read the state. No tool installs ClickHouse, so use the API. No tool runs the push-credential rows alone: `install_monitoring` runs them at the end of its apply. `get_component_status` with `component=clickhouse` reads its state, and `get_flow_store_instance` describes the installation.

## Virtualization

This step installs KubeVirt and, if you keep it on, CDI. On a new supervisor, both come from the platform's own charts, as the HelmReleases `kubevirt/virt-kubevirt` and `cdi/virt-cdi`. The step also does these things:

* labels the `kubevirt` and `cdi` namespaces as privileged;
* pins the KubeVirt and CDI control-plane pods (not the VMs) to control-plane nodes;
* adds two KubeVirt dashboards to Grafana.

The step unlocks when Cilium is Ready. For what you do with it afterwards, see [Image library](https://docs.celum.io/vms/image-library.md) and [Create a VM](https://docs.celum.io/vms/create.md).

### Release panels

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **kubevirt/virt-kubevirt** version (`kubevirtVersion`) | Sets the KubeVirt chart version. If the supervisor has its own registry, the apply first copies that version's images into it. The first apply of a new version therefore waits for the copy | Empty: the version Celum pins | Always. Once installed, the panel offers **Re-apply** and an upgrade to a newer version. If you pick a version older than the installed one, the step asks you to confirm, because KubeVirt does not support a downgrade across minor versions |
| **cdi/virt-cdi** version (`cdiVersion`) | Sets the CDI chart version | Empty: the version Celum pins | While **CDI** is on |

### Components

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **KubeVirt** | The virtualization control plane | On | Always on. You cannot turn it off |
| **CDI (Containerized Data Importer)** (`cdiEnabled`) | Installs CDI, which imports VM disk images from a URL, S3 or a container disk. The image library needs it | On | Always. On an installed supervisor, turning it off asks for confirmation and removes nothing: the apply skips CDI. While CDI is installed, the form shows it as on again |

### Feature gates

This section is collapsed. Open it only to change a gate.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Feature gates** (`featureGates`) | Sets the KubeVirt feature gates. The gates are grouped as **Lifecycle & Migration**, **Storage & Backup**, **Networking**, **Compute & Performance**, **Hardware passthrough**, **Security & Encryption** and **Observability & Extensions** | The gates marked **default** | Gates marked **always on** (generally available) or **deprecated** have no switch. On an installed supervisor, turning off a **default** gate asks for confirmation, because running VMs can lose snapshots, hot-plug and volume expansion |

Two gates need care on Talos:

* `KubevirtSeccompProfile` is a default gate. The `passt` VM network mode needs it on Talos. If you turn it on for an installed supervisor, restart `virt-handler` afterwards. Also set the node sysctl `user.max_user_namespaces` above `0` (see [Run a VM](https://docs.celum.io/get-started/run-a-vm.md)).
* `PersistentReservation` is marked **Talos-incompatible**. Leave it off on Talos: its sidecar needs a writable `/etc/multipath`, and without one `virt-handler` crash-loops.

### KubeVirt controller

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Host device passthrough** (`hostDevicesEnabled`) | Adds an empty `permittedHostDevices` list (PCI, mediated and USB devices) to the KubeVirt resource. Celum has no control to add devices to the list | Off | Always shown. Has an effect only on chart-delivered KubeVirt (see the warning below) |

### CDI configuration

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Upload proxy URL** (`cdiUploadProxyOverride`) | Sets CDI's `uploadProxyURLOverride`. Set it only if you publish the CDI upload proxy on an address of your own, for example `https://cdi-uploadproxy.example.com` | Empty: CDI's own `cdi-uploadproxy` Service | While **CDI** is on. Has an effect only on chart-delivered KubeVirt |

> [!WARNING]
>
> A supervisor whose KubeVirt was installed from GitHub release manifests stays on that delivery path, and the step says so above the panels. On that path, the apply fetches the manifests from `github.com`, so it needs internet access. It also ignores **Host device passthrough** and **Upload proxy URL**. Moving such a supervisor to the charts replaces what owns the KubeVirt operator. That is a migration, not a re-apply.

If the KubeVirt or CDI resource stays in a phase other than `Deployed` or `Deploying`, the step shows **Operator stuck** with **Restart operator + capture logs**. This action restarts the operator pod and shows its last log lines. It calls `POST .../virt/recover-rollback` (`virt:Install`).

**API:** `POST /api/supervisors/{name}/virt/install` (`virt:Install`), with the body fields named above. Send `"viaCharts": true`. If you leave the field out it is `false`, which selects the GitHub-manifest path. An empty `featureGates` list installs the default gates. **MCP:** no tool installs Virtualization, so use the API. `get_component_status` with `component=virt` reads the step's state. `get_kubevirt_platform` lists versions (`view=versions`, `source=kubevirt` or `cdi`) and the gate catalogue (`view=feature-gates`). `get_chart_versions` lists the chart versions that the panels offer.

## Cluster API

This step installs four things: `cluster-api-operator`; the Cluster API providers that your tenant cluster kinds need; the ClusterClasses those kinds create clusters from; and the CAAPH engine, which delivers add-ons into guest clusters. It also holds the DNS and NTP servers that guest nodes inherit. It stays locked until **Certificates** is done, because `cluster-api-operator` needs cert-manager. [Clusters overview](https://docs.celum.io/clusters/overview.md) describes the engine that this step installs. [Create a cluster](https://docs.celum.io/clusters/create.md) describes what you build with it.

The step is done when all of these are true:

* the operator release and the core provider are Ready;
* at least one tenant cluster kind is offered;
* every offered kind can create a cluster;
* the add-on engine is Ready, if it is installed.

One button applies the whole step: **Install Cluster API** on a new supervisor, **Re-apply Cluster API** after that. It sends up to three requests, in this order:

1. `POST /api/supervisors/{name}/capi/install` installs the operator release, waits for its CRDs and webhook, then applies the providers.
2. `POST .../capi/addons` installs the add-on engine. It is sent only when **Enable guest add-on engine (CAAPH)** is on.
3. `POST .../capi/clusterclasses/install` renders the ClusterClasses. It is sent when at least one kind is offered, or when the ClusterClass release already exists.

If the operator is not up in time, the first request answers `202`, and the step log tells you to apply again when the operator reports Ready. The add-on and ClusterClass requests then wait for that next apply.

The button stays disabled until the step has read what is installed. Otherwise an apply would send the defaults over a configured supervisor. If a change removes a provider or a ClusterClass, a confirmation lists what will be removed before anything is applied.

### Core providers

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **cluster-api-operator** version (`operatorVersion`) | Sets the chart version of the `cluster-api-operator` HelmRelease in `capi-operator-system` | The version Celum pins | Always |
| **CoreProvider** version (`coreVersion`) | Sets the version of the Cluster API core provider in `capi-system`. The feature gates below are checked against this version | The version Celum pins | Always |
| **BootstrapProvider (talos)** version (`talosBootstrapVersion`) | Sets the version of the Talos bootstrap provider in `cabpt-system` | The version Celum pins | Always |
| **ControlPlaneProvider (talos)** version (`talosControlPlaneVersion`) | Sets the version of the Talos control-plane provider in `cacppt-system` | The version Celum pins | Always |

These four are always installed, whichever kinds you offer. Each panel's own button applies the whole step with the version you picked, through the same request as the step's button.

> [!NOTE]
>
> On a new supervisor, the step installs every provider from Celum's own `capi-provider-*` charts, so nothing is fetched from GitHub at install time. A supervisor whose providers were installed as direct Provider CRs stays on that delivery path. Its panels then show operator state, and a line under **Core providers** says so. Moving such a supervisor to the charts is a migration, not a re-apply.

### Cluster API feature gates

The step writes these gates to the core provider's `featureGates` (`coreFeatureGates`).

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **ClusterTopology** | Enables ClusterClass and managed topologies. Turning it off breaks the ClusterClasses that this step installs | On | Always |
| **MachinePool** | Enables MachinePool, the Cluster API node-pool primitive that autoscaling acts on | On | Always |
| **RuntimeSDK** | Enables runtime hooks for extension servers. **InPlaceUpdates** needs it | Off | Always |
| **InPlaceUpdates** | Updates a machine's Kubernetes version or configuration in place instead of replacing the machine. It has no effect without **RuntimeSDK** and a deployed update extension. The step warns when **RuntimeSDK** is off | Off | Hidden, and left out of the request, when the core provider is older than v1.12 |
| **MachineTaintPropagation** | Copies taints from Machine, MachineSet and MachineDeployment specs onto the nodes | Off | Hidden, and left out of the request, when the core provider is older than v1.12 |

If the core provider does not recognise a gate, its controller does not start, even when the gate is set to off. That is why the step hides a gate from an older core instead of sending it as off.

### Operator runtime

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **replicaCount** (`operatorReplicaCount`) | Sets the number of operator controller-manager replicas, from 0 to 5 | `0`, which keeps the chart default of one replica | Always |
| **Image registry override** (`imageRegistry`) | Pulls the operator image from `<registry>/cluster-api-operator/cluster-api-operator` instead of `registry.k8s.io`, for example with `registry.example.com`. It applies to the operator image only | Empty, which pulls from upstream | Always |

### Tenant cluster kinds

Each card is a kind of tenant cluster that this supervisor offers. When you select a card, the next apply installs everything that kind needs: its providers and its ClusterClass. As soon as the step is done, the create form offers that kind.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Talos on KubeVirt** | Installs CAPK, the KubeVirt infrastructure provider, and the `talos-kubevirt` ClusterClass. Guest machines are KubeVirt VMs | Selected | Always. Guests cannot start until the **Virtualization** step is done, and the step warns while it is not |
| **Kamaji** | Installs the Kamaji control-plane provider, the standalone Kamaji operator, the kubeadm bootstrap provider, CAPK, and the `kamaji-kubevirt` ClusterClass. The control plane runs as pods on the supervisor. Workers are KubeVirt VMs | Selected | Always, with the same **Virtualization** warning |
| **vCluster** | Installs CAPVC, the vCluster infrastructure provider. A vCluster has no ClusterClass | Selected | Always |
| **Talos on vSphere** | Installs CAPV with vCenter credentials, and one `talos-vsphere-<tenant>` ClusterClass for each tenant | Not selected | Always. You can create a cluster only after the credentials are set and at least one tenant is complete |

Each offered kind shows a **ready** or **incomplete** badge. Under the cards, the step lists what each incomplete kind still needs, for example a provider that is still installing, or a missing ClusterClass.

When you clear a card, the next apply removes the kind's ClusterClass and every provider that no remaining kind needs. Tenant clusters keep running. Celum keeps the KubeVirt, vSphere and vCluster providers while a tenant cluster still references them. Cluster API refuses to delete a ClusterClass that clusters use, and the ClusterClass release then reports the error.

When a kind is selected, its provider panel appears under the cards:

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **InfrastructureProvider (kubevirt)** version (`kubevirtVersion`) | Sets the CAPK version in `capk-system` | The version Celum pins | When **Talos on KubeVirt** or **Kamaji** is selected |
| **InfrastructureProvider (vsphere)** version (`vsphereVersion`) | Sets the CAPV version in `capv-system` | The version Celum pins | When **Talos on vSphere** is selected |
| **InfrastructureProvider (vcluster fork)** version (`vclusterVersion`) | Sets the CAPVC version in `capvc-system`. The pinned version is a patched build that Celum ships | The version Celum pins | When **vCluster** is selected |
| **ControlPlaneProvider (kamaji)** version (`kamajiVersion`) | Sets the Kamaji control-plane provider version in `cacppk-system`. Under it, the standalone Kamaji operator in `kamaji-system` is shown read-only. The same apply installs it | The version Celum pins | When **Kamaji** is selected |

#### vCenter credentials

These fields appear when **Talos on vSphere** is selected.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **vCenter credentials**: **A registered vCenter** or **Type them** | Selects where CAPV's credentials come from. **A registered vCenter** takes them, through External Secrets, from a vCenter registered under **Settings → vCenters**. **Type them** stores them for this supervisor only | **Type them** | **A registered vCenter** is disabled while no vCenter is registered |
| **Registered vCenter** (`vsphereVCenter`) | Names the registered vCenter. External Secrets then writes `capv-system/vsphere-credentials` from the secret store, with the server and the pinned thumbprint from **Settings → vCenters** | — | When **A registered vCenter** is selected. Needs `vcenter:Use` on that vCenter and a connected **Secret Store** step |
| **VSPHERE\_SERVER** (`vsphereCredentials.server`) | Sets the vCenter host name, for example `vcenter.example.com` | — | When **Type them** is selected |
| **VSPHERE\_USERNAME** (`vsphereCredentials.username`) | Sets the vCenter user | — | When **Type them** is selected |
| **VSPHERE\_PASSWORD** (`vsphereCredentials.password`) | Sets the vCenter password. On a later apply, leave it empty to keep the stored password, or type a new one to rotate it | — | When **Type them** is selected. Required on the first vSphere install |
| **VSPHERE\_TLS\_THUMBPRINT** (`vsphereCredentials.tlsThumbprint`) | Pins the vCenter certificate. **Detect** reads the certificate from **VSPHERE\_SERVER**, without credentials, and fills in its SHA-1 thumbprint | — | When **Type them** is selected. **Detect** is disabled until **VSPHERE\_SERVER** is set |

If the **Secret Store** step is connected, typed credentials go to the secret store. If not, they go straight into the `capv-system/vsphere-credentials` Secret. After a successful apply, the step clears the typed user, password and thumbprint from the form.

When typed credentials are installed, two more actions appear:

* If **Settings → vCenters** already holds that vCenter, **Use** \<name> **and apply** moves Cluster API to it, and the copy kept for this supervisor is cleared.
* If not, **Register for the platform** registers it under a **Name** and an optional **Description**, from the credentials the supervisor already holds. It then moves Cluster API to it. The button is disabled until a thumbprint is pinned. It needs `vcenter:Create` and `vcenter:Use` on the new name.

While vSphere guest clusters run on the current vCenter, Celum refuses to point Cluster API at a registered vCenter with a different address.

Under the credentials, a panel compares the certificate that vCenter serves now with the pinned thumbprint. If they differ and the credentials are typed, **Pin this certificate** moves the pin. If the supervisor takes its credentials from a registered vCenter, you re-pin under **Settings → vCenters** instead. See [Settings](https://docs.celum.io/administration/settings.md#vcenters).

#### Tenants

Each vSphere tenant renders one `talos-vsphere-<name>` ClusterClass with fixed placement. When you create a cluster, picking that class picks the tenant, so the create form does not ask for a datacenter or a network.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Add tenant** | Adds an empty tenant row. **Remove** deletes a row | One empty row | When **Talos on vSphere** is selected. **Remove** appears while there is more than one row |
| **Name (slug)** (`name`) | Names the tenant and its class, `talos-vsphere-<name>`. Names must be unique | — | Every row |
| **Datacenter** (`datacenter`) | Sets the vSphere datacenter. The lists of the four fields below show only what this datacenter holds | — | Every row |
| **Datastore** (`datastore`) | Sets the datastore for the tenant's VMs | — | Every row |
| **Folder** (`folder`) | Sets the VM folder path | — | Every row |
| **Resource pool** (`resourcePool`) | Sets the resource pool path | — | Every row |
| **Network (portgroup)** (`network`) | Sets the port group that the VMs attach to | — | Every row |

The pickers list what the vCenter holds. If the inventory cannot be read, each picker becomes a text field. The apply refuses a row that has some fields filled and others empty, so Celum never drops a half-written tenant without telling you. When you remove a complete tenant, the next apply removes its ClusterClass, after a confirmation.

#### ClusterClasses

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **capi-clusterclasses** version (`chartVersion`) | Sets the chart version of the `capi-clusterclasses` HelmRelease in `default`, which renders the ClusterClasses. **Installed** lists the classes that the supervisor serves | The version Celum pins. A release that already runs a newer version is not downgraded | The panel's own button appears once the release exists. The first install comes from the step's button |

### What guest nodes inherit

Celum writes these values into each guest node's Talos machine config when it provisions the node. They apply to guest nodes, not to guest workloads. You declare them once per supervisor, so the same ClusterClass works at every site. **Save** stores them by themselves, without the step's button.

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Nameservers** (`nameservers`) | Sets the guest nodes' DNS resolvers, in order, one IP address per line. At most four. A host name is refused | Empty. `talos-*` guests then use the public resolvers `8.8.8.8` and `1.1.1.1`, which cannot answer internal zones. Kamaji workers keep the resolvers that DHCP gives them | Hidden if you cannot read the declaration |
| **Search domains** (`searchDomains`) | Lets guests resolve short internal names, one domain per line. An entry with a space, a tab, `/` or `:` is refused | Empty | As above. `talos-*` guests need `capi-clusterclasses` 0.6.4 or later. With an older chart, only the nameservers apply |
| **Time servers** (`timeServers`) | Sets the guest nodes' NTP servers, one per line. Talos starts the kubelet only after time is synchronised, so a site without internet access must set its own servers | Empty | As **Search domains** |

The step warns in two cases:

* With only one nameserver, that resolver is a single point of failure for every image pull, because guests send `docker.io`, `quay.io` and `registry.k8s.io` to the supervisor's registry mirror.
* When every time server is a host name, NTP depends on DNS, so a resolver outage stops nodes from joining.

Only clusters created after the save get the new values. Existing guests keep the values they were built with. After you change the values, run the step's apply. It renders the ClusterClasses again, and their defaults then carry the nameservers and search domains. Values that you set on the create form override these. See [Create a cluster](https://docs.celum.io/clusters/create.md).

Under the fields, **Registry mirror and trust** shows what guests inherit from **Source & Bootstrap**. It shows the mirror host, and compares how many redirected and trusted registries are declared with how many the ClusterClasses carry. It is read-only here. If the classes carry fewer, a guest created now pulls from the public internet. Run the step's apply to write them into the classes. A declared **Strict air-gap** reaches guests the same way, from `capi-clusterclasses` 0.6.14. With an older chart, the apply log says that guests still fall back to the internet.

### Cluster add-ons (CAAPH)

CAAPH, the Cluster API Add-on Provider for Helm, installs add-ons into guest clusters. Each guest cluster then opts in to each add-on on the create form. See [Create a cluster](https://docs.celum.io/clusters/create.md).

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Enable guest add-on engine (CAAPH)** | Includes the add-on engine in the step's apply: the CAAPH `AddonProvider` and an add-on catalog in the `default` namespace. When a cluster is created in a guest namespace, that namespace gets its own catalog. Turning the toggle off leaves an installed engine in place | On | Always |
| **CNI — Cilium** (`ciliumEnabled`) | Offers Cilium to guests. Talos ClusterClasses set no CNI, so without it guest nodes stay NotReady | On | When the engine is enabled |
| **CCM — Talos cloud-controller-manager** (`talosCcmEnabled`) | Offers the Talos cloud controller manager. On KubeVirt guests, it sets each node's provider ID and approves kubelet serving certificates | On | When the engine is enabled |
| **CSI — KubeVirt CSI** (`kubevirtCsiEnabled`) | Offers the KubeVirt CSI driver and a `kubevirt` StorageClass in the guest, so guest volumes are carved from supervisor storage | On | When the engine is enabled |
| **Monitoring — Prometheus agent → supervisor** (`prometheusAgentEnabled`) | Offers a Prometheus agent that remote-writes guest metrics to this supervisor's Prometheus, labelled `cluster=<name>` | On | When the engine is enabled |
| **Logs — Alloy → supervisor Loki** (`alloyEnabled`) | Offers Alloy, which pushes guest pod logs to this supervisor's Loki, labelled `cluster=<name>`. Loki must be exposed through the gateway | On | When the engine is enabled |
| **Flows — cluster-agent → supervisor ClickHouse** (`flowsEnabled`) | Offers the guest cluster agent, which forwards Hubble and conntrack flows to this supervisor's ClickHouse, so that the Security views cover the guest | On | When the engine is enabled |
| **CSI backing StorageClass** (`infraStorageClass`) | Sets the supervisor StorageClass that backs guest volumes | The supervisor's default StorageClass, else its first one | When **CSI — KubeVirt CSI** is on |
| **capi-addons** version (`chartVersion`) | Sets the chart version of the `capi-addons` engine release. Its own button applies only the engine | The version Celum pins. A newer installed version is not downgraded | When the engine is enabled |

The vSphere add-ons (CPI, CSI and the CSR approver) have no toggle. They follow the **Talos on vSphere** card. While `cluster-api-operator` is not installed, the engine request is refused with `409`.

### Recovery

When the operator release is stuck in a failed rollback, or a provider stays not Ready and gives a reason, an **Operator or provider stuck** banner appears. It offers **Force reconcile + restart operator**. This action force-reconciles the operator HelmRelease, restarts the operator pod, and shows its last log lines. On a healthy supervisor, it does nothing.

### Fields only the API sets

| Field | What it does | Default |
| - | - | - |
| `providersViaCharts` on `POST .../capi/install` | `true` installs the providers from Celum's `capi-provider-*` charts. `false` applies Provider CRs, and the operator then fetches the manifests from GitHub | `false`. The step always sends `true` on a new supervisor. On an installed one, it sends the delivery path that the supervisor already uses |
| `kamajiImageRepository`, `kamajiImageTag` on `POST .../capi/install` | Sets the image of the standalone Kamaji operator. The step has no field for it | Celum's own Kamaji operator build |
| `vsphere.identityRef` on `POST .../capi/clusterclasses/install` | Names the cluster-scoped `VSphereClusterIdentity` that the `talos-vsphere-*` classes use, so guest clusters in any namespace can reach vCenter. Celum creates it, with its Secret | `vsphere-credentials` |
| `vsphere.host`, `vsphere.thumbprint` on `POST .../capi/clusterclasses/install` | Sets the vCenter host and thumbprint baked into the vSphere classes | Read from `capv-system/vsphere-credentials` |

> [!WARNING]
>
> The install routes treat a field you leave out differently. `POST .../capi/install` takes the whole provider set. If you leave out a kind's `kubevirtEnabled`, `kamajiEnabled`, `vclusterEnabled` or `vsphereEnabled`, it counts as `false`, and that provider is removed. `POST .../capi/clusterclasses/install` and `POST .../capi/addons` keep the installed value of any field you leave out, so `{}` renders again what is there.

The ClusterClass request answers `400` in these cases: an enabled vSphere family without at least one tenant, a tenant with an empty field, or two tenants with the same name. It answers `409` for a `vsphere.thumbprint` that differs from the one the installed classes pin, because CAPV does not allow changes to those templates. If only the stored credentials carry a new thumbprint, it keeps the installed one and says so in the step log.

| Task | API | Permission | MCP |
| - | - | - | - |
| Install or re-apply the operator and providers | `POST .../capi/install` | `capi:Install` | none, use the API |
| Install the add-on engine | `POST .../capi/addons` | `capi:Install` | none, use the API |
| Install the ClusterClasses | `POST .../capi/clusterclasses/install` | `capi:Install` | none, use the API |
| Declare guest DNS and NTP | `PUT .../onboarding/host-resolution` | `cilium:Apply` | `set_onboarding_host_resolution` |
| Read guest DNS and NTP | `GET .../onboarding/host-resolution` | `cilium:GetDefaults` | `get_onboarding_prerequisites` with `view=host-resolution` |
| Read the vCenter certificate (**Detect**) | `POST /api/capi/vsphere-probe` | `capi:Install` | `probe_vsphere_certificate` |
| Register the typed vCenter for the platform | `POST .../capi/register-vcenter` | `capi:Install`, `vcenter:Create`, `vcenter:Use` | `register_vcenter_from_cluster_api` |
| Compare the served certificate with the pin | `GET .../capi/vcenter-certificate` | `capi:GetStatus` | `get_vcenter_certificate` |
| **Pin this certificate** | `POST .../capi/vcenter-certificate/accept` | `capi:Install` | `accept_vcenter_certificate` |
| List vSphere inventory for the tenant pickers | `GET .../vsphere/inventory` | `capi:GetStatus` | `get_vsphere_inventory` |
| Read the step's state | `GET .../capi/status`, `GET .../capi/addons/status` | `capi:GetStatus` | `get_component_status` with `component=capi` or `component=capi-addons` |
| Recover a stuck operator | `POST .../capi/recover-rollback` | `capi:Install` | none, use the API |

Over MCP, an assistant can declare guest DNS and NTP and manage the vCenter certificate. It cannot install the operator, the providers, the add-on engine or the ClusterClasses. After the step has installed a ClusterClass, clusters can be created over MCP with `create_capi_cluster`.

## Flow Telemetry

This step connects the cluster to ClickHouse as a flow pipeline. It applies the `clickhouse-schema` chart, sets the cluster agent's dispatchers, and applies the DNS visibility policy. [Pipeline & runtime](https://docs.celum.io/security/pipeline-and-runtime.md#what-the-flow-telemetry-apply-does) lists everything the apply does. [Flow telemetry](https://docs.celum.io/platform-health/flow-telemetry.md) describes the dispatchers and their tables. The step unlocks when Cilium is Ready.

> [!NOTE]
>
> The apply button stays disabled until ClickHouse is installed and published. To get there, run the Monitoring step with **Enable ClickHouse** on, on a supervisor with a domain. Then wait for the `clickhouse.<your domain>` route and its certificate to be Ready. Until then, the API answers `412`. The button is also disabled while every dispatcher is off, or while **Cluster name** is empty.

### Dispatchers

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Hubble — Cilium flow\_local events** (`enableHubble`) | Writes Cilium's L3/L4/L7 flows, drops and policy verdicts to `flows_local` | On | Always. The apply turns it off when Cilium's Hubble is not enabled |
| **TLS-SNI — egress ClientHello fingerprint** (`enableTlsSni`) | Records the server names of outbound TLS connections in `tls_sni_1m_local` | On | Always |
| **CT — Cilium conntrack bytes** (`enableCT`) | Reads Cilium's conntrack map into `ct_flows_1m_local`. It needs Cilium's conntrack accounting (`bpf.ctAccounting`), which the Networking step turns on by default | On | Always |
| **Gateway access log — external clients on the shared gateway** (`enableGwlog`) | Points the access log of every Envoy Gateway proxy that Celum manages at the cluster agent, which writes `gateway_requests_local`. It adds a ReferenceGrant, a certificate from its own cert-manager CA, and a BackendTLSPolicy in `kube-system`. Turning it off removes them | Off | Always. Has an effect only when the North-South gateway is Envoy Gateway |
| **— skip health probes** (`gwlogFilters.skipProbes`) | Stops Envoy from logging requests to `/healthz`, `/readyz`, `/livez`, `/health`, `/ready` and `/metrics`. The paths must match exactly | Off | While **Gateway access log** is on |
| **— errors only (4xx/5xx)** (`gwlogFilters.errorsOnly`) | Makes Envoy log only responses with a status of `400` or higher | Off | While **Gateway access log** is on |

Envoy applies the two filters before it sends anything, so a filtered request is never stored. With both filters on, Envoy logs only failed requests that are not probes.

The first apply that turns on **Gateway access log** usually warns that the Service `kube-system/cluster-agent-gwlog` does not exist yet, because the agent is still rolling out. The gateway connects when the Service appears. If a warning says that the sink stays off, apply again after cert-manager has issued the certificate. If the cluster agent is older than the TLS listener, the log travels in cleartext until you upgrade the agent, and the apply warns about it.

The step has no control for the Tetragon dispatcher. It sends back the stored `enableTetragon` value, so a supervisor that has Tetragon on keeps it. You install Tetragon itself through the API: see [Pipeline & runtime](https://docs.celum.io/security/pipeline-and-runtime.md#tetragon).

### Cluster identification

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **Cluster name** (`clusterName`) | Stamped on every row as the `cluster` column. This supervisor's Security views filter on the name that the cluster agent carries | The supervisor's name | Always. Rows written before a rename keep the old name, so they drop out of this supervisor's views |

### ClickHouse target

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **DSN override (advanced)** (`dsnOverride`) | Replaces the address that the cluster agents write to. Use it to send this supervisor's rows to another supervisor's ClickHouse | Empty: `clickhouse-hubble-flows.security.svc:9000`, as the user `flows`. Celum generates its password once and keeps it in `security/clickhouse-flows-writer` | Behind **Show advanced (DSN override)**. The step still needs the local ClickHouse published, because it applies the schema there |

Celum keeps the DSN in the Secret `kube-system/cluster-agent-clickhouse` when the agent's chart can read it from a Secret. Otherwise it keeps it in the HelmRelease values.

### Schema chart

| Option | What it does | Default | When it is available |
| - | - | - | - |
| **clickhouse-schema** version (`schemaChartVersion`) | Sets the schema chart version. The panel follows the newest version in the registry, so **Re-apply** carries an upgrade | Empty: the newest version in the registry, or a built-in version if the registry does not answer | Always. The panel's button runs the same apply as the step's button |

The API also takes `clusterAgentChartVersion` and `clusterAgentImageTag`, which move the cluster agent to that version. Empty keeps the version that the agent's release declares.

**API:** `POST /api/supervisors/{name}/flow-pipeline/configure` (`monitoring:Install`). Every dispatcher flag is `false` if you leave it out, so send all of them. **MCP:** no tool runs the apply, so use the API. `get_component_status` with `component=flow-pipeline` reads the step's state. `get_flow_freshness` shows whether rows are arriving. `get_clickhouse_schema` shows the live schema.

### ClickHouse passwords

Under its status, the step has a **ClickHouse passwords** section with a **Rotate password** button for each of the two users Celum manages, `k8sgate_admin` and `flows`. A rotation is not part of the apply and needs `monitoring:Install`. [Flow store passwords](https://docs.celum.io/platform-health/flow-telemetry.md#flow-store-passwords) describes what each rotation does and costs, and its API route and MCP tool.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.