Feature Specification: Component pixelfed
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/components/operator/pixelfed/ and the shared operator component code at 14504f80d, pull requests #1384, #1519, #1651, #1724, #1743, #1744, #1747, #1751, #1763, #1794, #1798, #1835, #1911.
User Scenarios & Testing (mandatory)
User Story 1 - Run a Pixelfed instance for a group (Priority: P1)
An operator enables pixelfed in the operator group. The component requests single sign-on, mail, cache, database, object storage, generated secrets, workload identity, TLS and public ports, and the setups route each request to a provider. The node serves Pixelfed on its own subdomain. The family spec ../spec.md gives what all operator applications share.
Why this priority: The component exists to deploy this application.
Independent Test: A VM test creates an account, posts an image, checks that the image comes from garage, and backs up the database.
Acceptance Scenarios:
- Given the component is enabled, When it evaluates, Then it places all of its requests. When it is disabled, it places none.
- Given no operator value for open registration, When the node evaluates, Then registration is open.
User Story 2 - Log in with the group's single sign-on (Priority: P1)
A user logs in with the account from the group directory. Pixelfed has generic OIDC without discovery, so the component gives it each endpoint from the contract result.
Why this priority: SSO is the only path from the group directory to the application.
Independent Test: The VM test checks the endpoints and the client secret, and logs in the directory administrator through authelia.
Acceptance Scenarios:
- Given the OIDC request, When the secrets render, Then the client secret comes from OpenBao and is not in the Nix store.
- Given the directory administrator, When it logs in, Then Pixelfed accepts the name, because the setups name the administrator after the group and not with a name that Pixelfed reserves.
User Story 3 - Configure the instance policy (Priority: P2)
An operator sets the instance policy and the maximum upload size. The settings type comes from the configuration schema of Pixelfed, narrowed to a selection of names.
Why this priority: The instance works with the defaults.
Independent Test: The VM test checks that an operator value reaches the service. Unit tests cover the schema helpers.
Acceptance Scenarios:
- Given a value with a space or a shell special character, When the component writes it, Then it reads back the same.
- Given the operator closes registration, When the node evaluates, Then the operator value wins over the component default.
User Story 4 - Store media in garage and back up the database (Priority: P3)
Pixelfed writes media to its garage bucket, and nginx proxies the public media path to the bucket. The component backs up the database with restic.
Why this priority: Media in object storage keeps the node small, and recovery needs the database.
Independent Test: The storage and backup parts of the VM test.
Acceptance Scenarios:
- Given the backup runs, Then it dumps the Pixelfed database.
- Given a posted image, When a client fetches its public media path from the domain of the instance, Then nginx serves the image from the bucket.
Edge Cases
- Without a
redisorpostgresqlprovider, a local Redis or PostgreSQL runs on the node. - The migration step waits for a verified cache connection, because a failed job dispatch fails the migration. PostgreSQL rolls back the failed migration and a later run applies it again.
- A database result that asks to verify the server fails the evaluation, because upstream Pixelfed uses only opportunistic TLS and reads no CA.
- Registration is a setting of Pixelfed only. Mastodon and PeerTube have a different registration model, thus no shared option covers the three applications. This is a design choice.
- PHP-FPM clears the environment, so the component passes the CA bundle path to the Pixelfed pool.
- A script on another origin cannot read the media, because the CORS rule of the bucket allows only the origin of the instance. This is a design choice: media elements without a CORS mode and clients outside a browser still load the media.
Requirements (mandatory)
Functional Requirements
- FR-001: The component MUST declare the Pixelfed package and its icon, and MUST be optional in the group.
- FR-002: The component MUST offer the shared options with the default subdomain
pixelfed, and MUST expose only the maximum upload size of the service options. - FR-003: The component MUST type the settings with the pinned Pixelfed schema, narrowed to a selection of names. A selection entry MAY select names by prefix.
- FR-004: The component MUST write each string setting quoted, with the special characters escaped.
- FR-005: The component MUST open registration by default, above the module default and below the operator value.
- FR-006: The component MUST request an
oidcclient with the Pixelfed callback and the scopesopenid,profileandemail, and MUST give Pixelfed the endpoints of the result. - FR-007: The component MUST request
smtpwith a no-reply sender on its domain, and MUST use TLS to the relay. - FR-008: The component MUST request
redisin its own namespace, with TLS when the result says so, as the ACL user of the result, and MUST make the migration step wait for the cache. - FR-009: The component MUST request a
generateFilesapplication key. - FR-010: The component MUST render all of its secrets into one secrets file through the OpenBao agent, for the migration step.
- FR-011: The component MUST request a
garagewebsite bucket for media, whose CORS rule allows only the origin of the instance, and MUST give the public media path to the bucket through a bucket front ofgarage. - FR-012: The component MUST request
spiffe,sslfor its domain, and public HTTP and HTTPS ports fromfirewall. - FR-013: The
nixos-modulerequest MUST enable Pixelfed, MUST let nginx read its files, and MUST take the certificate of the virtual host from thesslresult. - FR-014: The component MUST request a streaming backup of the database with restic.
- FR-015: The
redisrequest MUST contain only data. The node MUST render the client from the contract result, and nothing when the result is absent. - FR-016: The component MUST request a
postgresqldatabase and user for Pixelfed. The node MUST render the client and the backup from the contract result, with the password from the engine, and MUST use a local PostgreSQL when the result is absent. MariaDB MUST NOT run on the node. - FR-017: The component MUST request a generated Laravel Passport key pair, readable only by Pixelfed. The generator MUST adopt a pair that the node already has, so that the tokens it signed stay valid. Passport MUST read the rendered key files, because a PEM key in the environment does not parse.
- FR-018: The component MUST mark the captcha and payment secrets in the settings as secrets of the operator, and MUST fail the evaluation when the settings hold a value for one of them. It MUST read only the secrets that are set from the OpenBao KV of the deployment into its secrets file, quoted so that a value with a space, a comment sign, a quote, a dollar sign or a newline gets to Pixelfed unchanged. A secret that is not set MUST NOT stop the start.
Key Entities
- Secrets file: The rendered secrets that Pixelfed reads.
- Migration step: The service that runs the migrations. It reads the secrets file and waits for the cache.
- Operator settings: The selection of names that the operator can set, for example the instance name, open registration and the payment settings.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: The VM test passes. A posted image comes from
garage, also through the public media path of the instance, the backup works, an OAuth token stays valid after the stored Passport key pair is replaced, and a secret that only OpenBao holds gets to the.envand not to the Nix store. - SC-002: The unit tests of the database wiring, the trust wiring, the cache gate, the operator configuration, the secrets of the operator and the quoting of a secret in the secrets file pass.
Assumptions
- The
operatorgroup has the sharedvalkey,smtp,lldapandautheliacomponents. - The schema comes from a branch pin of upstream pull request pixelfed#6555. A rename of a selected name fails the evaluation.
Known Deviations
None.