Catalog

A Catalog is a namespace’s shared catalog: one Corrosion cluster that every Library in the namespace writes into. Declare one Catalog in a namespace. It runs the catalog pod, the one durable member of that cluster, which holds the namespace’s catalog on a durable claim and reports what it holds over the bus. It sizes that claim, the claim every Library’s Jobs take, and the claim every screen’s agent runs on, and it owns the pod, its claim, and the namespace’s catalog Service and EndpointSlice.

A Catalog name is at most 32 characters, because library-operator builds the names of the objects it creates from it, and Kubernetes limits some of those names to 63 characters.

Each catalog agent holds the whole namespace’s catalog, because the cluster gossips every row to every peer. So one size covers the whole namespace, on the Catalog, in place of a size on each Library.

apiVersion: library.liken.sh/v1alpha1
kind: Catalog
metadata:
  name: media
  namespace: media
spec:
  storage:
    size: 1Gi

A namespace has exactly one Catalog. A Library in a namespace with no Catalog waits until one exists, and more than one Catalog marks every Catalog in the namespace Blocked and runs no cluster. An empty storageClassName binds each catalog volume to the cluster’s default StorageClass. A claimName names an existing claim for the catalog pod to mount in place of the one the operator provisions. A SQLite file on a claim served over NFS can corrupt when its node is lost, so prefer a StorageClass that binds node-local storage. A spec.screens.storageClassName classes the screens’ claims apart from the durable one, and omitted, the cluster’s default binds them too.

The namespace’s shared catalog. Declare one Catalog in a namespace, and every Library in it writes into that catalog.

spec

Where the catalog is stored and how large each agent’s copy is.

Field Type Required Description
storage object yes The catalog of record every other agent copies, held on one claim that every durable copy mounts. The size is also the size of every working copy, and the class is the default class for the progress and libraries claims.
progress object no The claim the progress store runs on: who watched what, and how far. Each field defaults to the field of the same name under storage, so a Catalog that names neither keeps the store on the catalog’s class at the catalog’s size.
libraries object no The claim each Library’s Jobs run on, one Job at a time, named after the Library with the suffix -catalog. Each is a working copy of the whole catalog that a Job rebuilds from the catalog of record, so a namespace that keeps the catalog of record on a durable class keeps these on a node-local class such as local-path. On a per-node class the claim is ReadWriteOncePod, so Kubernetes admits one pod of it in the cluster at a time.
screens object no The settings every screen pod in the namespace takes.
jellyfin object no The Jellyfin server this namespace keeps playback progress with, in both directions. A Catalog that names one makes the operator run a pod and a Service named after the Catalog with the suffix -jellyfin, beside the progress store. The pod records what Jellyfin reports into the progress store, and writes what a screen played back to Jellyfin. Omitted, the operator runs neither and deletes the previous pair.

spec.storage

The catalog of record every other agent copies, held on one claim that every durable copy mounts. The size is also the size of every working copy, and the class is the default class for the progress and libraries claims.

Field Type Required Description
size string no The size of each agent’s catalog volume. Small by default. Default: 1Gi.
storageClassName string no The StorageClass each agent’s catalog volume binds to. Omitted, the cluster’s default binds it. A class the per-node driver serves, whose provisioner is per-node.liken.sh, lets the operator run more than one copy of the catalog, because every copy then holds a directory of its own on the node where it runs. The operator reads the provisioner of the class and never matches its name.
claimName string no An existing PersistentVolumeClaim in this namespace for every copy of the catalog to mount, in place of the one the operator provisions. The operator creates no claim and no volume when it is set.
replicas integer no How many durable copies of the catalog the operator runs for the namespace. The copies are peers that Corrosion syncs from one another, and every copy mounts one claim of storage.size, named after the Catalog with the suffix -catalog. More than one copy needs a per-node class. On any other class the operator runs one copy, and the Ready condition is False with the reason ClassNotPerNode. No two copies share a node, so a copy the scheduler cannot place stays Pending and the Catalog is not Ready. A copy on a node that stays NotReady for ten minutes is deleted and started again elsewhere. It takes the claim with it only on a class that binds the claim to the node. A copy taken away by a lower count loses its pod alone, because the claim serves the copies that remain. Default: 1.

spec.progress

The claim the progress store runs on: who watched what, and how far. Each field defaults to the field of the same name under storage, so a Catalog that names neither keeps the store on the catalog’s class at the catalog’s size.

Field Type Required Description
size string no The size of the progress claim, in a binary unit such as 256Mi. The progress rows are small next to the catalog, so a namespace that keeps both central stores on a durable class names a smaller size here. Omitted, the claim takes storage.size. A size change applies to a new claim, not an existing one, because a bound claim’s spec is immutable. Delete the existing claim, and the next pass creates it at the new size.
storageClassName string no The StorageClass the progress claim binds to. Omitted, the claim takes storage.storageClassName, and when that is also omitted the cluster’s default binds it. A per-node class lets the operator run more than one copy of the progress store, on the same terms as storage.storageClassName.
replicas integer no How many durable copies of the progress store the operator runs for the namespace, on the same terms as storage.replicas, against the class this block names. Every copy mounts one claim, named after the Catalog with the suffix -progress. More than one copy needs a per-node class here as well, and on any other class the Ready condition is False with the reason ClassNotPerNode. The first copy records what crosses the bus, and every copy after it holds the rows. Default: 1.

spec.libraries

The claim each Library’s Jobs run on, one Job at a time, named after the Library with the suffix -catalog. Each is a working copy of the whole catalog that a Job rebuilds from the catalog of record, so a namespace that keeps the catalog of record on a durable class keeps these on a node-local class such as local-path. On a per-node class the claim is ReadWriteOncePod, so Kubernetes admits one pod of it in the cluster at a time.

Field Type Required Description
storageClassName string no The StorageClass a Library’s catalog claim binds to. Omitted, it takes storage.storageClassName, and when that is also omitted the cluster’s default binds it. There is no size here: every agent holds the whole catalog, so the claim takes storage.size.

spec.screens

The settings every screen pod in the namespace takes.

Field Type Required Description
storageClassName string no The StorageClass both of a screen’s claims bind to. Omitted, the cluster’s default binds them. A node-local class such as local-path is the right one, because a screen pod is already pinned to the machine that holds its display. On such a class, a screen the scheduler refuses for five minutes loses its pod and both claims, and the next pass creates them again where the display is. On a per-node class the claims pin the pod to no node, and the operator never deletes them.
artCache object no The volume each screen’s browser keeps its scaled art on: posters, backdrops, episode stills, logos, and headshots. A screen that restarts draws the wall from art it already scaled.

spec.screens.artCache

The volume each screen’s browser keeps its scaled art on: posters, backdrops, episode stills, logos, and headshots. A screen that restarts draws the wall from art it already scaled.

Field Type Required Description
size string no The size of each screen’s art claim, in a binary unit such as 2Gi. The browser is told to keep 128 MiB under it. A size change applies to new screens, not existing ones, because a bound claim’s spec is immutable. Delete an existing screen’s claim, and the next pass creates it at the new size. Default: 2Gi.

spec.jellyfin

The Jellyfin server this namespace keeps playback progress with, in both directions. A Catalog that names one makes the operator run a pod and a Service named after the Catalog with the suffix -jellyfin, beside the progress store. The pod records what Jellyfin reports into the progress store, and writes what a screen played back to Jellyfin. Omitted, the operator runs neither and deletes the previous pair.

Field Type Required Description
url string yes The Jellyfin server’s address on the cluster network, such as http://jellyfin.jellyfin.svc:8096 .
secretRef object yes The Secret in this namespace that holds a Jellyfin API key, and the key inside it. The API key is one an administrator issues on the server, because the pod writes the playback position of any user.

spec.jellyfin.secretRef

The Secret in this namespace that holds a Jellyfin API key, and the key inside it. The API key is one an administrator issues on the server, because the pod writes the playback position of any user.

Field Type Required Description
name string yes The Secret’s name, in this Catalog’s own namespace.
key string no The key inside that Secret. When omitted, token. Default: token.

status

The catalog cluster that this Catalog runs. Only the library operator writes it.

Field Type Required Description
members []string no The pods that are members of the namespace’s catalog cluster: the durable copies of the catalog, the pods of the Jobs that are running, and the screen pods.
storageSize string no The storage size the agents were given.
replicas object no The durable copies of the namespace’s two stores: for each, the count that is up beside the count the Catalog asks for.
screens []object no One entry per screen pod in the namespace, in Player order: the Player it draws for, the claim its catalog agent runs on, the claim its art cache is on, the node it runs on, and its phase. A screen whose namespace has no single Catalog runs on emptyDirs and names neither claim.
jellyfin object no The one-time backfill of the progress the namespace’s Jellyfin server already held before this operator recorded it: the server it ran against, its current phase, and when it finished. Present only while spec.jellyfin names a server.
conditions []object no The typed observations the operator keeps on this Catalog, in the standard Kubernetes form. Ready is True when every durable copy of the catalog runs with every container ready. It is False with the reason ClassNotPerNode when the Catalog asks for copies of a store on a class that cannot hold more than one, and otherwise False with the reason PodPending, PodFailed, or ManyCatalogs, naming the first copy that is not up.

status.replicas

The durable copies of the namespace’s two stores: for each, the count that is up beside the count the Catalog asks for.

Field Type Required Description
catalog object no The copies of the catalog.
progress object no The copies of the progress store.

status.replicas.catalog

The copies of the catalog.

Field Type Required Description
ready integer no How many copies run with every container ready.
wanted integer no How many copies the Catalog asks for, from storage.replicas. The count is the one the Catalog states, so a Catalog whose class cannot hold more than one copy reports the copies it asked for beside the one that is up.

status.replicas.progress

The copies of the progress store.

Field Type Required Description
ready integer no How many copies run with every container ready.
wanted integer no How many copies the Catalog asks for, from progress.replicas.

status.screens[]

One entry per screen pod in the namespace, in Player order: the Player it draws for, the claim its catalog agent runs on, the claim its art cache is on, the node it runs on, and its phase. A screen whose namespace has no single Catalog runs on emptyDirs and names neither claim.

Field Type Required Description
player string no The Player the screen draws for.
claim string no The claim the screen’s catalog agent runs on, or empty for a screen on an emptyDir.
artClaim string no The claim the screen’s art cache is on, or empty for a screen on an emptyDir.
node string no The node the screen pod runs on. On a node-local class it is the node both of its claims are bound to.
phase string no The screen pod’s phase, as the kubelet reports it.

status.jellyfin

The one-time backfill of the progress the namespace’s Jellyfin server already held before this operator recorded it: the server it ran against, its current phase, and when it finished. Present only while spec.jellyfin names a server.

Field Type Required Description
server string no The server address the backfill ran against, from spec.jellyfin.url. A Catalog that changes the address runs the backfill again against the new one.
backfill string no The backfill’s current phase. Pending waits for every durable copy of the progress store to be up. Running means the Job is running. Failed means the Job failed past its backoff limit, and the operator deletes it and creates it again after a wait that doubles each time. Finished means the Job exited zero. To run the backfill again against the same server, clear status.jellyfin.
backfilled string no When the backfill finished, in UTC. Absent until it did.

status.conditions[]

The typed observations the operator keeps on this Catalog, in the standard Kubernetes form. Ready is True when every durable copy of the catalog runs with every container ready. It is False with the reason ClassNotPerNode when the Catalog asks for copies of a store on a class that cannot hold more than one, and otherwise False with the reason PodPending, PodFailed, or ManyCatalogs, naming the first copy that is not up.

Field Type Required Description
type string yes The check this entry reports, in CamelCase. It is the key of this list. Pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$.
status string yes The condition’s status. True is the healthy status, and Unknown means the operator cannot tell yet. One of: True, False, Unknown.
observedGeneration integer no The metadata.generation that this condition reflects.
reason string no One CamelCase word for why the condition has this status, for a program to match on. Pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$.
message string no The same reason, as a sentence for a person to read.
lastTransitionTime string yes When the status last changed. A change of the reason or the message alone does not move it.