Every spec and status field of all eight CRDs in kopiur.home-operations.com/v1alpha1, with its type, schema default, and a one-line meaning taken straight from the Rust doc comments.
This page is generated from the kopiur-api CRD schemas by cargo xtask gen-docs (run mise run gen); it is drift-checked in CI, so it can never go stale against the shipped CRDs. To change an entry, edit the doc comment on the field in crates/api — not this file. For the task-oriented explanations (mental model, which knobs you actually change), see the per-CRD pages under CRD reference.
Conventions
Type uses the CRD/YAML shape. enum: A \| B lists the allowed values; []T is an array, map[string]T a map, object (free-form) an unvalidated object, and core/v1 X an embedded Kubernetes type (see the Kubernetes API reference for its fields).
Default is the schema default: the API server materializes when the field is absent. — means no schema default — an optional field that is simply unset, or one whose effective default depends on context (the description says which).
required marks a field with no default that must be present, or admission fails.
Externally-tagged unions select a variant by which key you set, never a kind: field; each is flagged "set exactly one of …".
Validation rules enforced by the API server (x-kubernetes-validations, including immutability) are listed under the table they apply to.
Tuning for the bootstrap/discovery mover Job (<name>-discovery) that connects/creates an object-store repository the operator cannot reach in-process (and re-runs for catalog re-scans).
Identity defaults (CEL *Expr) applied when consumers don't override. Set cluster when this repository's backend is shared across clusters (e.g. one bucket backed up from more than one Kubernetes cluster) — the default kopia identity hostname then becomes <namespace>.<cluster> instead of bare <namespace>, so same-named namespaces on different clusters never collide. Same semantics as ClusterRepository's field of the same name (ADR-0004 §5); a consumer's explicit spec.identity still wins over anything here.
Scheduling defaults (e.g. timezone) inherited by consumers that don't set their own equivalent field — verification, replication, and maintenance schedules today; set once here instead of repeating it on every cron.
Google Drive via kopia's native gdrive provider (service-account JSON). kopia marks this provider experimental / not maintained, and a native gdrive repository is not interchangeable with an rclone-backed Drive remote.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Secret holding the Google service-account JSON used to reach the folder, read by the well-known key KOPIA_GDRIVE_CREDENTIALS. Absent means kopia falls back to ambient credentials (GOOGLE_APPLICATION_CREDENTIALS or instance metadata).
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Secret holding the rclone.conf that defines the remote referenced by remote_path.
startupTimeout
string
—
How long kopia waits for its embedded rclone serve to come up before failing the connect, as a Go duration (e.g. 2m). kopia's default is 15s; raise it for slow remotes whose repository metadata/indexes load through the rclone/WebDAV bridge and take longer than the default budget.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Failure policy for the bootstrap Job. activeDeadlineSeconds caps how long a connect may run before the Job is marked failed (default 120s); raise it for a slow backend — e.g. an rclone remote whose repository metadata and indexes load through kopia's embedded rclone serve/WebDAV bridge. backoffLimit bounds retries. podStartupDeadlineSeconds is accepted for shape parity but is not honored by the bootstrap Job.
Whether a discovered snapshot whose resolved identity matches a live SnapshotPolicy is automatically adopted — re-attached (stamped with that policy's config label, status.origin flipped to Adopted) so GFS retention governs it and eventually prunes it, instead of it sitting in the catalog forever as an immortal discovered row.
fallbackNamespace
string
—
Where to materialize discovered Snapshots with no allowed-namespace mapping (ClusterRepository only).
foreignSnapshots
enum: Ignore | Fallback
—
How catalog discovery treats snapshots written by ANOTHER cluster. status.catalog.foreignSnapshotCount counts an identity hostname carrying a different .<cluster> suffix ALWAYS, under either value below, plus — under Ignore only — a bare hostname naming no allowed namespace here. Under Fallback, that same disallowed bare host is NOT counted foreign: it materializes into catalog.fallbackNamespace exactly like a disallowed OwnCluster host would, so it is placed rather than dropped-and-counted (see classify_hostname/decide_cluster_placement). Meaningful only when identityDefaults.cluster is set: without a cluster identity there is no notion of "foreign" and the legacy hostname-names-a-namespace placement applies (validators enforce this).
periodicRefresh
boolean
—
Opt-in: periodically re-scan the repository to keep discovered Snapshot CRs current (re-list snapshots; for object-store / volume-backed repos this recycles the <name>-discovery mover Job every refreshInterval). Off by default — the repository still bootstraps once, re-bootstraps on a spec change, and re-probes on a backup failure, but does not re-run on a timer. Enable it if you rely on discovered snapshots reflecting changes made outside this operator.
refreshInterval
string
1h
How often to re-scan when periodicRefresh: true (Go-style duration; minimum 30s, default 1h). Inert unless periodicRefresh is enabled.
Reed-Solomon ECC parity for a freshly-created repository (creation-time only, immutable after).
enabled
boolean
true
Create the repository if it does not exist yet — on by default. Repository create/connect is idempotent: pointing create at an already-initialized repository just connects to it (it never re-creates or clobbers), so the only effect of the default is that a genuinely-absent repository is bootstrapped instead of erroring. Set false for a strictly read-only or externally-managed repository the operator must never create.
encryption
string
—
kopia encryption algorithm for a freshly-created repository (creation-time only).
hash
string
—
kopia content hash algorithm for a freshly-created repository (creation-time only).
splitter
string
—
kopia object splitter for a freshly-created repository (creation-time only).
Pending EXTERNAL destructive Snapshot deletions (deletionTimestamp set, effective deletionPolicy Delete, not operator-pruned) that trip the breaker for this repository: at or above this, those deletions are HELD (finalizers wait) until acknowledged via the kopiur.home-operations.com/allow-mass-deletion annotation. 0 disables the breaker. Default 10.
Turn the probe on. Off by default — existing repositories keep their current behavior until a user opts in.
failureThreshold
integer
3
How many consecutive failing probes to require before raising the loud condition + event (default 3). Debounces a single transient blip from alarming or nudging a destructive manual recreate. Any success resets it.
interval
string
30m
How often to re-probe the backend (Go-style duration like 30m or 1h; minimum 30s, default 30m). Inert unless enabled.
This cluster's identity suffix for repositories shared across clusters (an RFC 1123 label, at most 32 characters; dots are rejected — the first . in a hostname is the namespace/cluster delimiter). When set, the default kopia identity hostname becomes <namespace>.<cluster> instead of <namespace>, so two clusters backing up same-named namespaces write distinct identities (and one cluster's retention prune can no longer touch the other's snapshots). Also exposed to hostnameExpr/ usernameExpr as the CEL variable cluster.
hostnameExpr
string
—
CEL expression for the kopia identity hostname (e.g. "namespace").
usernameExpr
string
—
CEL expression for the kopia identity username (e.g. "namespace + '-' + policyName").
Copy the UID/GID security context from a live workload rather than hard-coding it. Requires the workload to pin runAsUser (container or pod level): a UID that comes from the container image's USER line is invisible in the pod spec and cannot be inherited — the mover would silently run as its own image's UID instead. May be combined with securityContext/podSecurityContext, which override it field-wise and act as the fallback when no workload pod can be resolved.
podSecurityContext
core/v1 PodSecurityContext
—
Pod security context for the mover (notably fsGroup for group-writable restore volumes). Same layering as securityContext: highest layer, merged field-wise, and combinable with inheritSecurityContextFrom.
privilegedMode
boolean
—
Opt-in, namespace-gated privileged mode; preserves UID/GID on restore.
resources
core/v1 ResourceRequirements
—
Resource requests/limits for the mover container.
securityContext
core/v1 SecurityContext
—
Container security context for the mover; merged field-wise over the hardened base, moverDefaults, and any inherited context — this is the highest layer, so every field set here wins. Combines with inheritSecurityContextFrom: fields you set override the workload's, fields you omit are inherited, and this context stands in alone when inheritance cannot resolve a pod.
ttlSecondsAfterFinished
integer
—
Per-recipe override of Job.spec.ttlSecondsAfterFinished so finished Jobs self-GC.
Backup sources only: auto-derive the workload from the PVC this snapshot backs up.
snapshot
object
—
Restores only: inherit the identity RECORDED on the backup itself (Snapshot.status.recorded, decoded from the kopiur-meta kopia tag) — uid/gid/fsGroup the backup mover actually ran as. Needs no live workload pod, so it works on a rebuilt cluster and with target.populator. Rejected at admission on SnapshotPolicy/Maintenance (backups read the live workload; maintenance has no snapshot). Write it as snapshot: {} (an empty sub-object) — a bare snapshot: is null and rejected.
Index blobs in an epoch that trigger an advance, once older than minDuration (kopia default 20).
advanceOnSizeMiB
integer
—
Total index size in an epoch that triggers an advance, once older than minDuration (kopia default 10 MiB). Named MiB, not MB, with an explicit rename rather than the derived camelCase (advanceOnSizeMb, which reads as megabit). The unit is genuinely mebibytes — kopia's --epoch-advance-on-size-mb multiplies by 1048576, so 10 is 10485760 bytes — even though kopia's own log renders the result as "MB". That ambiguity is this field's main hazard; the API surface should not reproduce it.
checkpointFrequency
integer
—
Epochs between full index checkpoints (kopia default 7).
deleteParallelism
integer
—
Parallelism for epoch cleanup deletions (kopia default 4).
minDuration
string
—
Minimum epoch age before it may advance (kopia default 24h). A Go-style duration (6h, 90m). The advance gate — no blob count closes an epoch younger than this.
refreshFrequency
string
—
How often clients re-read epoch state (kopia default 20m). Go-style duration.
IANA timezone name applied to every consuming cron that doesn't set its own timezone (e.g. America/New_York). Set once here instead of repeating it on every SnapshotPolicy.verification, RepositoryReplication.schedule, and Maintenance.schedule cron.
How many Snapshot CRs were materialized from the catalog scan.
foreignSnapshotCount
integer
—
Snapshots in the last complete listing classified as another cluster's (see catalog.foreignSnapshots); never materialized under Ignore. As of catalog.lastRefreshAt — enable periodicRefresh to keep it current.
lastRefreshAt
string
—
RFC 3339 timestamp of the last catalog refresh.
scanRequestAttemptAt
string
—
RFC 3339 timestamp of the last bootstrap/scan attempt initiated BECAUSE OF a pending catalog-scan-requested-at token (i.e. the token arm was the reason the attempt fired). Used only to rate-limit token-driven attempts on a Ready-but-unreachable repository — it is never compared against the token for retirement (that's scanRequestHonored, by equality).
scanRequestHonored
string
—
The RFC3339 token VALUE of the kopiur.home-operations.com/catalog-scan-requested-at annotation last honored by a completed catalog scan. Compared by equality against the live annotation to decide whether a requested on-demand scan is still pending — deliberately NOT a timestamp comparison against lastRefreshAt (a periodic refresh completing after the request was made would otherwise look like it honored the request even though it started before the annotation was set).
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
Consecutive failing probes accrued; reset to zero on any success. The loud condition is raised only once this reaches the failure threshold.
firstFailureAt
string
—
RFC 3339 timestamp of the first failure in the current failing streak.
lastHealthyAt
string
—
RFC 3339 timestamp of the last successful probe (backend reachable, repo present).
lastProbeAt
string
—
RFC 3339 timestamp of the last completed probe (success or failure); drives the health_probe_due timer so the probe re-fires on cadence.
probeAttemptAt
string
—
RFC 3339 timestamp at which the last backend health probe was launched (the bootstrap Job created for it), cleared when its result is finalized. This is the launch-side rate limit, and it is what makes the probe terminate. lastProbeAt is written only at finalize, so gating the launch on that alone recycles the bootstrap Job forever: the gate destroys every Job whose completion would have cleared it (#273). Never compared against lastProbeAt — see kopiur_controller::health::probe_action.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Number of content-index blobs (kopia index list) observed at the last bootstrap.
lastObservedAt
string
—
RFC 3339 timestamp these stats were last observed.
snapshotCount
integer
—
Total snapshots present in the repository (across all identities).
totalSize
string
—
Human-readable total on-disk size (e.g. 412Gi).
totalSizeBytes
integer
—
Logical bytes under management (the integer form of total_size): the sum, over each distinct snapshot source, of the most-recent snapshot's logical size. Exposed to backup preflight as repository.sizeBytes. This is repository total size, not backend free space (object stores don't report remaining capacity).
Tuning for the bootstrap/discovery mover Job (<name>-discovery) that connects/creates an object-store repository the operator cannot reach in-process (and re-runs for catalog re-scans).
Scheduling defaults (e.g. timezone) inherited by consumers that don't set their own equivalent field — verification, replication, and maintenance schedules today; set once here instead of repeating it on every cron.
Google Drive via kopia's native gdrive provider (service-account JSON). kopia marks this provider experimental / not maintained, and a native gdrive repository is not interchangeable with an rclone-backed Drive remote.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Secret holding the Google service-account JSON used to reach the folder, read by the well-known key KOPIA_GDRIVE_CREDENTIALS. Absent means kopia falls back to ambient credentials (GOOGLE_APPLICATION_CREDENTIALS or instance metadata).
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Secret holding the rclone.conf that defines the remote referenced by remote_path.
startupTimeout
string
—
How long kopia waits for its embedded rclone serve to come up before failing the connect, as a Go duration (e.g. 2m). kopia's default is 15s; raise it for slow remotes whose repository metadata/indexes load through the rclone/WebDAV bridge and take longer than the default budget.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Failure policy for the bootstrap Job. activeDeadlineSeconds caps how long a connect may run before the Job is marked failed (default 120s); raise it for a slow backend — e.g. an rclone remote whose repository metadata and indexes load through kopia's embedded rclone serve/WebDAV bridge. backoffLimit bounds retries. podStartupDeadlineSeconds is accepted for shape parity but is not honored by the bootstrap Job.
Whether a discovered snapshot whose resolved identity matches a live SnapshotPolicy is automatically adopted — re-attached (stamped with that policy's config label, status.origin flipped to Adopted) so GFS retention governs it and eventually prunes it, instead of it sitting in the catalog forever as an immortal discovered row.
fallbackNamespace
string
—
Where to materialize discovered Snapshots with no allowed-namespace mapping (ClusterRepository only).
foreignSnapshots
enum: Ignore | Fallback
—
How catalog discovery treats snapshots written by ANOTHER cluster. status.catalog.foreignSnapshotCount counts an identity hostname carrying a different .<cluster> suffix ALWAYS, under either value below, plus — under Ignore only — a bare hostname naming no allowed namespace here. Under Fallback, that same disallowed bare host is NOT counted foreign: it materializes into catalog.fallbackNamespace exactly like a disallowed OwnCluster host would, so it is placed rather than dropped-and-counted (see classify_hostname/decide_cluster_placement). Meaningful only when identityDefaults.cluster is set: without a cluster identity there is no notion of "foreign" and the legacy hostname-names-a-namespace placement applies (validators enforce this).
periodicRefresh
boolean
—
Opt-in: periodically re-scan the repository to keep discovered Snapshot CRs current (re-list snapshots; for object-store / volume-backed repos this recycles the <name>-discovery mover Job every refreshInterval). Off by default — the repository still bootstraps once, re-bootstraps on a spec change, and re-probes on a backup failure, but does not re-run on a timer. Enable it if you rely on discovered snapshots reflecting changes made outside this operator.
refreshInterval
string
1h
How often to re-scan when periodicRefresh: true (Go-style duration; minimum 30s, default 1h). Inert unless periodicRefresh is enabled.
Reed-Solomon ECC parity for a freshly-created repository (creation-time only, immutable after).
enabled
boolean
true
Create the repository if it does not exist yet — on by default. Repository create/connect is idempotent: pointing create at an already-initialized repository just connects to it (it never re-creates or clobbers), so the only effect of the default is that a genuinely-absent repository is bootstrapped instead of erroring. Set false for a strictly read-only or externally-managed repository the operator must never create.
encryption
string
—
kopia encryption algorithm for a freshly-created repository (creation-time only).
hash
string
—
kopia content hash algorithm for a freshly-created repository (creation-time only).
splitter
string
—
kopia object splitter for a freshly-created repository (creation-time only).
Pending EXTERNAL destructive Snapshot deletions (deletionTimestamp set, effective deletionPolicy Delete, not operator-pruned) that trip the breaker for this repository: at or above this, those deletions are HELD (finalizers wait) until acknowledged via the kopiur.home-operations.com/allow-mass-deletion annotation. 0 disables the breaker. Default 10.
Turn the probe on. Off by default — existing repositories keep their current behavior until a user opts in.
failureThreshold
integer
3
How many consecutive failing probes to require before raising the loud condition + event (default 3). Debounces a single transient blip from alarming or nudging a destructive manual recreate. Any success resets it.
interval
string
30m
How often to re-probe the backend (Go-style duration like 30m or 1h; minimum 30s, default 30m). Inert unless enabled.
This cluster's identity suffix for repositories shared across clusters (an RFC 1123 label, at most 32 characters; dots are rejected — the first . in a hostname is the namespace/cluster delimiter). When set, the default kopia identity hostname becomes <namespace>.<cluster> instead of <namespace>, so two clusters backing up same-named namespaces write distinct identities (and one cluster's retention prune can no longer touch the other's snapshots). Also exposed to hostnameExpr/ usernameExpr as the CEL variable cluster.
hostnameExpr
string
—
CEL expression for the kopia identity hostname (e.g. "namespace").
usernameExpr
string
—
CEL expression for the kopia identity username (e.g. "namespace + '-' + policyName").
Copy the UID/GID security context from a live workload rather than hard-coding it. Requires the workload to pin runAsUser (container or pod level): a UID that comes from the container image's USER line is invisible in the pod spec and cannot be inherited — the mover would silently run as its own image's UID instead. May be combined with securityContext/podSecurityContext, which override it field-wise and act as the fallback when no workload pod can be resolved.
podSecurityContext
core/v1 PodSecurityContext
—
Pod security context for the mover (notably fsGroup for group-writable restore volumes). Same layering as securityContext: highest layer, merged field-wise, and combinable with inheritSecurityContextFrom.
privilegedMode
boolean
—
Opt-in, namespace-gated privileged mode; preserves UID/GID on restore.
resources
core/v1 ResourceRequirements
—
Resource requests/limits for the mover container.
securityContext
core/v1 SecurityContext
—
Container security context for the mover; merged field-wise over the hardened base, moverDefaults, and any inherited context — this is the highest layer, so every field set here wins. Combines with inheritSecurityContextFrom: fields you set override the workload's, fields you omit are inherited, and this context stands in alone when inheritance cannot resolve a pod.
ttlSecondsAfterFinished
integer
—
Per-recipe override of Job.spec.ttlSecondsAfterFinished so finished Jobs self-GC.
Backup sources only: auto-derive the workload from the PVC this snapshot backs up.
snapshot
object
—
Restores only: inherit the identity RECORDED on the backup itself (Snapshot.status.recorded, decoded from the kopiur-meta kopia tag) — uid/gid/fsGroup the backup mover actually ran as. Needs no live workload pod, so it works on a rebuilt cluster and with target.populator. Rejected at admission on SnapshotPolicy/Maintenance (backups read the live workload; maintenance has no snapshot). Write it as snapshot: {} (an empty sub-object) — a bare snapshot: is null and rejected.
Index blobs in an epoch that trigger an advance, once older than minDuration (kopia default 20).
advanceOnSizeMiB
integer
—
Total index size in an epoch that triggers an advance, once older than minDuration (kopia default 10 MiB). Named MiB, not MB, with an explicit rename rather than the derived camelCase (advanceOnSizeMb, which reads as megabit). The unit is genuinely mebibytes — kopia's --epoch-advance-on-size-mb multiplies by 1048576, so 10 is 10485760 bytes — even though kopia's own log renders the result as "MB". That ambiguity is this field's main hazard; the API surface should not reproduce it.
checkpointFrequency
integer
—
Epochs between full index checkpoints (kopia default 7).
deleteParallelism
integer
—
Parallelism for epoch cleanup deletions (kopia default 4).
minDuration
string
—
Minimum epoch age before it may advance (kopia default 24h). A Go-style duration (6h, 90m). The advance gate — no blob count closes an epoch younger than this.
refreshFrequency
string
—
How often clients re-read epoch state (kopia default 20m). Go-style duration.
IANA timezone name applied to every consuming cron that doesn't set its own timezone (e.g. America/New_York). Set once here instead of repeating it on every SnapshotPolicy.verification, RepositoryReplication.schedule, and Maintenance.schedule cron.
How many Snapshot CRs were materialized from the catalog scan.
foreignSnapshotCount
integer
—
Snapshots in the last complete listing classified as another cluster's (see catalog.foreignSnapshots); never materialized under Ignore. As of catalog.lastRefreshAt — enable periodicRefresh to keep it current.
lastRefreshAt
string
—
RFC 3339 timestamp of the last catalog refresh.
scanRequestAttemptAt
string
—
RFC 3339 timestamp of the last bootstrap/scan attempt initiated BECAUSE OF a pending catalog-scan-requested-at token (i.e. the token arm was the reason the attempt fired). Used only to rate-limit token-driven attempts on a Ready-but-unreachable repository — it is never compared against the token for retirement (that's scanRequestHonored, by equality).
scanRequestHonored
string
—
The RFC3339 token VALUE of the kopiur.home-operations.com/catalog-scan-requested-at annotation last honored by a completed catalog scan. Compared by equality against the live annotation to decide whether a requested on-demand scan is still pending — deliberately NOT a timestamp comparison against lastRefreshAt (a periodic refresh completing after the request was made would otherwise look like it honored the request even though it started before the annotation was set).
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
Consecutive failing probes accrued; reset to zero on any success. The loud condition is raised only once this reaches the failure threshold.
firstFailureAt
string
—
RFC 3339 timestamp of the first failure in the current failing streak.
lastHealthyAt
string
—
RFC 3339 timestamp of the last successful probe (backend reachable, repo present).
lastProbeAt
string
—
RFC 3339 timestamp of the last completed probe (success or failure); drives the health_probe_due timer so the probe re-fires on cadence.
probeAttemptAt
string
—
RFC 3339 timestamp at which the last backend health probe was launched (the bootstrap Job created for it), cleared when its result is finalized. This is the launch-side rate limit, and it is what makes the probe terminate. lastProbeAt is written only at finalize, so gating the launch on that alone recycles the bootstrap Job forever: the gate destroys every Job whose completion would have cleared it (#273). Never compared against lastProbeAt — see kopiur_controller::health::probe_action.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Number of content-index blobs (kopia index list) observed at the last bootstrap.
lastObservedAt
string
—
RFC 3339 timestamp these stats were last observed.
snapshotCount
integer
—
Total snapshots present in the repository (across all identities).
totalSize
string
—
Human-readable total on-disk size (e.g. 412Gi).
totalSizeBytes
integer
—
Logical bytes under management (the integer form of total_size): the sum, over each distinct snapshot source, of the most-recent snapshot's logical size. Exposed to backup preflight as repository.sizeBytes. This is repository total size, not backend free space (object stores don't report remaining capacity).
Discriminated reference to a Repository or ClusterRepository.
adoption
enum: Adopt | Ignore
—
Whether a discovered snapshot whose resolved identity matches a live SnapshotPolicy is automatically adopted — re-attached (stamped with that policy's config label, status.origin flipped to Adopted) so GFS retention governs it and eventually prunes it, instead of it sitting in the catalog forever as an immortal discovered row.
Opt-in credential-Secret projection into each backup mover's namespace (default off).
defaultDeletionPolicy
enum: Delete | Retain | Orphan
Delete
Lifecycle of the underlying kopia snapshot when its Snapshot CR is deleted. Produced backups default to Delete; discovered snapshots are forced to Retain.
Paths/patterns kopia should skip while snapshotting.
groupBy
enum: VolumeGroupSnapshot | None
VolumeGroupSnapshot
Multi-PVC grouping strategy. Defaults to a consistent group snapshot across all PVCs; set Noneexplicitly to accept independent per-PVC snapshots, because a silent per-PVC fallback would produce inconsistent backups.
Named CEL preconditions evaluated before each backup run; opt-in (absent ⇒ no preflight). A failing check holds the Snapshot in Pending (PreflightFailed) and, after timeout, fails it.
Abort the snapshot at the first error instead of collecting and continuing (snapshot create --fail-fast; kopia default: false). This is a snapshot create argv flag, not a policy set knob, but lives beside its semantic opposites (ignore*Errors) for discoverability.
ignoreDirErrors
boolean
—
Continue the snapshot when a directory cannot be read (--ignore-dir-errors).
ignoreFileErrors
boolean
—
Continue the snapshot when a file cannot be read (--ignore-file-errors).
ignoreUnknownTypes
boolean
—
Continue past entries of unknown type (--ignore-unknown-types).
Filename/path globs to exclude from the snapshot (e.g. *.tmp, */cache/*). Absent ⇒ default_ignore_rules (OS-artifact junk: /lost+found, System Volume Information, $RECYCLE.BIN, @eaDir, .snapshot). An explicit list REPLACES the default wholesale (re-add any entries you still want); explicit ignoreRules: [] opts fully out. NOT skip_serializing_if — an explicit empty list must round-trip as [], not vanish back to "absent" (which would silently resurrect the default on the next parse).
Copy the UID/GID security context from a live workload rather than hard-coding it. Requires the workload to pin runAsUser (container or pod level): a UID that comes from the container image's USER line is invisible in the pod spec and cannot be inherited — the mover would silently run as its own image's UID instead. May be combined with securityContext/podSecurityContext, which override it field-wise and act as the fallback when no workload pod can be resolved.
podSecurityContext
core/v1 PodSecurityContext
—
Pod security context for the mover (notably fsGroup for group-writable restore volumes). Same layering as securityContext: highest layer, merged field-wise, and combinable with inheritSecurityContextFrom.
privilegedMode
boolean
—
Opt-in, namespace-gated privileged mode; preserves UID/GID on restore.
resources
core/v1 ResourceRequirements
—
Resource requests/limits for the mover container.
securityContext
core/v1 SecurityContext
—
Container security context for the mover; merged field-wise over the hardened base, moverDefaults, and any inherited context — this is the highest layer, so every field set here wins. Combines with inheritSecurityContextFrom: fields you set override the workload's, fields you omit are inherited, and this context stands in alone when inheritance cannot resolve a pod.
ttlSecondsAfterFinished
integer
—
Per-recipe override of Job.spec.ttlSecondsAfterFinished so finished Jobs self-GC.
Backup sources only: auto-derive the workload from the PVC this snapshot backs up.
snapshot
object
—
Restores only: inherit the identity RECORDED on the backup itself (Snapshot.status.recorded, decoded from the kopiur-meta kopia tag) — uid/gid/fsGroup the backup mover actually ran as. Needs no live workload pod, so it works on a rebuilt cluster and with target.populator. Rejected at admission on SnapshotPolicy/Maintenance (backups read the live workload; maintenance has no snapshot). Write it as snapshot: {} (an empty sub-object) — a bare snapshot: is null and rejected.
Checks that must all pass (AND) before the backup launches. An empty list is an inert no-op (symmetric with verification absent).
timeout
string
—
How long to hold a Snapshot in Pending while a check is unsatisfied before failing it (Go-style duration like 10m or 1h; default 10m). A zero duration (0/0s) holds indefinitely (never fail on preflight).
Externally tagged — set exactly one of: nfs · pvc · pvcSelector.
Field
Type
Default
Description
acknowledgeLiveMutation
boolean
—
Acknowledges that copyMethod: Direct + readOnly: false lets the kubelet recursively chgrp the live volume to the mover's fsGroup and make it group-writable — permanently, while the workload is running. Required for that combination alone. Ignored (not rejected) otherwise: it is an acknowledgement, never harmful to carry, and rejecting a stale one would make switching copyMethod between Direct and Snapshot/Clone a two-step edit in both directions.
Label/namespace selector matching many PVCs. Mutually exclusive with pvc/nfs.
readOnly
boolean
true
Mount the source read-only (default true; kopia only ever reads it). Set falseonly to make fsGroup work on the source. The kubelet applies fsGroup by recursively chgrp-ing the volume and adding group-write — and it skips that walk entirely on a read-only mount, which is why a mover fsGroup/fsGroupChangePolicy otherwise has no effect here. Under copyMethod: Snapshot/Clone the walk rewrites the throwaway staged PVC and never touches your data. Under copyMethod: Direct it rewrites the LIVE volume, which requires acknowledgeLiveMutation. Not supported on an nfs source: the kubelet does not apply fsGroup to in-tree NFS volumes at all, so a read-write mount would grant nothing.
sourcePathOverride
string
— maxLength 4096
What kopia records as the source path (default /pvc/<name>, or the NFS export path).
sourcePathStrategy
enum: PvcName | PvcNamespacedName
PvcName
How a selector-matched PVC's source path is derived. Only relevant for pvcSelector sources, where one recipe expands to many PVCs and each needs a distinct kopia source path. Defaults to PvcName.
Access modes for the staged PVC. Empty ⇒ copy the source PVC's modes. ReadOnlyMany pairs with snapshot-backed read-only classes (e.g. CephFS backingSnapshot); the mover mounts the staged PVC read-only to match, and rejects it at admission if a source sets readOnly: false (a read-only stage cannot be mounted read-write).
storageClassName
string
—
StorageClass for the staged PVC — the temporary PVC restored from the CSI VolumeSnapshot (copyMethod: Snapshot) or cloned from the source (copyMethod: Clone). Absent ⇒ the staged PVC copies the source PVC's class. Must belong to the same CSI driver as the source (staging fails fast on a mismatch). Flagship use: a rook-ceph CephFS class with backingSnapshot: "true", which mounts the snapshot shallowly (metadata-only, near-instant, read-only) instead of running a full subvolume clone that can take many minutes on small-file-heavy volumes.
timeout
string
—
How long each staging phase may take before the backup is failed (Go-style duration like 10m or 1h; default 10m): first the staged VolumeSnapshot becoming readyToUse (measured from its creation), then — on an Immediate-binding StorageClass — the staged PVC binding (a fresh budget measured from the PVC's creation, covering the CSI restore/clone). A transient CSI/snapshot-controller error during either wait is retried, never fatal on its own — only this deadline fails staging. A zero duration (0/0s) waits indefinitely. Raise this for backends whose snapshots or clones take long (e.g. cloud snapshots of large volumes, CephFS full clones of small-file-heavy volumes).
snapshot create --upload-limit-mb: abort the snapshot once this many MB have been uploaded (kopia default: 0 — unlimited). Named limitMb rather than uploadLimitMb to avoid the upload.uploadLimitMb stutter; like failFast, this is a snapshot create argv flag, not a policy set knob, but lives here beside its parallelism siblings.
maxParallelFileReads
integer
—
--max-parallel-file-reads: file-read concurrency within a snapshot.
maxParallelSnapshots
integer
—
--max-parallel-snapshots: how many sources snapshot concurrently.
Quick (blob-level) verification tier; absent ⇒ no quick verification. Its cron lives under quick.schedule (matching deep.schedule), see QuickVerification.
successExpr
string
—
CEL pass/fail predicate over the verify result; applies to both tiers.
verifyFilesPercent
integer
— min 0; max 255
How many files quick verifies fully (--verify-files-percent); absent leaves kopia's default.
Number of discovered Snapshot CRs adopted by the last adoption pass.
lastAdoptionAt
string
—
RFC3339 timestamp of the last adoption pass that adopted at least one snapshot.
scanRequestedAt
string
—
RFC3339 token echoing an in-flight on-demand adoption scan request for this policy's identity; cleared once honored.
scanRequestedIdentity
string
—
The resolved kopia identity the requested scan was scoped to, pinned at request time so a later identity-changing edit can't retarget an in-flight scan.
skippedByRetention
integer
— min 0
Identity-matching discovered snapshots the last adoption pass left discovered because spec.retention would prune them immediately under the effective deletionPolicy (Retain/Orphan — a CR-only prune that would re-discover and re-adopt forever). 0/absent when nothing was withheld. See the AdoptionSkippedByRetention event for the levers.
totalAdopted
integer
— min 0
Running total of Snapshot CRs ever adopted into this recipe.
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
Lifecycle of the underlying kopia snapshot when its Snapshot CR is deleted. Produced backups default to Delete; discovered snapshots are forced to Retain.
description
string
— maxLength 1024
Free-form text recorded on the kopia snapshot manifest (snapshot create --description). Per-invocation by nature — scheduled/discovered Snapshots never set this (no templated descriptions).
What the deletion of a SnapshotSchedule does to the Snapshot CRs it produced (which Kubernetes GC cascade-deletes via their ownerReference). Default Retain: the CRs are removed but their kopia snapshots survive and the catalog rediscovers them as origin: discovered. Delete opts into the cascade: each Snapshot's own deletionPolicy applies. Deliberately 2-variant (not reusing DeletionPolicy): an Orphan in cascade position would differ from Retain only in per-CR event/metric bookkeeping — an invalid state made unrepresentable. The guard's Retain is exactly DeletionPolicy::Retain's semantics (CR removed, kopia snapshot stays, catalog rediscovers it), deliberately NOT the Orphan event storm (no per-CR "orphaned" event/metric for every produced Snapshot).
The SnapshotPolicy recipe to run; absent for discovered backups.
tags
map[string]string
—
Free-form tags attached to the kopia snapshot manifest itself (snapshot create --tags), e.g. reason: pre-upgrade — durable in the repository, independent of this CR. Keys must be non-empty, colon-free (kopia splits on the first colon), and must not start with the reserved kopiur prefix; at most 10 tags, keys ≤ 63 bytes, values ≤ 256 bytes (webhook-enforced).
The mover Job backing this run; absent for discovered.
logTail
string
—
The last lines of the run's output, written by the mover at the terminal transition.
observedGeneration
integer
—
metadata.generation last reconciled, for staleness detection.
origin
enum: scheduled | manual | discovered | adopted
—
How a Snapshot came to exist. Canonical value mirrored from the kopiur.home-operations.com/origin label. Origin drives the deletion-policy default: discovered backups are forced to Retain because the operator did not create those snapshots.
The observed kopia-side pin state: Some(true) if pinned, Some(false) if unpinned, None before any pin reconcile.
preflightSince
string
—
RFC 3339 timestamp of the first reconcile where the repository was Ready but a spec.preflight check was failing. The one-shot anchor for the preflight timeout deadline (so the budget covers preflight only, not the earlier repository-not-Ready wait). Cleared once every preflight check passes, so a later failing episode gets a fresh budget rather than a stale anchor.
The mover identity recorded on the kopia snapshot itself (the kopiur-meta tag): the resolved effective uid/gid/fsGroup the backup ran as, plus its provenance. Produced runs stamp this at launch (from the same value written into the tag); discovered rows decode it from the tag during the catalog scan. Absent for pre-feature snapshots, foreign backups without the tag, or a tag this operator version cannot decode.
When the run's projected credential Secrets were reclaimed (RFC 3339); absent until the reap has run. A projected copy is only needed while a mover Job can still load it via envFrom, but it is owner-ref'd to this CR, which long outlives that Job — so without an explicit reap it would sit in the workload namespace holding live repository credentials until the CR is pruned (#240).
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
The kopiur-meta schema version this value was written under. Writers emit the lowest schema that represents the data (currently KOPIUR_META_SCHEMA_V1); readers reject only a schema NEWER than they understand (degrading to recorded-absent, never an error).
src
enum: inherited | explicit | defaults | unknown
required
Which layer pinned the recorded identity. Only inherited means the identity tracked the workload.
fsGroup
integer
—
The resolved pod-level fsGroup the mover ran with (the hardened default is 65532). Absent = no layer set one.
gid
integer
—
The resolved effective runAsGroup the mover ran as at backup time (container runAsGroup, else pod). Absent = image-determined.
uid
integer
—
The resolved effective runAsUser the mover ran as at backup time (container runAsUser, else pod). Absent = no layer pinned a UID, so the mover's UID was image-determined.
The recipe's spec.credentialProjection as it stood for this run. The deletion path re-projects the mover's credentials, but the opt-in lives on the SnapshotPolicy — which a user may delete first. Pinning it here lets the finalizer honor the opt-in that was actually in force, instead of reading an absent recipe as "projection off" and blocking on a Secret that was never meant to be namespace-local (#255). Absent only on a Snapshot that predates the pin or never ran; a run always writes it, including enabled: false, so absent stays distinguishable from off.
The username@hostname:path identity recorded for this snapshot.
kopiaSnapshotID
string
required
kopia's snapshot ID — the handle the finalizer uses to delete content.
description
string
—
The kopia snapshot description (snapshot create --description), when one is recorded and non-empty. For discovered rows this is copied from the repository listing TRUNCATED to 1024 bytes (char-boundary-safe) — the value is foreign-writer-controlled and must never fail the CR write.
The resolved capture method (Snapshot or Clone) that produced this stage.
pvcName
string
—
Name of the staged PersistentVolumeClaim the mover mounts in place of the live source PVC.
ready
boolean
—
true once the stage is ready for the mover.
stagingTimeoutSeconds
integer
—
The resolved spec.staging.timeout (seconds) pinned when the stage was stamped, so the running-Job staged-PVC bind watchdog never re-resolves a policy that may have been edited or deleted mid-run. 0 = wait indefinitely.
storageClassName
string
—
StorageClass of the staged PVC — spec.staging.storageClassName when set, else the source PVC's class. Pinned for observability (e.g. confirming a CephFS shallow-clone class actually took effect).
volumeSnapshotName
string
—
Name of the VolumeSnapshot created from the source PVC (copyMethod: Snapshot only).
Stamped onto every produced Snapshot at creation (spec.onScheduleDelete) and consulted by the Snapshot finalizer when the owning schedule is gone or replaced. Absent resolves to Retain.
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
The Snapshot CR this slot produced, when one was created.
timezone
string
—
The IANA timezone the cron was evaluated in when this slot was pinned (nextSchedule only). Recorded so the controller can detect an effective-timezone change — a spec.schedule.timezone edit or a change to the target repository's scheduleDefaults.timezone — and invalidate the pinned wall-clock slot, recomputing it in the new zone. Absent on legacy pins written before this field existed (treated as "unchanged").
The Snapshot CR this slot produced, when one was created.
timezone
string
—
The IANA timezone the cron was evaluated in when this slot was pinned (nextSchedule only). Recorded so the controller can detect an effective-timezone change — a spec.schedule.timezone edit or a change to the target repository's scheduleDefaults.timezone — and invalidate the pinned wall-clock slot, recomputing it in the new zone. Absent on legacy pins written before this field existed (treated as "unchanged").
The Snapshot CR this slot produced, when one was created.
timezone
string
—
The IANA timezone the cron was evaluated in when this slot was pinned (nextSchedule only). Recorded so the controller can detect an effective-timezone change — a spec.schedule.timezone edit or a change to the target repository's scheduleDefaults.timezone — and invalidate the pinned wall-clock slot, recomputing it in the new zone. Absent on legacy pins written before this field existed (treated as "unchanged").
Access modes for the new PVC; empty defaults to ReadWriteOnce. Closed enum in the schema; a non-canonical value persisted before enforcement decodes as PvcAccessMode::Unknown and is rejected per-CR with the value quoted (webhook + reconciler, via validate_access_modes) instead of poisoning the typed watcher.
capacity
string
—
Requested size of the new PVC (e.g. 100Gi).
storageClassName
string
—
StorageClass for the new PVC; absent uses the cluster default.
Copy the UID/GID security context from a live workload rather than hard-coding it. Requires the workload to pin runAsUser (container or pod level): a UID that comes from the container image's USER line is invisible in the pod spec and cannot be inherited — the mover would silently run as its own image's UID instead. May be combined with securityContext/podSecurityContext, which override it field-wise and act as the fallback when no workload pod can be resolved.
podSecurityContext
core/v1 PodSecurityContext
—
Pod security context for the mover (notably fsGroup for group-writable restore volumes). Same layering as securityContext: highest layer, merged field-wise, and combinable with inheritSecurityContextFrom.
privilegedMode
boolean
—
Opt-in, namespace-gated privileged mode; preserves UID/GID on restore.
resources
core/v1 ResourceRequirements
—
Resource requests/limits for the mover container.
securityContext
core/v1 SecurityContext
—
Container security context for the mover; merged field-wise over the hardened base, moverDefaults, and any inherited context — this is the highest layer, so every field set here wins. Combines with inheritSecurityContextFrom: fields you set override the workload's, fields you omit are inherited, and this context stands in alone when inheritance cannot resolve a pod.
ttlSecondsAfterFinished
integer
—
Per-recipe override of Job.spec.ttlSecondsAfterFinished so finished Jobs self-GC.
Backup sources only: auto-derive the workload from the PVC this snapshot backs up.
snapshot
object
—
Restores only: inherit the identity RECORDED on the backup itself (Snapshot.status.recorded, decoded from the kopiur-meta kopia tag) — uid/gid/fsGroup the backup mover actually ran as. Needs no live workload pod, so it works on a rebuilt cluster and with target.populator. Rejected at admission on SnapshotPolicy/Maintenance (backups read the live workload; maintenance has no snapshot). Write it as snapshot: {} (an empty sub-object) — a bare snapshot: is null and rejected.
Delete files in the target that are not present in the snapshot (exact mirror); off by default. Wired to kopia's --[no-]delete-extra (previously a silent no-op — see issue #216 gap sweep).
ignoreErrors
boolean
—
--[no-]ignore-errors: ignore all restore errors and continue (kopia default false).
ignorePermissionErrors
boolean
—
Continue past permission errors during restore (default true).
overwriteDirectories
boolean
—
--[no-]overwrite-directories: overwrite existing directories in the target (kopia default true).
overwriteFiles
boolean
—
--[no-]overwrite-files: overwrite existing files in the target (kopia default true).
overwriteSymlinks
boolean
—
--[no-]overwrite-symlinks: overwrite existing symlinks in the target (kopia default true).
What to do when the resolved source matches no snapshot. Defaults to Fail (fail-closed) so an explicit restore can never silently no-op; choose Continue to provision an empty volume instead (deploy-or-restore).
waitTimeout
string
—
How long to wait for the source snapshot to appear before giving up (e.g. 5m).
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
Previous lease strings still recognized as SELF (a migration path): when kopia's currently-recorded maintenance owner matches the owner derived from one of these aliases, a run treats the lease as its own — it claims it and re-stamps owner, upgrading the recorded owner to the current format. The operator populates this when a repository's managed Maintenance moves to a cluster-qualified lease (identityDefaults.cluster), so the transition never yields the lease to what merely looks like a foreign owner.
takeoverPolicy
enum: Never | PromptCondition | Force
Never
What to do when the lease is already held by a different owner.
Copy the UID/GID security context from a live workload rather than hard-coding it. Requires the workload to pin runAsUser (container or pod level): a UID that comes from the container image's USER line is invisible in the pod spec and cannot be inherited — the mover would silently run as its own image's UID instead. May be combined with securityContext/podSecurityContext, which override it field-wise and act as the fallback when no workload pod can be resolved.
podSecurityContext
core/v1 PodSecurityContext
—
Pod security context for the mover (notably fsGroup for group-writable restore volumes). Same layering as securityContext: highest layer, merged field-wise, and combinable with inheritSecurityContextFrom.
privilegedMode
boolean
—
Opt-in, namespace-gated privileged mode; preserves UID/GID on restore.
resources
core/v1 ResourceRequirements
—
Resource requests/limits for the mover container.
securityContext
core/v1 SecurityContext
—
Container security context for the mover; merged field-wise over the hardened base, moverDefaults, and any inherited context — this is the highest layer, so every field set here wins. Combines with inheritSecurityContextFrom: fields you set override the workload's, fields you omit are inherited, and this context stands in alone when inheritance cannot resolve a pod.
ttlSecondsAfterFinished
integer
—
Per-recipe override of Job.spec.ttlSecondsAfterFinished so finished Jobs self-GC.
Backup sources only: auto-derive the workload from the PVC this snapshot backs up.
snapshot
object
—
Restores only: inherit the identity RECORDED on the backup itself (Snapshot.status.recorded, decoded from the kopiur-meta kopia tag) — uid/gid/fsGroup the backup mover actually ran as. Needs no live workload pod, so it works on a rebuilt cluster and with target.populator. Rejected at admission on SnapshotPolicy/Maintenance (backups read the live workload; maintenance has no snapshot). Write it as snapshot: {} (an empty sub-object) — a bare snapshot: is null and rejected.
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
The backend to mirror to; must differ from the source's backend (webhook-enforced). kopia repository sync-to is a blob-level copy: the destination inherits the source repository's format and encryption password verbatim, so there is no separate destination password to configure. The destination backend's own access credentials (e.g. S3 keys) ride its auth.secretRef, which — like the source's — must live in this CR's namespace.
Google Drive via kopia's native gdrive provider (service-account JSON). kopia marks this provider experimental / not maintained, and a native gdrive repository is not interchangeable with an rclone-backed Drive remote.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Secret holding the Google service-account JSON used to reach the folder, read by the well-known key KOPIA_GDRIVE_CREDENTIALS. Absent means kopia falls back to ambient credentials (GOOGLE_APPLICATION_CREDENTIALS or instance metadata).
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Secret holding the rclone.conf that defines the remote referenced by remote_path.
startupTimeout
string
—
How long kopia waits for its embedded rclone serve to come up before failing the connect, as a Go duration (e.g. 2m). kopia's default is 15s; raise it for slow remotes whose repository metadata/indexes load through the rclone/WebDAV bridge and take longer than the default budget.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Namespace of the Secret; absent = same namespace as the referrer. A ClusterRepository is cluster-scoped and has no namespace of its own, so when IT reads the Secret (to connect, to bootstrap, or to run its repository server) an absent namespace means the operator's namespace (KOPIUR_NAMESPACE). A workload mover (Snapshot/Restore/Maintenance) still needs the Secret in its OWN namespace — envFrom is namespace-local — so put it there, or use credentialProjection, which needs this namespace set EXPLICITLY to know what to copy. Set it whenever anything other than the operator itself reads the Secret.
Copy the UID/GID security context from a live workload rather than hard-coding it. Requires the workload to pin runAsUser (container or pod level): a UID that comes from the container image's USER line is invisible in the pod spec and cannot be inherited — the mover would silently run as its own image's UID instead. May be combined with securityContext/podSecurityContext, which override it field-wise and act as the fallback when no workload pod can be resolved.
podSecurityContext
core/v1 PodSecurityContext
—
Pod security context for the mover (notably fsGroup for group-writable restore volumes). Same layering as securityContext: highest layer, merged field-wise, and combinable with inheritSecurityContextFrom.
privilegedMode
boolean
—
Opt-in, namespace-gated privileged mode; preserves UID/GID on restore.
resources
core/v1 ResourceRequirements
—
Resource requests/limits for the mover container.
securityContext
core/v1 SecurityContext
—
Container security context for the mover; merged field-wise over the hardened base, moverDefaults, and any inherited context — this is the highest layer, so every field set here wins. Combines with inheritSecurityContextFrom: fields you set override the workload's, fields you omit are inherited, and this context stands in alone when inheritance cannot resolve a pod.
ttlSecondsAfterFinished
integer
—
Per-recipe override of Job.spec.ttlSecondsAfterFinished so finished Jobs self-GC.
Backup sources only: auto-derive the workload from the PVC this snapshot backs up.
snapshot
object
—
Restores only: inherit the identity RECORDED on the backup itself (Snapshot.status.recorded, decoded from the kopiur-meta kopia tag) — uid/gid/fsGroup the backup mover actually ran as. Needs no live workload pod, so it works on a rebuilt cluster and with target.populator. Rejected at admission on SnapshotPolicy/Maintenance (backups read the live workload; maintenance has no snapshot). Write it as snapshot: {} (an empty sub-object) — a bare snapshot: is null and rejected.
--delete: prune destination-only blobs so the mirror is an exact copy (kopia default false — additive sync, never removes destination content). Named deleteExtra, not kopia's bare delete: a delete: true key on backup-adjacent YAML is dangerously ambiguous at a glance. CAUTION: with this true, blobs present at the destination but absent from the source are deleted on every run.
maxDownloadSpeedBytesPerSecond
integer
—
--max-download-speed: cap read throughput from the source, in bytes/sec (kopia default: unlimited).
maxUploadSpeedBytesPerSecond
integer
—
--max-upload-speed: cap write throughput to the destination, in bytes/sec (kopia default: unlimited).
mustExist
boolean
—
--[no-]must-exist: fail the sync instead of initializing the destination's repository-format blob (kopia default false — sync-to may create the destination layout on first run).
parallel
integer
— min 0
--parallel: number of concurrent blob-copy workers (kopia default 1 — sequential, the root cause of #216's multi-week seed times to R2).
times
boolean
—
--[no-]times: synchronize blob modification times to the destination, when the destination backend supports it (kopia default true).
update
boolean
—
--[no-]update: update blobs already present at the destination when the source copy is newer (kopia default true).
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
message
string
required
message is a human readable message indicating details about the transition. This may be an empty string.
reason
string
required
reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
status
string
required
status of the condition, one of True, False, Unknown.
type
string
required
type of condition in CamelCase or in foo.example.com/CamelCase.
observedGeneration
integer
—
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditionsx.observedGeneration is 9, the condition is out of date with respect to the current state of the instance.