Feature Specification: Component peertube

Created: 2026-10-07

Status: Implemented

Input: Reconstructed from the code at 093e68b6f (core/components/operator/peertube/default.nix, core/components/operator/peertube/oidc-plugin.nix, core/components/operator/peertube/oidc-plugin-install.nix, core/components/operator/peertube/test.nix, core/components/operator/peertube/test-script.py, core/components/operator/peertube/test-secrets.nix, core/components/operator/default.nix, core/components/operator/app-resources.nix, core/components/operator/operator-facing-options.csv, core/components/operator/testing/lib.nix, core/checks/nix-unit-tests.nix), pull requests #1384, #1597, #1651, #1694, #1724, #1753, #1835, #1853, #1911.

User Scenarios & Testing (mandatory)

User Story 1 - Run a PeerTube instance for a group (Priority: P1)

An operator enables peertube in the operator group. The component places resource requests for single sign-on, mail, cache, database, object storage, workload identity, TLS and public ports. The setups route each request to a provider in the deployment. The node then serves PeerTube at https://<subdomain>.<domain>, and accepts RTMP and RTMPS live streams. The family spec ../spec.md gives what all operator applications share.

Why this priority: the component exists to deploy this application.

Independent Test: test-peertube-service (core/components/operator/peertube/test.nix). It posts a video in the browser, finds the video in garage, and backs up the database.

Acceptance Scenarios:

  1. Given peertube.enable = true, When the component evaluates, Then it requests peertube-oidc, peertube-smtp, peertube-cache, peertube-db, spiffe, tls, ingest, peertube-bucket and codez.
  2. Given peertube.enable = false, When the component evaluates, Then it requests no resources.
  3. Given the ingest request, When the firewall resolves, Then TCP ports 80, 443, 1935 and 1936 are public.

User Story 2 - Log in with the group's single sign-on (Priority: P1)

A user of the group logs in to PeerTube with the account from the group directory. PeerTube has no declarative plugin option and its own installer needs the network. So the component installs the auth-openid-connect plugin offline and writes the plugin row and settings into the database.

Why this priority: SSO is the only path from the group directory to the application.

Independent Test: the subtests "Authelia registered the peertube OIDC client with the extended request", "Authelia serves discovery" and "OIDC issuer + secret" of test-peertube-service. No nix-unit check covers the plugin install.

Acceptance Scenarios:

  1. Given an authelia provider, When the peertube-oidc request resolves, Then the client peertube has the redirect URIs https://<domain>/plugins/auth-openid-connect/router/code-cb and https://<domain>/plugins/auth-openid-connect/0.x/router/code-cb.
  2. Given PeerTube serves HTTP, When peertube-oidc-plugin-install runs, Then it copies the plugin into the live plugins store and upserts the plugin row with the issuer, client and scopes.
  3. Given the client secret credential arrives empty, When the install unit fails, Then systemd retries it after 30 s.

User Story 3 - Configure the instance policy (Priority: P2)

An operator sets instance policy through peertube.settings. The option type comes from PeerTube's own config/config-schema.json, narrowed to the keys in operatorSettings. What the operator writes goes to services.peertube.settings.

Why this priority: operators run their instance by their own policy, but the instance works with the defaults.

Independent Test: nix-unit-lib-option-selection, nix-unit-lib-json-schema and nix-unit-lib-nixos-service-options cover the helpers. No check evaluates the peertube selection itself.

Acceptance Scenarios:

  1. Given a selected key such as signup.enabled, When the operator sets it, Then the value reaches production.yaml.
  2. Given federation.prevent_ssrf or user.disable_root_auth, When the operator sets it, Then the schema refuses the value.
  3. Given an operator value for a services.peertube option that codez also sets, When the node evaluates, Then the evaluation fails and names the option.

User Story 4 - Store media in garage and back up the database (Priority: P3)

PeerTube writes videos and playlists to garage, and serves them from the bucket web endpoint. The component backs up the database to the peertube-backups bucket with restic.

Why this priority: media in object storage keeps the node small, and recovery needs the database.

Independent Test: the "Find video in garage" and "Backing up {db}" subtests of test-peertube-service.

Acceptance Scenarios:

  1. Given the garage request, When PeerTube uploads a video, Then it uses the physical buckets of peertube-videos and peertube-playlists, and base_url is the bucket web URL.
  2. Given a routed postgresql provider, When the backup runs, Then pg_dump connects to the remote host with the rendered password and the contract sslMode.

Edge Cases

  • No redis provider resolves. services.peertube.redis.createLocally stays at its mkDefault true and a local redis runs.
  • No postgresql provider resolves. services.peertube.database.createLocally stays at its mkDefault true and a local postgres runs.
  • The contract result has a verify-* sslMode. An assertion fails the evaluation, because the result has no CA path for node's pg client.
  • A password file is not rendered yet. peertube.service is ordered after openbao-agent-default.service, and the postgresql request gates the peertube unit.
  • A rotated redis or database password reaches PeerTube only on its next start.
  • The install unit runs before PeerTube creates its storage tree. The unit waits for the HTTP listener, because a root-made storage ancestor makes PeerTube fail with EACCES.

Requirements (mandatory)

Functional Requirements

  • FR-001: The component MUST declare name = "PeerTube", package = pkgs.peertube, the icon client/src/assets/images/logo.svg from pkgs.peertube.src, and optional = true.
  • FR-002: The component MUST offer enable, subdomain (default peertube) and the hidden domain from sharedOptions.nix, and expose only settings of services.peertube.
  • FR-003: The component MUST type settings with the pinned peertube-config-schema (draft 2020-12), narrowed by operatorSettings: an allow-list at the top layer and deny-lists in federation, tracker, user and client.
  • FR-004: The component MUST request an oidc client peertube with both plugin callback URIs and the scopes openid, profile, email, and set FEDIVERSITY_OIDC_ISSUER on peertube.service.
  • FR-005: peertube-oidc-plugin-install MUST read the client secret only as its own systemd credential, and MUST order after peertube.service with Wants=, not Requires=.
  • FR-006: The component MUST request smtp with the sender noreply@<domain>, use STARTTLS on the relay (tls = false, disable_starttls = false), and load the password as the credential smtp-password.
  • FR-007: The component MUST request redis with namespace = "peertube", set enable_tls from the tls field of the contract result, with the CA from its caFile field (/var/lib/openbao-init/pki-ca.pem), and load the password from database/static-creds/<namespace> (<namespace>-<deployment namespace> in a tenant deployment) as the credential redis-password.
  • FR-008: The component MUST request postgresql with database, consumer and user peertube, password file owner peertube and mode 0400, and set settings.database.ssl when sslMode is not disable.
  • FR-009: The component MUST request garage through mkGarageRequest with the website buckets peertube-videos and peertube-playlists, and render AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY into the peertube env file through envSecrets.
  • FR-010: The component MUST request spiffe, ssl for domain, and firewall public TCP ports 80, 443, 1935 and 1936.
  • FR-011: The codez nixos-module request MUST enable services.peertube with configureNginx, listenWeb = 443, enableWebHttps, and serviceEnvironmentFile = /run/openbao-secrets/peertube.env.
  • FR-012: The component MUST request a streamingBackup of the postgres database to the peertube-backups bucket with the restic provider.
  • FR-013: The redis and postgresql requests MUST contain only data. The codez module MUST render their clients (cache.nix, database.nix) from contracts.redis.results.peertube.default and contracts.postgresql.results.peertube.default, and MUST render nothing when the result is absent.

Key Entities

  • peertube-oidc-plugin-install.service: the root oneshot that installs the offline plugin and seeds its plugin row.
  • oidc-plugin.nix: the plugin directory, built without network from a vendored package-lock.json.
  • /run/openbao-secrets/peertube.env: the env file with the S3 keys.
  • peertube-secret: the vars-backend generateFiles instance with the file secret (openssl rand -hex 32, owned by peertube), for services.peertube.secrets.secretsFile. It is minted for each deployment and kept in OpenBao KV. peertube starts after generate-vars and openbao-agent-default, and restarts when the agent renders a different value.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: test-peertube-service passes. A video posted in the browser is in garage, and the backup works.
  • SC-002: nix-unit-setups-operator-config, nix-unit-setups-postgresql-render-gate and nix-unit-setups-valkey-gate pass.
  • SC-003: After one run of the install unit, the plugin settings carry the display name Fediversity SSO.

Assumptions

  • The operator group has the shared valkey, smtp, lldap and authelia components. See ../spec.md.
  • The schema comes from a branch pin of PeerTube develop, because no release ships the file. A rename of a selected key fails the evaluation.
  • PeerTube's PEERTUBE_SMTP_* env overrides are inert under NixOS, so SMTP goes through settings.smtp.

Known Deviations

  • mkGarageRequest gives each website bucket a CORS rule for GET from *. A TODO says this is too broad.