Feature Specification: Component mastodon
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/components/operator/mastodon/ and the shared operator component code at 14504f80d, pull requests #1384, #1597, #1651, #1724, #1742, #1750, #1752, #1762, #1768, #1834, #1835, #1846, #1853, #1867, #1911.
User Scenarios & Testing (mandatory)
User Story 1 - Run a Mastodon instance for a group (Priority: P1)
An operator enables mastodon 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 Mastodon 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, logs in with a command-line client, posts text and an image, reads the image from garage, and backs up and restores 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.
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, through authelia.
Why this priority: SSO is the only path from the group directory to the application.
Independent Test: A unit test of the OIDC request, and the OIDC part of the VM test.
Acceptance Scenarios:
- Given the client secret, When the login services start, Then they read it at run time, not from the configuration.
- Given no
autheliaprovider, When the component evaluates, Then the OIDC request stays inert.
User Story 3 - Configure the instance policy (Priority: P2)
An operator sets the instance policy and the automatic removal of old remote media. The form shows only the keys that the component selects from the upstream configuration schema.
Why this priority: The instance works with the defaults.
Independent Test: Unit tests cover the schema helpers. No check evaluates the selection of mastodon itself.
Acceptance Scenarios:
- Given a key that the selection leaves out, for example the local domain, When the operator sets it, Then the schema refuses the value.
- Given an operator value for a service option that the component also sets, When the node evaluates, Then the evaluation fails and names the option.
User Story 4 - Back up the database off the node (Priority: P3)
The component backs up the database with restic: the remote database of a routed postgresql provider, or else the local one. The valkey node backs up the cache.
Why this priority: Recovery needs the database, but the instance runs without a backup.
Independent Test: The restore part of the VM test.
Acceptance Scenarios:
- Given a snapshot of the local database, When the data is emptied and the snapshot is restored, Then the data is the same as before, and the client and the RSS feed of the account work again.
Edge Cases
- A credential that the agent has not rendered yet fails the directory setup of Mastodon. The services want the setup but do not require it, so they keep their start jobs. The setup retries without a start limit, because a failed one-shot step stays failed.
- The services wait until the database password is present.
- The default cache driver of Mastodon reads no CA, so the component uses a driver that verifies the TLS-only
valkeyagainst the private CA. - A recreated container keeps the same Rails and VAPID keys, because they live in OpenBao.
- 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 Mastodon package and its icon, and MUST be optional in the group.
- FR-002: The component MUST offer the shared options with the default subdomain
mastodon, plus the automatic removal of old media and its age. - FR-003: The component MUST type the settings with the pinned upstream schema, narrowed to a selection of keys, and MUST write each value as a string.
- FR-004: The component MUST request an
oidcclient with the Mastodon callback and the scopesopenid,profileandemail. - FR-005: The component MUST keep the OIDC client secret out of the Nix store and the unit environment, and MUST give it only to the services that serve logins.
- FR-006: The component MUST request
smtpwith a no-reply sender on its domain, and MUST log in with the user name of the result. - FR-007: The component MUST request
redisin its own namespace, and MUST render the cache URL at run time, with TLS when the result says so. - FR-008: The component MUST request a
postgresqldatabase and user for Mastodon. - FR-009: The component MUST request a
garagewebsite bucket for media, whose CORS rule allows only the origin of the instance. - FR-010: The component MUST request
generateFilessecrets for the Rails secret base, the Active Record encryption keys and the VAPID key pair, and MUST check the lengths of the VAPID keys. - FR-011: The component MUST request
spiffe,sslfor its domain, and public HTTP and HTTPS ports fromfirewall. - FR-012: The
nixos-modulerequest MUST enable Mastodon and merge the operator values at normal priority, so that a conflict with a value of the component fails the evaluation. - FR-013: The component MUST request a streaming backup of the database with restic. Without a routed
postgresqlprovider, the backup MUST dump the local database. With one, the database request MUST point the backup at the remote database. - FR-014: The
redisandpostgresqlrequests MUST contain only data. The node MUST render the clients from the contract results, and nothing when a result is absent. - FR-015: The component MUST request the
pod-rolesrolesidekiqon itswebnode, with the units ofsidekiqProcesses. The units that migrate data or back it up, the web units, the streaming units,nginxand media removal MUST run in the main pod. - FR-016: The component MUST mark the translation and captcha 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 the environment of the web service. A secret that is not set MUST NOT stop the start.
Key Entities
- Generated keys: The Rails and VAPID secrets, kept in OpenBao.
- Operator settings: The selection of keys that the operator can set, for example authorized fetch, translation and single-user mode.
- Option audit table: The audience of each operator option, with the sensitive ones marked.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: The VM test passes. A posted image comes from
garage, and the content security policy allows it. - SC-002: The unit tests of the OIDC request, the trust wiring, the operator configuration, the database and cache gates and the secrets of the operator pass.
- SC-003: A recreated container gets the same generated keys. No check covers this.
Assumptions
- The
operatorgroup has the sharedvalkey,smtp,lldapandautheliacomponents. - The schema comes from a branch pin of upstream pull request mastodon#38474. A rename of a selected key fails the evaluation.
- The VM test runs all services on one node with a development OpenBao.
Known Deviations
None.