Technical design, as one document
This page holds the technical design and each of its parts. Print the page from the browser to get one file.
Technical design
This page is the entry of the technical design of Fediversity. It gives the scope, the list of the parts, and links to the page that has the details of each subject. The other pages of this site, the site of the panel and the site of the API stay the source for their subjects. This page does not repeat them.
To get the technical design as one file, open the one-document page and print it from the browser.
Scope
Fediversity lets a hosting provider offer applications to its customers. These we call operator: a person or an organization that has a deployment with applications on its own domain. The glossary defines the terms and the actors.
The technical design covers five functions:
- Install applications.
- Configure applications.
- Update applications.
- Manage users.
- Switch an operator to and from a different hosting provider.
The repository has three components, each with its own flake and its own documentation site:
| Component | Directory | What it is |
|---|---|---|
core |
core/ |
Nix code: the data model, the applications and services, the resources and contracts, and the deployment programs that use Nix and OpenTofu. |
api |
api/ |
A FastAPI service. It validates a configuration, puts a build in a queue, runs the deployment programs of core, and sends progress events. |
panel |
panel/ |
A Django web application. It keeps the accounts and the ownership of the deployments, shows the configuration form, and answers the authorization calls of api. |
api and panel use core. core does not use api or panel.
Parts of the technical design
Each page in core/docs/md/technical-design/ is one part of the technical
design. The build fails when this list does not link a part.
- Access model: the persons and the components that have access to the state of the system, and the limits of that access.
- Architecture: the system context, the containers of an installation, and the components of each deployment.
- Capability flows: the run-time flow of each capability, with the code paths, the stores that change and the failure paths.
- Configuration data flow: how an operator configuration becomes a deployment.
- Contracts (RFC 189): how the contracts connect the software that needs a service to the software that gives it.
- Credentials register: where each credential is, what makes it, how long it lives and how it changes.
- Data subject requests: which party answers a request about personal data, and which mechanism the code gives for it.
- Deployment topology: the nodes of a deployment, where they run, the networks between them and the trust boundaries.
- The five functions: how the system installs, configures and updates applications, manages users, and switches an operator to a different hosting provider.
- Metering and accounting: what the system measures about a deployment, and where a hosting provider reads it.
- Logs, traces and audit records: each channel that records what the software and its users do, with its personal data, store and retention.
- Requirements of hosting providers and design review: the requirements of the hosting providers, the place in the design that answers each one, and the review of the design.
- Quality attributes: the target of each quality attribute, what the code does, and the test that shows it.
- State lifecycle: what happens to the state when the code changes, or when a service gets a new version.
- State register: each store of state, its owner, its personal data, its retention and its protection.
- Traceability of the capabilities: the requirements, the code, the tests and the docs pages of each capability.
Where to find the details
| Subject | Page |
|---|---|
| Terms and actors | Concepts |
| How a configuration becomes a deployment | Configuration data flow |
| Options of the data model, the operators and resources | Data model, Operator configuration, Hosting resources |
Requirements of core |
Specifications of core |
Design and requirements of api |
Design of the API, specifications of api |
HTTP endpoints and events of api |
Swagger UI, AsyncAPI |
Design and requirements of panel |
Architecture, specifications of panel |
| Use of the panel | Guides |
| Operation of a hosting provider | Hosting-provider guide, Maintainer guide |
| Security | Threat model, SBOM |
| Personal data and contracts | Data protection, Contract templates, Legal roles |
| Technical documentation of the Cyber Resilience Act | Technical documentation |
Access model
This part gives the persons and the components that have access to the state of the system, and the limits of that access.
The tables that have the label "generated" come from the code at each build of the documentation (core/docs/md/technical-design/access-model.nix).
The threats and the controls are in the threat model.
Actors
| Actor | Definition | Where the identity is |
|---|---|---|
| Hosting provider | The organization that runs the software of this repository for its customers (Actors). | It has no account of its own. Its persons are staff and administrators. |
| Staff | A person of the hosting provider who reads the telemetry. | A user in the lldap of the hosting provider, with the group fediversity-staff. |
| Administrator | A person of the hosting provider who can administer the nodes and the shared services. | A user in the lldap of the hosting provider, with the group fediversity-admins. The person logs in to the shared account admin with a short-lived SSH certificate. When no certificate is available, the holder of the offline key of ADMIN_EMERGENCY can log in to the account emergency (core/setups/nixos/users.nix). |
| Panel superuser | An account of the panel that can see and assign the domains of all operators. | A User of the panel with is_superuser. Each SSO login sets this flag and is_staff from the groups claim: a member of fediversity-admins gets them, a different user loses them (superuserGroups in panel/nix/component/oidc.nix, sync_superuser in panel/src/panel/oidc.py). A password login does not change them. |
| Deployer | A program or a person that applies a configuration to the nodes: the CD workflows, an administrator on a workstation, and the front end (the api and Windmill), which deploys the deployments of the operators. | An OpenBao token with the policy fediversity-deployer (CD and workstation) or fediversity-operator-deployer (front end), and a user of the state backend: cd, workstation-<person> or operator (core/components/hosting-provider/terraform-backend/front.nix). |
| Operator | A customer of the hosting provider, who configures and deploys applications through the panel. | A User in the database of the panel. The log-in is with a local password, or with OpenID Connect when the hosting provider sets the OIDC_* settings (panel/src/panel/settings.py). |
| Tenant | One deployment of an operator, as an owner of names in a shared service. The code calls the name of a tenant the namespace of the deployment. | The namespace is in the name of each node of the deployment and in the certificate that SPIRE gives the node. |
| Application user | A person who uses an application of an operator. | An account in the application. Each deployment has its own lldap and authelia nodes for single sign-on (core/setups/groups/operator.nix). |
| Node | A machine of the hosting provider or of a deployment. | A certificate (SVID) from SPIRE. OpenBao gives the node the policy host-<name> (core/components/hosting-provider/openbao/spire-auth.nix). |
Permissions of each role
In the panel
The panel has three levels of access.
- A visitor without an account can read the public pages.
- An operator can read and change only the deployments for which
Deployment.operatoris the account of the operator (panel/src/panel/models.py). The views filter on this field. - A superuser has the access of an operator.
A superuser can also list, claim and assign domains for all operators, and can read the audit history of each account (
reads_all_historyand_AUTHZ_SUPERUSER_PROCEDURESinpanel/src/panel/views.py).
The admin site of Django (/admin/) shows only the User model (panel/src/panel/admin.py).
The views of the panel do not use is_staff.
The directory groups fediversity-staff and fediversity-admins give no permission in the panel.
In the api
The api does not keep accounts and does not decide on access.
For each request, the api sends the name of the procedure, the scope and the headers to the authorization callback of the panel (authorize in api/src/fediversity_api/authz.py, AuthzCallback in panel/src/panel/views.py).
The callback accepts a request only with the callback token of the api (AUTHZ_CALLBACK_TOKEN), when that token is set.
The panel identifies the requester from a bearer token that the panel signed, which stays valid for API_TOKEN_TTL seconds (panel/src/panel/tokens.py).
| Requester | Procedures that the callback permits |
|---|---|
| No account | AUTHZ_PUBLIC_PROCEDURES: the schema, the list of applications and their icons, the configuration sections and the domain terms. |
| Operator | The procedures of AUTHZ_PROCEDURE_RULES, only for its own deployments, its own domain contact and its own audit events. |
| Superuser | The same, and domain_assign. domain_list and domain_claim apply to the deployments of all operators. |
| Service token | A request with the service token of the api (API_SERVICE_TOKEN) is an internal call. The callback then decides for the user that the request names. domain_terms_notice accepts only such a call (api/src/fediversity_api/routes/domains.py). |
The callback denies a procedure that is not in AUTHZ_PROCEDURE_RULES.
Without a callback URL, the api starts only when API_AUTHZ_ALLOW_ALL is set, and then it permits each request.
Directory groups of the hosting provider
The groups are in the lldap of the hosting provider. The section Manage users and groups of the hosting-provider guide tells how to give a group to a user.
| Service | fediversity-staff |
fediversity-admins |
Source |
|---|---|---|---|
| Grafana of the hosting provider | The Viewer role. Grafana has one organization, thus a viewer can read the telemetry of all deployments. |
The Admin role. |
core/components/hosting-provider/telemetry/ |
| OpenBao | None. | The policy fediversity-admins: read of the full KV. With ADMIN_UI_WRITE=1, also write and delete. |
core/components/hosting-provider/openbao/oidc.nix |
| OpenBao, deploy token | None. | A token with the policy fediversity-deployer. |
core/components/hosting-provider/openbao/oidc.nix |
| SSH to the nodes | None. | A certificate for the shared account admin, which is in wheel. Each node accepts the certificate when ADMIN_SSH_CA_KEYS is set. |
core/components/hosting-provider/openbao/ssh-certificates.nix, core/setups/nixos/users.nix |
| Incus | None. | Administrator of all containers. The authorization scriptlet of Incus refuses an OIDC user outside the group. | core/components/hosting-provider/incus/default.nix |
| Windmill | None. | The UI, from which a person starts and cancels deployments. | core/components/hosting-provider/windmill/default.nix |
pgweb |
None. | The UI, which connects as the postgres superuser. A member can read the databases of all deployments. |
core/components/hosting-provider/postgresql/default.nix |
| UI of Valkey | None. | The UI. | core/components/shared/valkey/default.nix |
| UI of Garage | None. | The UI, only when ADMIN_UI_WRITE=1. |
core/components/hosting-provider/garage/default.nix |
When ADMIN_SOURCES is set, the firewall of each node accepts the administrative ports (port 22 by default) only from those addresses (core/resources/firewall/default.nix).
An operator gets no shell access to the nodes of its deployment.
The persons who can read each log, trace and audit channel are in Logs, traces and audit records.
Application roles
Each deployment of an operator has its own lldap, with the same group names.
The lldap of a deployment has the groups application-admins and application-moderators (core/resources/ldap/application-role-groups.nix), and the groups of each application.
Each application gives a role to the members of a group at log-in.
The Grafana of a deployment accepts the members of fediversity-admins in the lldap of that deployment (core/components/operator/grafana/oidc.nix).
Generated from the role-groups.nix file of each application in core/components/operator/:
| Application | Directory group | Role in the application | The first administrator is a member |
|---|---|---|---|
mastodon |
mastodon-owners |
Owner |
yes |
mastodon |
application-admins |
Admin |
yes |
mastodon |
mastodon-admins |
Admin |
no |
mastodon |
application-moderators |
Moderator |
no |
mastodon |
mastodon-moderators |
Moderator |
no |
peertube |
application-admins |
0 |
yes |
peertube |
peertube-admins |
0 |
no |
peertube |
application-moderators |
1 |
no |
peertube |
peertube-moderators |
1 |
no |
pixelfed |
application-admins |
admin |
yes |
pixelfed |
pixelfed-admins |
admin |
no |
Access of each component to each store
The tables in this section are generated from the configuration of the nodes, with the groups of core/testers/groups.nix.
That evaluation knows no deployment.
Thus <own deployment> shows where a name has the namespace of the deployment of the node, and <each deployment> shows a path that applies to all deployments.
Secrets in the KV of OpenBao
Each node logs in to OpenBao with its SPIRE certificate and gets the policy host-<name>.
The entries of the hosting provider are below secret/data/vars/.
The entries of a deployment are below secret/data/vars/operator-deployments/<namespace>/.
Each entry has a public half and a secret half.
The credentials register gives the lifetime and the rotation of each credential.
The table gives the name of the entry when a policy has the same capabilities on the two halves, and <half>/<entry> when it has them on one half.
The table gives a grant on secret/metadata/ only when the policy has no grant on the data of the same entry.
| Policy | Scope | Capabilities | Entries |
|---|---|---|---|
fediversity-admins |
not in vars |
read | * |
fediversity-deployer |
hosting provider | create, read, update | secret/deployer/adminSources, secret/deployer/dnsApiToken, secret/deployer/netboxApiToken, secret/deployer/openproviderPassword, secret/deployer/openproviderUsername, secret/deployer/zonesManagerToken, secret/smarthost-upstream |
fediversity-deployer |
hosting provider | read | secret/incus-store, secret/netbox, secret/octodns, public/* |
fediversity-operator-deployer |
<each deployment> |
create, read, update, delete, list | authelia-backups-s3, secret/authelia-operator-secrets, desec-zone, domain-claims, lldap-backups-s3, mastodon-backups-s3, secret/mastodon-operator-secrets, mastodon-s3, migration, operator-authelia-cache-backups-s3, operator-mastodon-cache-backups-s3, operator-peertube-cache-backups-s3, operator-pixelfed-cache-backups-s3, operator-valkey-cache-backups-s3, peertube-backups-s3, peertube-s3, pixelfed-backups-s3, secret/pixelfed-operator-secrets, pixelfed-s3 |
fediversity-operator-deployer |
<each deployment> |
deny | secret/authelia-backups-restic, secret/ldap-key-operator, secret/lldap-backups-restic, secret/mastodon-backups-restic, secret/mastodon-keys, secret/mastodon-media-backup, secret/operator-authelia-cache-backups-restic, secret/operator-authelia-oidc, secret/operator-mastodon-cache-backups-restic, secret/operator-peertube-cache-backups-restic, secret/operator-pixelfed-cache-backups-restic, secret/operator-valkey-cache-backups-restic, secret/peertube-backups-restic, secret/peertube-media-backup, secret/peertube-secret, secret/pixelfed-app, secret/pixelfed-backups-restic, secret/pixelfed-media-backup, secret/pixelfed-passport |
fediversity-operator-deployer |
<each deployment> |
metadata only: list, delete | * |
fediversity-operator-deployer |
<each deployment> |
read | public/* |
fediversity-operator-deployer |
hosting provider | create, read, update, delete, list | authelia-backups-s3, desec-zone, domain-claims, lldap-backups-s3, mastodon-backups-s3, mastodon-s3, operator-authelia-cache-backups-s3, operator-mastodon-cache-backups-s3, operator-peertube-cache-backups-s3, operator-pixelfed-cache-backups-s3, operator-valkey-cache-backups-s3, peertube-backups-s3, peertube-s3, pixelfed-backups-s3, pixelfed-s3 |
fediversity-operator-deployer |
hosting provider | metadata only: read | secret/smarthost-upstream |
fediversity-operator-deployer |
hosting provider | read | secret/netbox, secret/postgresql-admin, public/* |
fediversity-operator-deployer |
not in vars |
metadata only: create | retained-backups/* |
host-authelia |
hosting provider | create, read, update, delete, list | authelia-backups-restic, authelia-oidc, authelia-session |
host-authelia |
hosting provider | read | authelia-backups-s3, secret/smtp |
host-bind |
hosting provider | create, read, update, delete, list | acme-dns, bind-backups-restic, dnssec, octodns, operator-dns |
host-bind |
hosting provider | read | secret/authelia-oidc, bind-backups-s3 |
host-garage |
<each deployment> |
metadata only: read, list, delete | authelia-backups-restic, ldap-key-operator, lldap-backups-restic, mastodon-backups-restic, mastodon-keys, mastodon-media-backup, operator-authelia-cache-backups-restic, operator-authelia-oidc, operator-mastodon-cache-backups-restic, operator-peertube-cache-backups-restic, operator-pixelfed-cache-backups-restic, operator-valkey-cache-backups-restic, peertube-backups-restic, peertube-media-backup, peertube-secret, pixelfed-app, pixelfed-backups-restic, pixelfed-media-backup, pixelfed-passport |
host-garage |
hosting provider | create, read, update, delete, list | authelia-backups-s3, bind-backups-s3, garage-admin, grafana-backups-s3, hosting-provider-authelia-cache-backups-s3, hosting-provider-valkey-cache-backups-s3, lldap-backups-s3, netbox-backups-s3, openbao-backups-s3, postgresql-backups-s3, terraform-backend-backups-s3, windmill-backups-s3 |
host-garage |
hosting provider | read | secret/acme-dns, secret/authelia-oidc, telemetry-s3 |
host-garage |
not in vars |
metadata only: read, list, delete | retained-backups/* |
host-incus |
hosting provider | create, read, update, delete, list | incus-deploy, incus-store |
host-incus |
hosting provider | read | secret/authelia-oidc |
host-lldap |
hosting provider | create, read, update, delete, list | ldap-bind-hosting-provider, ldap-jwt-hosting-provider, ldap-key-hosting-provider, lldap-backups-restic |
host-lldap |
hosting provider | read | secret/authelia-oidc, lldap-backups-s3, secret/smtp |
host-netbox |
hosting provider | create, read, update, delete, list | netbox, netbox-backups-restic |
host-netbox |
hosting provider | read | secret/authelia-oidc, netbox-backups-s3, secret/smtp |
host-openbao |
hosting provider | read | secret/authelia-oidc, openbao-backups-s3, secret/postgresql-admin |
host-operator-authelia |
<own deployment> |
create, read, update, delete, list | authelia-backups-restic, authelia-session, operator-authelia-oidc |
host-operator-authelia |
<own deployment> |
read | authelia-backups-s3, secret/authelia-operator-secrets, secret/node-telemetry, secret/operator-smtp |
host-operator-grafana |
<own deployment> |
create, read, update, delete, list | grafana-secret, metrics-query, node-telemetry |
host-operator-grafana |
<own deployment> |
read | secret/operator-authelia-oidc |
host-operator-lldap |
<own deployment> |
create, read, update, delete, list | ldap-bind-operator, ldap-jwt-operator, ldap-key-operator, ldap-roles-operator, lldap-backups-restic |
host-operator-lldap |
<own deployment> |
read | lldap-backups-s3, secret/node-telemetry, secret/operator-authelia-oidc, secret/operator-smtp |
host-operator-mastodon |
<own deployment> |
create, read, update, delete, list | mastodon-backups-restic, mastodon-keys, mastodon-media-backup, metrics |
host-operator-mastodon |
<own deployment> |
read | secret/ldap-roles-operator, mastodon-backups-s3, secret/mastodon-operator-secrets, mastodon-s3, secret/node-telemetry, secret/operator-authelia-oidc, secret/operator-smtp |
host-operator-peertube |
<own deployment> |
create, read, update, delete, list | metrics, peertube-backups-restic, peertube-media-backup, peertube-secret |
host-operator-peertube |
<own deployment> |
read | secret/ldap-roles-operator, secret/node-telemetry, secret/operator-authelia-oidc, secret/operator-smtp, peertube-backups-s3, peertube-s3 |
host-operator-pixelfed |
<own deployment> |
create, read, update, delete, list | metrics, pixelfed-app, pixelfed-app-previous, pixelfed-backups-restic, pixelfed-media-backup, pixelfed-passport |
host-operator-pixelfed |
<own deployment> |
read | secret/ldap-roles-operator, secret/node-telemetry, secret/operator-authelia-oidc, secret/operator-smtp, pixelfed-backups-s3, secret/pixelfed-operator-secrets, pixelfed-s3 |
host-operator-smtp |
<own deployment> |
create, read, update, delete, list | operator-smtp, operator-smtp-dkim |
host-operator-smtp |
<own deployment> |
read | secret/node-telemetry, secret/operator-authelia-oidc |
host-operator-smtp |
hosting provider | read | secret/smarthost-upstream |
host-operator-valkey |
<own deployment> |
create, read, update, delete, list | operator-authelia-cache-backups-restic, operator-mastodon-cache-backups-restic, operator-peertube-cache-backups-restic, operator-pixelfed-cache-backups-restic, operator-valkey-cache-backups-restic, valkey-operator-authelia, valkey-operator-mastodon, valkey-operator-peertube, valkey-operator-pixelfed, valkey-operator-valkey |
host-operator-valkey |
<own deployment> |
read | secret/node-telemetry, operator-authelia-cache-backups-s3, secret/operator-authelia-oidc, operator-mastodon-cache-backups-s3, operator-peertube-cache-backups-s3, operator-pixelfed-cache-backups-s3, operator-valkey-cache-backups-s3 |
host-otel-collector |
metadata only: list | `` | |
host-otel-collector |
<each deployment> |
read | public/metrics |
host-otel-collector |
hosting provider | read | secret/authelia-oidc, secret/telemetry-ingest |
host-postgresql |
hosting provider | create, read, update, delete, list | postgresql-admin, postgresql-backups-restic |
host-postgresql |
hosting provider | read | secret/authelia-oidc, postgresql-backups-s3 |
host-smtp |
hosting provider | create, read, update, delete, list | smtp, smtp-dkim |
host-smtp |
hosting provider | read | secret/authelia-oidc, secret/smarthost-upstream |
host-telemetry |
metadata only: list | `` | |
host-telemetry |
<each deployment> |
read | public/metrics-query/htpasswd, public/node-telemetry/htpasswd |
host-telemetry |
hosting provider | create, read, update, delete, list | audit-ingest-api, grafana-backups-restic, telemetry-grafana, telemetry-grafana-previous, telemetry-ingest, telemetry-s3 |
host-telemetry |
hosting provider | read | secret/authelia-oidc, grafana-backups-s3, secret/smtp |
host-terraform-backend |
hosting provider | create, read, update, delete, list | terraform-backend-operator |
host-terraform-backend |
hosting provider | read | secret/authelia-oidc, terraform-backend-backups-s3 |
host-valkey |
hosting provider | create, read, update, delete, list | hosting-provider-authelia-cache-backups-restic, hosting-provider-valkey-cache-backups-restic, valkey-hosting-provider-authelia, valkey-hosting-provider-valkey |
host-valkey |
hosting provider | read | secret/authelia-oidc, hosting-provider-authelia-cache-backups-s3, hosting-provider-valkey-cache-backups-s3 |
host-windmill |
hosting provider | create, read, update, delete, list | windmill, windmill-backups-restic, windmill-superadmin |
host-windmill |
hosting provider | read | secret/authelia-oidc, public/domain-claims, secret/incus-deploy, secret/incus-store, secret/operator-dns, secret/deployer/adminSources, secret/deployer/dnsApiToken, secret/deployer/netboxApiToken, secret/deployer/openbaoToken, secret/deployer/openproviderPassword, secret/deployer/openproviderUsername, secret/garage-admin/adminToken, secret/terraform-backend-operator/password, secret/smtp, windmill-backups-s3 |
The policy fediversity-admins in this table is that of the evaluation, where ADMIN_UI_WRITE is not set.
Other paths in OpenBao
These paths give certificates (pki), database passwords (database, postgresql), directory passwords (ldap, ldap-operator) and child tokens.
| Policy | Path | Capabilities |
|---|---|---|
fediversity-admins |
identity/* |
read, list |
fediversity-admins |
sys/auth |
read |
fediversity-admins |
sys/mounts |
read |
fediversity-admins |
sys/policies/acl |
read, list |
fediversity-admins |
sys/policies/acl/* |
read |
fediversity-deployer |
auth/token/create |
update |
fediversity-operator-deployer |
postgresql/static-roles/operator-authelia-* |
create, read, update, delete |
fediversity-operator-deployer |
postgresql/static-roles/operator-lldap-* |
create, read, update, delete |
fediversity-operator-deployer |
postgresql/static-roles/operator-mastodon-* |
create, read, update, delete |
fediversity-operator-deployer |
postgresql/static-roles/operator-peertube-* |
create, read, update, delete |
fediversity-operator-deployer |
postgresql/static-roles/operator-pixelfed-* |
create, read, update, delete |
fediversity-operator-deployer |
ldap-operator/<each deployment>/rotate-role/authelia-bind |
update |
fediversity-operator-deployer |
auth/token/create |
update |
host-authelia |
pki/issue/internal |
update |
host-authelia |
database/static-creds/hosting-provider-authelia |
read |
host-authelia |
ldap/static-cred/authelia-bind |
read |
host-authelia |
postgresql/static-creds/hosting-provider-authelia |
read |
host-bind |
pki/issue/internal |
update |
host-garage |
pki/issue/internal |
update |
host-incus |
pki/issue/internal |
update |
host-lldap |
pki/issue/internal |
update |
host-lldap |
postgresql/static-creds/hosting-provider-lldap |
read |
host-netbox |
pki/issue/internal |
update |
host-netbox |
postgresql/static-creds/hosting-provider-netbox |
read |
host-openbao |
pki/issue/internal |
update |
host-operator-authelia |
pki/issue/internal |
update |
host-operator-authelia |
database/static-creds/operator-authelia-<own deployment> |
read |
host-operator-authelia |
ldap-operator/<own deployment>/static-cred/authelia-bind |
read |
host-operator-authelia |
postgresql/static-creds/operator-authelia-<own deployment> |
read |
host-operator-grafana |
pki/issue/internal |
update |
host-operator-lldap |
pki/issue/internal |
update |
host-operator-lldap |
postgresql/static-creds/operator-lldap-<own deployment> |
read |
host-operator-mastodon |
pki/issue/internal |
update |
host-operator-mastodon |
database/static-creds/operator-mastodon-<own deployment> |
read |
host-operator-mastodon |
postgresql/static-creds/operator-mastodon-<own deployment> |
read |
host-operator-peertube |
pki/issue/internal |
update |
host-operator-peertube |
database/static-creds/operator-peertube-<own deployment> |
read |
host-operator-peertube |
postgresql/static-creds/operator-peertube-<own deployment> |
read |
host-operator-pixelfed |
pki/issue/internal |
update |
host-operator-pixelfed |
database/static-creds/operator-pixelfed-<own deployment> |
read |
host-operator-pixelfed |
postgresql/static-creds/operator-pixelfed-<own deployment> |
read |
host-operator-smtp |
pki/issue/internal |
update |
host-operator-valkey |
pki/issue/internal |
update |
host-operator-valkey |
database/static-creds/operator-valkey-<own deployment> |
read |
host-operator-valkey |
database/static-creds/operator-authelia-<own deployment> |
read |
host-operator-valkey |
database/static-creds/operator-mastodon-<own deployment> |
read |
host-operator-valkey |
database/static-creds/operator-peertube-<own deployment> |
read |
host-operator-valkey |
database/static-creds/operator-pixelfed-<own deployment> |
read |
host-otel-collector |
pki/issue/internal |
update |
host-postgresql |
pki/issue/internal |
update |
host-smtp |
pki/issue/internal |
update |
host-telemetry |
pki/issue/internal |
update |
host-terraform-backend |
pki/issue/internal |
update |
host-valkey |
pki/issue/internal |
update |
host-valkey |
database/static-creds/hosting-provider-valkey |
read |
host-valkey |
database/static-creds/hosting-provider-authelia |
read |
host-windmill |
pki/issue/internal |
update |
host-windmill |
postgresql/static-creds/hosting-provider-windmill |
read |
PostgreSQL
The database engine of OpenBao owns the password of each role and changes it at the interval in the table.
A node reads the password of its role from OpenBao (core/setups/wiring/postgresql-roster.nix).
The database and the role of a deployment have the namespace of the deployment as a suffix, and the OpenTofu graph of the deployment creates and drops them (core/effects/tf/incus/operator/postgresql.tf).
| Nodes that read the password | Role | Database | Scope | Credential |
|---|---|---|---|---|
authelia |
hosting-provider-authelia |
hosting-provider-authelia |
hosting provider | rotating, 24h |
lldap |
hosting-provider-lldap |
hosting-provider-lldap |
hosting provider | rotating, 24h |
netbox |
hosting-provider-netbox |
hosting-provider-netbox |
hosting provider | rotating, 24h |
windmill |
hosting-provider-windmill |
hosting-provider-windmill |
hosting provider | rotating, 24h |
operator-authelia |
operator-authelia-<own deployment> |
operator-authelia-<own deployment> |
each operator deployment | rotating, 24h |
operator-lldap |
operator-lldap-<own deployment> |
operator-lldap-<own deployment> |
each operator deployment | rotating, 24h |
operator-mastodon |
operator-mastodon-<own deployment> |
operator-mastodon-<own deployment> |
each operator deployment | rotating, 24h |
operator-peertube |
operator-peertube-<own deployment> |
operator-peertube-<own deployment> |
each operator deployment | rotating, 24h |
operator-pixelfed |
operator-pixelfed-<own deployment> |
operator-pixelfed-<own deployment> |
each operator deployment | rotating, 24h |
The postgresql node also has these group roles:
| Group role | Members | Privileges on the database | Privileges on the objects | Bypasses row-level security |
|---|---|---|---|---|
windmill_admin |
hosting-provider-windmill |
each sequence of hosting-provider-windmill: ALL; each table of hosting-provider-windmill: ALL |
yes | |
windmill_user |
windmill_admin |
hosting-provider-windmill: CONNECT, CREATE, TEMPORARY |
each sequence of hosting-provider-windmill: ALL; each table of hosting-provider-windmill: ALL |
no |
Two actors have access to all databases of the node: the front-end deployer, which reads the password of the postgres superuser (the entry secret/postgresql-admin in the KV table), and a member of fediversity-admins through pgweb.
Buckets in Garage
Each row is one ensureAccess entry of a key that a node requests.
owner lets the key change the configuration of the bucket.
| Group | Node | Key | Bucket | Access |
|---|---|---|---|---|
hosting-provider |
authelia |
hosting-provider-authelia-backups |
hosting-provider-authelia-backups |
read, write, owner |
hosting-provider |
authelia |
hosting-provider-authelia-cache-backups |
hosting-provider-authelia-cache-backups |
read, write, owner |
hosting-provider |
bind |
hosting-provider-bind-backups |
hosting-provider-bind-backups |
read, write, owner |
hosting-provider |
lldap |
hosting-provider-lldap-backups |
hosting-provider-lldap-backups |
read, write, owner |
hosting-provider |
netbox |
hosting-provider-netbox-backups |
hosting-provider-netbox-backups |
read, write, owner |
hosting-provider |
openbao |
hosting-provider-openbao-backups |
hosting-provider-openbao-backups |
read, write, owner |
hosting-provider |
postgresql |
hosting-provider-postgresql-backups |
hosting-provider-postgresql-backups |
read, write, owner |
hosting-provider |
telemetry |
hosting-provider-grafana-backups |
hosting-provider-grafana-backups |
read, write, owner |
hosting-provider |
telemetry |
hosting-provider-telemetry |
hosting-provider-loki |
read, write, owner |
hosting-provider |
telemetry |
hosting-provider-telemetry |
hosting-provider-mimir |
read, write, owner |
hosting-provider |
telemetry |
hosting-provider-telemetry |
hosting-provider-tempo |
read, write, owner |
hosting-provider |
terraform-backend |
hosting-provider-terraform-backend-backups |
hosting-provider-terraform-backend-backups |
read, write, owner |
hosting-provider |
valkey |
hosting-provider-valkey-cache-backups |
hosting-provider-valkey-cache-backups |
read, write, owner |
hosting-provider |
windmill |
hosting-provider-windmill-backups |
hosting-provider-windmill-backups |
read, write, owner |
operator |
authelia |
<own deployment>-authelia-backups |
<own deployment>-authelia-backups |
read, write, owner |
operator |
authelia |
<own deployment>-authelia-cache-backups |
<own deployment>-authelia-cache-backups |
read, write, owner |
operator |
lldap |
<own deployment>-lldap-backups |
<own deployment>-lldap-backups |
read, write, owner |
operator |
mastodon |
<own deployment>-mastodon |
<own deployment>-mastodon |
read, write, owner |
operator |
mastodon |
<own deployment>-mastodon-backups |
<own deployment>-mastodon |
read |
operator |
mastodon |
<own deployment>-mastodon-backups |
<own deployment>-mastodon-backups |
read, write, owner |
operator |
mastodon |
<own deployment>-mastodon-cache-backups |
<own deployment>-mastodon-cache-backups |
read, write, owner |
operator |
peertube |
<own deployment>-peertube |
<own deployment>-peertube-playlists |
read, write, owner |
operator |
peertube |
<own deployment>-peertube |
<own deployment>-peertube-videos |
read, write, owner |
operator |
peertube |
<own deployment>-peertube-backups |
<own deployment>-peertube-backups |
read, write, owner |
operator |
peertube |
<own deployment>-peertube-backups |
<own deployment>-peertube-playlists |
read |
operator |
peertube |
<own deployment>-peertube-backups |
<own deployment>-peertube-videos |
read |
operator |
peertube |
<own deployment>-peertube-cache-backups |
<own deployment>-peertube-cache-backups |
read, write, owner |
operator |
pixelfed |
<own deployment>-pixelfed |
<own deployment>-pixelfed |
read, write, owner |
operator |
pixelfed |
<own deployment>-pixelfed-backups |
<own deployment>-pixelfed |
read |
operator |
pixelfed |
<own deployment>-pixelfed-backups |
<own deployment>-pixelfed-backups |
read, write, owner |
operator |
pixelfed |
<own deployment>-pixelfed-cache-backups |
<own deployment>-pixelfed-cache-backups |
read, write, owner |
operator |
valkey |
<own deployment>-valkey-cache-backups |
<own deployment>-valkey-cache-backups |
read, write, owner |
How the system keeps the tenants apart
- Nodes. Each deployment has its own nodes for each application and for
valkey,authelia,smtp,lldapandgrafana(core/setups/groups/operator.nix). The users, the sessions and the cache of a deployment are thus not in a store that a different deployment uses. - Network. All nodes are on one Incus bridge.
Each deployment gets a
/28of its own, and an Incus network ACL on each NIC drops the traffic to and from the other deployments (core/effects/tf/incus/operator/bridge-isolation.tf). The Incus host declares the ACL of each/28(core/components/hosting-provider/incus-bridge-acls.nix), thus the certificate of a deploy runner is restricted to the Incus project of the deployments and cannot change an ACL. Each NIC has MAC and IP address filtering (core/effects/tf/incus/instance.tf). A second ACL and the firewall of each node of the hosting provider permit only the ports that a deployment uses (core/components/hosting-provider/operator-reach.nix). The second ACL holds only the IPv4 addresses of the nodes of the hosting provider. The bridge has no IPv6 subnet (ipv6.address = noneincore/components/hosting-provider/incus.nix), thus Incus drops all IPv6 oneth0. A node of a deployment sends IPv6 only on its publiceth1, which has no NAT, thus the firewall of the destination node sees the real source address and applies the port limit. - Secrets. The policy of a node of a deployment names the KV paths with a template that OpenBao replaces with the namespace from the certificate of the node.
Thus a node reads only the entries of its own deployment (
core/components/hosting-provider/openbao/spire-auth.nix). - Databases. All deployments use the PostgreSQL node of the hosting provider. Each deployment has its own database and role for each application, and a node can read only the password of the role that has its own namespace.
- Buckets. All deployments use the Garage cluster of the hosting provider.
Each bucket and key has the namespace of the deployment as a prefix (
tenantincore/resources/garage/default.nix), and a key has access only to the buckets in the table above. - Metrics. The query path for the metrics of an application adds the
deployment_idlabel to each query (core/components/hosting-provider/metrics-query.nix). - Panel and api. An operator gets only the deployments that it owns (see In the panel).
These components are common to all tenants, and the separation there is weaker:
- The front-end deployer is one identity for all deployments.
Its OpenBao policy can write the entries of each deployment that the KV table gives, and it can read the password of the
postgressuperuser. The state backend has one useroperatorfor the states of all deployments (core/components/hosting-provider/terraform-backend/front.nix). - The Grafana of the hosting provider has one organization, thus
fediversity-staffandfediversity-adminscan read the telemetry of all deployments. - A member of
fediversity-adminscan read the secrets, the databases and the containers of all deployments.
Architecture
This part shows the system context of Fediversity and the containers of one installation of a hosting provider. It also shows the components of each deployment, with the services that each component requests and provides.
System context
flowchart TB operator["<b>Operator</b><br/>[Person]<br/>Has a deployment with applications"] user["<b>Application user</b><br/>[Person]<br/>Uses an application of an operator"] provider["<b>Hosting provider</b><br/>[Person]<br/>Runs the infrastructure and manages the accounts"] maintainer["<b>Maintainer</b><br/>[Person]<br/>Changes and releases the code"] fediversity["<b>Fediversity</b><br/>[Software system]<br/>Installs, configures and updates applications, manages users, moves deployments between hosting providers"] registrar["<b>Domain registrar</b><br/>[External system]<br/>OpenProvider, INWX or Porkbun"] dns["<b>DNS service</b><br/>[External system]<br/>Authoritative DNS that is not in the deployment, for example deSEC or PowerDNS"] acme["<b>ACME certificate authority</b><br/>[External system]<br/>Issues TLS certificates"] relay["<b>Mail relay</b><br/>[External system]<br/>SMTP submission service of a third party"] upstream["<b>Upstream sources</b><br/>[External system]<br/>nixpkgs and the other pinned sources"] other["<b>Other hosting provider</b><br/>[Software system]<br/>A different installation of Fediversity"] operator -->|"configures and deploys, HTTPS"| fediversity user -->|"uses the applications, HTTPS"| fediversity provider -->|"configures, deploys and monitors"| fediversity maintainer -->|"releases code and pins"| fediversity fediversity -->|"registers and transfers domains"| registrar fediversity -->|"publishes DNS records"| dns fediversity -->|"requests certificates"| acme fediversity -->|"sends mail"| relay fediversity -->|"gets packages and modules"| upstream fediversity -->|"exports and imports a deployment"| other classDef external stroke-dasharray: 5 5 class registrar,dns,acme,relay,upstream,other external
Each external system is a selection of the hosting provider, not a fixed part:
- Registrars:
core/effects/tf/registrars.nixhas one entry for each registrar, and the steps that each registrar can do. - DNS: the
dnsresource has a provider for a BIND node in the deployment (core/resources/dns/self-hosted.nix) and a provider for a service that the hosting provider already has (core/resources/dns/external.nix). Thecore/effects/octodnseffect publishes the records. Choose where the DNS zones are gives the selection. - Certificates: the
sslresource has an ACME provider, an OpenBao PKI provider and a self-signed provider (core/resources/ssl/). - Mail: the
smtpresource has a provider that runs a mail server in the deployment and a provider for an external relay (core/resources/smtp/). - Upstream sources:
npins/sources.jsonpins nixpkgs and each other source. The SBOM page tells how to find the software of a node.
The threat model gives the trust boundaries between these parties, and data protection gives the parties that get personal data.
Containers
The diagram shows the parts of one installation of a hosting provider. The
architecture page of the panel
has the container and component views of panel, and the
site of the API has those of api.
flowchart TB
browser["<b>Browser of the operator</b><br/>[Person and browser]"]
subgraph frontend["Front end"]
panel["<b>panel</b><br/>[Container: Django]<br/>Accounts, ownership of deployments, configuration form"]
panelDb[("<b>Panel database</b><br/>[Container: PostgreSQL or SQLite]")]
end
subgraph apiSide["API"]
api["<b>api</b><br/>[Container: FastAPI]<br/>Validates, queues builds, runs deployments"]
apiDb[("<b>API state store</b><br/>[Container: PostgreSQL or SQLite]")]
centrifugo["<b>Centrifugo</b><br/>[Container]<br/>Sends progress events"]
jobs["<b>Job runner</b><br/>[Container: subprocess or Windmill]<br/>Runs a build or a deployment"]
end
subgraph coreSide["core"]
programs["<b>Deployment programs</b><br/>[Container: Nix and OpenTofu]<br/>Change a configuration into machines, records and secrets"]
schema["<b>Schema and catalog</b><br/>[Container: Nix]<br/>JSON Schema of the options, list of applications"]
end
subgraph providerSide["Deployment of the hosting provider"]
services["<b>Shared services</b><br/>[Containers]<br/>See the diagrams below"]
incus["<b>Incus hypervisor</b><br/>[Container]<br/>Runs the machines of the deployments"]
end
subgraph operatorSide["Deployment of an operator"]
apps["<b>Applications and their services</b><br/>[Containers]<br/>See the diagrams below"]
end
browser -->|"HTTPS"| panel
browser -->|"HTTPS, bearer token"| api
centrifugo -->|"WebSocket"| browser
panel --> panelDb
api -->|"authorization callback, HTTPS"| panel
api --> apiDb
api -->|"publishes events"| centrifugo
api -->|"reads"| schema
api -->|"starts a job"| jobs
jobs -->|"runs"| programs
programs -->|"creates and changes machines"| incus
programs -->|"reads and writes state, secrets and addresses"| services
incus -->|"runs"| apps
apps -->|"use"| services
Notes for the diagram:
apiruns a deployment program as anixsubprocess, directly or in a Windmill job (api/src/fediversity_api/_runner.py,api/src/fediversity_api/deployment.py).apiandpanelare also components of the data model (api/nix/component/,panel/nix/component/), thus the deployment programs ofcorecan deploy them the same as each other service.- Configuration data flow shows how a configuration becomes a
deployment in
core. - Flake attributes lists the deployment programs.
Components of a deployment of an operator
The build makes the two diagrams and the two tables below from the code. They
show the components of the operator group and of the hosting-provider group
of core/setups/groups, each with its default settings. A node shows the
resources that the component provides (wiring.provides in
core/components/wiringProperties.nix). An arrow goes from a component to the
component that answers its request, and its label gives the resources. A
request goes to a component of the same group. If the group has no such
component, the request goes to the component of the hosting-provider group.
Two requests have a name that is different from the name of the backend that
answers them: a generateFiles request goes to varsBackend, and a garage
request goes to objectStore. A request without an arrow has no service
component that provides it. Examples are nixos-module and firewall, which
change the machine itself, and ssl, which has three providers.
Hosting resources describes each resource.
flowchart LR
subgraph applications["Applications that the operator selects"]
applications_mastodon["<b>mastodon<br/>peertube<br/>pixelfed</b>"]
end
subgraph own["Service components of the operator group"]
own_authelia["<b>authelia</b><br/>provides: oidc"]
own_grafana["<b>grafana</b>"]
own_lldap["<b>lldap</b><br/>provides: ldap"]
own_smtp["<b>smtp</b><br/>provides: smtp"]
own_valkey["<b>valkey</b><br/>provides: redis"]
end
subgraph central["Service components of the hosting-provider group"]
central_garage["<b>garage</b><br/>provides: objectStore"]
central_openbao["<b>openbao</b><br/>provides: varsBackend, pki"]
central_postgresql["<b>postgresql</b><br/>provides: postgresql"]
central_spire["<b>spire</b><br/>provides: spiffe"]
end
applications_mastodon -->|"garage"| central_garage
applications_mastodon -->|"generateFiles"| central_openbao
applications_mastodon -->|"postgresql"| central_postgresql
applications_mastodon -->|"spiffe"| central_spire
applications_mastodon -->|"oidc"| own_authelia
applications_mastodon -->|"ldap"| own_lldap
applications_mastodon -->|"smtp"| own_smtp
applications_mastodon -->|"redis"| own_valkey
own_authelia -->|"garage"| central_garage
own_authelia -->|"generateFiles"| central_openbao
own_authelia -->|"postgresql"| central_postgresql
own_authelia -->|"spiffe"| central_spire
own_authelia -->|"redis"| own_valkey
own_grafana -->|"generateFiles"| central_openbao
own_grafana -->|"spiffe"| central_spire
own_grafana -->|"oidc"| own_authelia
own_lldap -->|"garage"| central_garage
own_lldap -->|"generateFiles"| central_openbao
own_lldap -->|"postgresql"| central_postgresql
own_lldap -->|"spiffe"| central_spire
own_lldap -->|"oidc"| own_authelia
own_smtp -->|"spiffe"| central_spire
own_valkey -->|"spiffe"| central_spire
own_valkey -->|"oidc"| own_authelia
| Component | Kind | Provides | Requests |
|---|---|---|---|
authelia |
service | oidc |
garage, postgresql, generateFiles, redis, nixos-module, spiffe, firewall |
grafana |
service | -- | nixos-module, oidc, spiffe, ssl, generateFiles, firewall |
lldap |
service | ldap |
garage, postgresql, oidc, nixos-module, spiffe, generateFiles |
mastodon |
application | -- | nixos-module, garage, redis, postgresql, oidc, ldap, smtp, generateFiles, pod-roles, spiffe, ssl, firewall |
peertube |
application | -- | nixos-module, firewall, generateFiles, garage, redis, postgresql, oidc, ldap, smtp, spiffe, ssl |
pixelfed |
application | -- | nixos-module, generateFiles, garage, redis, postgresql, oidc, ldap, smtp, spiffe, ssl, firewall |
smtp |
service | smtp |
spiffe |
valkey |
service | redis |
nixos-module, oidc, spiffe, redis |
Components of the deployment of the hosting provider
flowchart LR
subgraph own["Service components of the hosting-provider group"]
own_authelia["<b>authelia</b><br/>provides: oidc"]
own_bind["<b>bind</b>"]
own_garage["<b>garage</b><br/>provides: objectStore"]
own_incus["<b>incus</b><br/>provides: instances"]
own_lldap["<b>lldap</b><br/>provides: ldap"]
own_netbox["<b>netbox</b><br/>provides: netbox"]
own_openbao["<b>openbao</b><br/>provides: varsBackend, pki"]
own_otel_collector["<b>otel-collector</b><br/>provides: otel"]
own_overview["<b>overview</b><br/>provides: uiIndex"]
own_postgresql["<b>postgresql</b><br/>provides: postgresql"]
own_smtp["<b>smtp</b><br/>provides: smtp"]
own_spire["<b>spire</b><br/>provides: spiffe"]
own_telemetry["<b>telemetry</b><br/>provides: otelBackend"]
own_terraform_backend["<b>terraform-backend</b><br/>provides: tfState"]
own_valkey["<b>valkey</b><br/>provides: redis"]
own_windmill["<b>windmill</b>"]
end
own_authelia -->|"garage"| own_garage
own_authelia -->|"generateFiles"| own_openbao
own_authelia -->|"postgresql"| own_postgresql
own_authelia -->|"spiffe"| own_spire
own_authelia -->|"redis"| own_valkey
own_bind -->|"garage"| own_garage
own_bind -->|"generateFiles"| own_openbao
own_bind -->|"spiffe"| own_spire
own_garage -->|"oidc"| own_authelia
own_garage -->|"spiffe"| own_spire
own_incus -->|"oidc"| own_authelia
own_incus -->|"generateFiles"| own_openbao
own_incus -->|"spiffe"| own_spire
own_lldap -->|"oidc"| own_authelia
own_lldap -->|"garage"| own_garage
own_lldap -->|"generateFiles"| own_openbao
own_lldap -->|"postgresql"| own_postgresql
own_lldap -->|"spiffe"| own_spire
own_netbox -->|"oidc"| own_authelia
own_netbox -->|"garage"| own_garage
own_netbox -->|"generateFiles"| own_openbao
own_netbox -->|"otel"| own_otel_collector
own_netbox -->|"postgresql"| own_postgresql
own_netbox -->|"smtp"| own_smtp
own_netbox -->|"spiffe"| own_spire
own_openbao -->|"oidc"| own_authelia
own_openbao -->|"garage"| own_garage
own_openbao -->|"spiffe"| own_spire
own_otel_collector -->|"generateFiles"| own_openbao
own_otel_collector -->|"spiffe"| own_spire
own_overview -->|"spiffe"| own_spire
own_postgresql -->|"oidc"| own_authelia
own_postgresql -->|"garage"| own_garage
own_postgresql -->|"generateFiles"| own_openbao
own_postgresql -->|"spiffe"| own_spire
own_smtp -->|"spiffe"| own_spire
own_telemetry -->|"oidc"| own_authelia
own_telemetry -->|"garage"| own_garage
own_telemetry -->|"generateFiles"| own_openbao
own_telemetry -->|"smtp"| own_smtp
own_telemetry -->|"spiffe"| own_spire
own_terraform_backend -->|"garage"| own_garage
own_terraform_backend -->|"generateFiles"| own_openbao
own_terraform_backend -->|"spiffe"| own_spire
own_valkey -->|"oidc"| own_authelia
own_valkey -->|"spiffe"| own_spire
own_windmill -->|"oidc"| own_authelia
own_windmill -->|"garage"| own_garage
own_windmill -->|"netbox"| own_netbox
own_windmill -->|"generateFiles"| own_openbao
own_windmill -->|"otel"| own_otel_collector
own_windmill -->|"postgresql"| own_postgresql
own_windmill -->|"smtp"| own_smtp
own_windmill -->|"spiffe"| own_spire
| Component | Kind | Provides | Requests |
|---|---|---|---|
authelia |
service | oidc |
garage, postgresql, generateFiles, redis, nixos-module, spiffe, firewall |
bind |
service | -- | garage, nixos-module, spiffe, generateFiles |
garage |
service | objectStore |
nixos-module, oidc, spiffe, firewall |
incus |
service | instances |
nixos-module, generateFiles, oidc, spiffe, ssl |
lldap |
service | ldap |
garage, postgresql, oidc, nixos-module, spiffe, generateFiles |
netbox |
service | netbox |
nixos-module, garage, postgresql, oidc, smtp, otel, generateFiles, spiffe, ssl |
openbao |
service | varsBackend, pki |
nixos-module, garage, oidc, spiffe, ssl |
otel-collector |
service | otel |
nixos-module, spiffe, generateFiles |
overview |
service | uiIndex |
nixos-module, spiffe, ssl, firewall |
postgresql |
service | postgresql |
nixos-module, oidc, garage, spiffe, generateFiles |
smtp |
service | smtp |
spiffe |
spire |
service | spiffe |
nixos-module |
telemetry |
service | otelBackend |
generateFiles, nixos-module, garage, spiffe, oidc, smtp, ssl |
terraform-backend |
service | tfState |
nixos-module, generateFiles, spiffe, garage, ssl |
valkey |
service | redis |
nixos-module, oidc, spiffe, redis |
windmill |
service | -- | generateFiles, nixos-module, dns, netbox, otel, spiffe, ssl, garage, postgresql, smtp, oidc |
api and panel are not in this table, because their component definitions
are outside core.
Capability flows
This part gives the run-time flow of each capability: install, configure and update an application, manage users, and switch to and from a different hosting provider. Each flow has a sequence diagram, the code paths, the stores that change, the failure paths, and the work that is planned.
The flows have these participants:
- The operator is the customer of the hosting provider. The operator uses a browser.
panelis the Django front-end (panel/). It keeps the accounts, the stored configurations and the permissions.apiis the FastAPI service (api/). It validates a configuration and starts a deploy.coreis the Nix and OpenTofu code (core/). It makes the schema, and it deploys.
The design of the API gives the contract of each call. The architecture of the panel gives the design of the panel.
Calls that each flow uses
Each call from the browser to api uses the same permission check:
- The browser gets a bearer token from
panelatapi/token/(views.ApiToken,panel/src/panel/tokens.py).panelsigns the token with itsSECRET_KEY. The token is valid forAPI_TOKEN_TTL. - The browser sends the call to
apiwith the token. apidoes not examine the token. It sends the procedure name, the scope and the headers to the callback ofpanelatapi/authz/(api/src/fediversity_api/authz.py,views.AuthzCallback).panelanswersallow, and for an allowed call the subject and the deployments that the caller can use.paneldenies a procedure that has no rule inAUTHZ_PROCEDURE_RULES.- If
paneldenies the call,apianswersauthz_denied(HTTP 403).
The diagrams show this check as one step, "check the permission".
Install an application
The operator fills in the form of a deployment, enables an application, and deploys. The same call does the first install and each later change.
sequenceDiagram
actor O as Operator
participant P as panel
participant A as api
participant C as core
participant I as Provider infrastructure
participant N as Node
O->>P: save the form
P->>P: store the configuration
O->>A: POST /deployments/submit/{id}
A->>P: check the permission
A->>C: build the schema of the module source
A->>A: migrate renamed options, validate
A->>C: evaluate the names of the deployment
A->>A: claim the domain, store the build as queued
A-->>O: build_id, status queued, position
A->>C: run tf-run.sh
C->>I: NetBox address, Incus instance and volume
C->>I: bucket, database, secrets in OpenBao
C->>I: hold the zone, register the domain, publish the records
C->>N: switch to the NixOS configuration
N->>N: order certificates with ACME, start the services
C->>N: read the state of the units
C-->>A: exit code and outputs
A-->>O: build events through Centrifugo
Code paths:
panel:views.DeploymentFormshows only the deployments of the operator.static/deployment_submit.jssaves the form inpanel, then callsapi.api: the routedeployment_submit(routes/deployments.py) callsprocedures.deployment_submit. In sequence, this function:- refuses a module source that is not on the trusted-source allowlist (
source_allowlist.py), - builds the schema and the rename table of the module source (
schema_build.py), - applies the renames and validates the configuration with
jsonschema, - takes the secrets of the operator out of the configuration (
operator_secrets.py), - refuses a full queue,
- evaluates the names of the deployment and claims the apex of the domain (
apex_claims.py), - makes the DNS token of the zone (
zone_tokens.py), writes the secrets of the operator to OpenBao, and stores the deployment (state.upsert_deployment), - starts the build (
deployment.start_deployment).
- refuses a module source that is not on the trusted-source allowlist (
- The build runs as a subprocess of
api, or as a Windmill job whenwindmill_base_urlis set (deployment.py). The runner startsnix develop .#<attribute> --impure --command tf-run.sh(_runner.py). core:tf-run.sh(core/effects/common/lib.nix,core/effects/tf/common/bootstrap-apply.sh) applies the OpenTofu graph ofcore/effects/tf/incus/operator/, then gives the outputs as JSON. The state of the graph is in the HTTP state backend.- DNS:
dns-lane-zone.tfandregistrar.tfhold the zone, register the domain and wait until the parent zone delegates.dns-lane-records.tfpublishes the names of each node and waits until the names resolve. All of this occurs before the switch of the node, because the certificate order of the node needs the records. - TLS:
core/resources/ssl/acme.nixsetssecurity.acme. A node orders the certificate of an ordinary name with the HTTP challenge, and the certificate of a wildcard name with the DNS-01 challenge. - After the switch,
null_resource.deploy_verdictreads the units of each node. A node with a unit that continues to restart fails the deploy.
Stores that change:
| Store | Change |
|---|---|
Database of panel |
The stored configuration of the deployment. |
Database of api |
The deployment, its build and the audit event. |
| OpenBao KV | The secrets of the operator, the S3 keys, the claim of the domain. |
| OpenTofu state | Each resource of the graph. |
| NetBox | The address and the virtual machine record. |
| Incus | The instance and its persistent volume. |
| Garage, PostgreSQL | The bucket and its key, the database and its role. |
| DNS zone, registrar | The records of the nodes, the registration of the domain. |
Failure paths:
- A configuration that fails the schema gives
invalid_configuration(HTTP 400). Nothing is stored. - A full queue gives
queue_full(HTTP 409). A domain that a different deployment holds givesconflict(HTTP 409). - A schema build that passes its time limit gives
build_timeout(HTTP 504). - A deploy that fails sets the build to failed.
apipublishes the end of the log. The resources that the apply made stay in the state, thus the next submit continues from them. - When
apistarts again,reconcile_orphaned_builds(deployment.py) marks the builds that lost their process.
Planned:
- Builds of one deployment that run in parallel (issue 242 of fediversity/fediversity).
- The configuration of the deploy method in the panel (issues 723 and 1071).
Configure an application
The form of an application comes from the options of its NixOS module. No form is written by hand.
sequenceDiagram
actor O as Operator
participant F as Form in the browser
participant P as panel
participant A as api
participant C as core
O->>P: open the form of the deployment
P-->>F: stored, deployed and default module source
F->>A: GET /schema and /schema/migrations
A->>C: build provider-configuration-schema and configuration-renames
C-->>A: JSON Schema and rename table
A-->>F: JSON Schema and rename table
F->>F: apply the renames, show the form
O->>F: change a value
F->>F: validate with AJV
O->>F: deploy
F->>P: save the configuration
F->>A: POST /deployments/submit/{id}
A->>A: apply the renames, validate with jsonschema
A->>C: run tf-run.sh
Code paths:
- Schema:
core/lib/json-schema.nixmakes a JSON Schema from the options offrontendConfiguration(core/lib/data-model.nix). Add an application and the data model give the options. api:schema_build.pybuilds the outputsprovider-configuration-schemaandconfiguration-renamesof the module source. A build has a time limit, andapilimits the number of builds that run together.apikeeps the result for each source in memory. The routes are inroutes/schema.py.- Form:
panel/forms-island-adapter/panel-adapter.tsgets the schema and the rename table, validates each change with AJV, and shows the difference between the schema of the stored source and the schema of the source that is deployed. - Validation occurs two times: in the browser with AJV, and in
apiwithjsonschema(procedures._checked_submission). The check inapiis the authority. - Apply: the apply is the submit of the install flow. A changed option changes the NixOS configuration of a node, and the deploy switches the node to it.
Stores that change: the stored configuration in panel, then the stores of the install flow.
Failure paths:
- A module source that is not on the allowlist is refused before a build.
- A configuration that fails the schema gives
invalid_configuration. POST /deployments/preview/{id}does the checks of the submit and shows the nodes that a submit deploys. It deploys nothing.
Planned:
- Resource versions, which let
apifind a difference between the environments of the provider and the pinned data model before a deploy (issue 634). - To follow the new revisions of a fork of the module source (issue 3053).
Update an application
An update is a submit with a newer module source. The module source selects the revision of the application modules.
sequenceDiagram
actor O as Operator
participant P as panel
participant A as api
participant C as core
participant N as Node
participant S as Object store
alt policy manual
P-->>O: notice that a newer revision exists
O->>A: POST /deployments/submit/{id} with the update applied
else policy automatic or window
A->>A: the scheduler submits the stored configuration again
end
A->>A: resolve the null source to the revision of the provider
A->>A: apply the renames, validate
A->>C: run tf-run.sh
C->>N: switch to the new NixOS configuration
N->>N: guard reads the recorded version
alt downgrade or skipped release series
N-->>C: the guard fails, the service does not start
else upgrade
N->>N: stop the service, dump the database
N->>S: restic snapshot of the dump
N->>N: start the service, run the migrations after the deployment
N->>N: record the new version
end
C->>N: read the state of the units
Code paths:
- Policy:
api/src/fediversity_api/update_policy.pyhas the policiesmanual,automaticandwindow. The operator sets the policy withPUT /deployments/update_policy/{id}(views.DeploymentUpdatePolicy,static/update_policy.js).static/update_notice.jsshows that a newer revision exists. The form has a check box beside the deploy button: when it is off, the submit hasapply_updateset tofalse(static/deployment_submit.js) andapikeeps the deployed revision. - Scheduler:
automatic_updates.run_schedulersubmits the stored configuration with anullmodule source. It tries each revision of the provider one time for each deployment (state.claim_update_attempt), and writes the audit actionautomatic_update. It does not update a deployment that is suspended or not active. A deployment whose deployed module source is not the source of the provider is due too (run_due_updates), thus the scheduler moves a deployment from a fork to the source of the provider. - Version boundary:
version_boundary.pyselects, for the layerscore/resourcesandcore/effects, the newer of the pinned revision and the revision of the provider. - Guards: each application has a
systemd.stateMigrationsentry.core/components/operator/mastodon/migrations.nixrecords the version, refuses a downgrade or a skipped minor release series, dumps the database before an upgrade, and runs the migrations after the services are up again. The guards of PeerTube and Pixelfed refuse a downgrade and dump the database before an upgrade.core/setups/nixos/postgresql.nixguards the major version of PostgreSQL and usespg_upgrade. - Backup before the upgrade:
core/components/shared/database-upgrade-backup.nixsends the dump to the restic repository<server>-upgradein the backup bucket of the application. The repository keeps the snapshots of the 2 newest upgrades. Without an object store, the dump stays on the node.
Stores that change: the stores of the install flow, the database of the application (schema migrations), the version file of each guard on the node, and the restic repository of the upgrade backups.
Failure paths and rollback:
- A guard that fails keeps the service down.
deploy_verdictthen fails the deploy. - The deploy method
k8ssets a pod that does not become ready back to the previous image (kubectl rollout undoincore/effects/k8s/effect.nix) and fails the deploy. The state on the volume of the pod stays as it is. No other code path rolls back a deploy automatically. - A guard refuses a downgrade, thus a submit of the older revision does not go back after a migration of the data.
- The way back is the dump from before the upgrade. The hosting provider guide gives the restore procedures.
- A submit with
apply_updateset tofalseuses the module source of the last build that was deployed.
Planned:
- Application versions that do not depend on the revision of the provider (issue 304).
- Resource versions (issue 634).
- An automatic rollback of a failed update, data included (issue 3052).
- A guide for the operator through the options that an update adds or changes (issue 3054).
Manage users
There are two levels of users: the account of the operator in panel, and the users of each deployment.
Each deployment has its own directory (lldap at users.<domain>) and its own identity provider (Authelia at auth.<domain>).
sequenceDiagram
actor O as Operator
actor U as User of an application
participant P as panel
participant IdP as Identity provider of the hosting provider
participant L as lldap of the deployment
participant Au as Authelia of the deployment
participant K as OpenBao KV
participant App as Application
O->>P: log in
P->>IdP: OpenID Connect authorization
IdP-->>P: identity of the operator
O->>L: add a user, put the user in a group
U->>App: log in
App->>Au: OpenID Connect authorization
Au->>L: check the password and the groups
Au-->>App: identity of the user
L->>K: timer writes the members of the role groups
App->>K: timer reads the members
App->>App: set the role of each member
Code paths:
- Log-in to
panel:panel/src/panel/oidc.pyuses OpenID Connect whenOIDC_ISSUER,OIDC_CLIENT_IDandOIDC_CLIENT_SECRET_FILEare all set (settings.py). If not,paneluses the log-in of Django. A wallet log-in (panel.wallet.WalletAuth) opens only an account that linked the wallet. - Directory:
core/components/shared/lldapandcore/components/shared/authelia.core/resources/ldap/lldap-provider.nixmakes the first admin user. - Single sign-on:
core/resources/oidc/authelia-oidc-provider.nixmakes an OpenID Connect client for each application.allowedGroupsbecomes an Authelia policy that allows only the members of those groups. The application reads its client from theoidccontract (for examplecore/components/operator/mastodon/oidc.nix). - Groups to roles:
core/resources/ldap/application-role-groups.nixand therole-groups.nixof each application declare the role groups, for examplemastodon-owners,mastodon-adminsandmastodon-moderators.core/resources/ldap/publish-roles.nixwrites the members of the role groups to the OpenBao KV on a timer, only when they change. Theldap-roles.nixof each application reads the members on a timer and sets the roles in the application. - Legal roles gives the role of each group in each application. The hosting provider guide and the user guide of the panel give the procedure.
Stores that change: the database of lldap, the members entry in the OpenBao KV, and the accounts and roles in the database of each application. The account of the operator is in the database of panel.
Failure paths:
- A role sync that fails does not fail a deploy. The timer tries again at its next interval.
- A role change is not immediate: the publish and the sync each run on a timer.
- Without the OpenBao backend for secrets, the role sync does nothing.
- A user who is not in an allowed group cannot log in to the application.
Planned: api has no route, and panel has no view, that changes the users of a deployment. The operator uses the web interface of lldap. Issue 3055 asks for the decision if the panel must manage users.
Switch hosting provider
A switch has two parts: the export at the source provider, and the import at the target provider. The migration string moves between them. The operator carries the string.
sequenceDiagram
actor O as Operator
participant SA as api of the source
participant SN as Nodes of the source
participant S3 as Object store of the source
participant R as Registrar
participant TA as api of the target
participant TC as core of the target
participant TN as Nodes of the target
O->>SA: GET /migration-string/{id}
SA->>R: read the auth code, unlock the domain, remove the DNSSEC keys
SA->>SN: stop the application services
SN->>S3: restic snapshot of each application
SA->>SA: mark the deployment as exported, submit suspend
SA-->>O: migration string
O->>TA: POST /migration-import/{id} with the string
TA->>TA: check the module source, remove the provider settings of the source
TA->>TC: dry evaluation
TA->>TA: write the snapshot pointers and the auth code to OpenBao
TA->>TC: run tf-run.sh
TC->>R: transfer the domain with the auth code
TC->>TC: hold the zone, wait for the delegation, publish the records
TC->>TN: switch to the NixOS configuration
TN->>S3: restore each application from its snapshot
TC->>TC: delete the pointers and the auth code
SA->>TN: zone transfer, copy the records to the old zone
Code paths:
- Panel:
static/migration_export.jscallsGET /migration-string/{id}and shows the string.static/migration_import.jsmakes a deployment and sends the string toPOST /migration-import/{id}. - Domain:
migration.migration_export(api/src/fediversity_api/migration.py) first callsrelease_domain, which reads the auth code at the registrar, unlocks the domain and removes its DNSSEC keys. This occurs before the snapshot, thus a registrar that fails stops no service. - Export:
migration_exportthen runscore/effects/migration/run.sh. This script stops the services of each node withmigration-quiesce(core/components/shared/migration-quiesce.nix), then makes a snapshot of each backup unit. Thenapimarks the deployment as exported and submits the actionsuspend. - Import:
migration.migration_importparses the string, removes the settings that belong to the source provider, and changes the configuration to the shape of the target.import_check.pyevaluates the result withDRY_RUN=1beforeapistores it. Then the import is the submit of the install flow. - Restore:
migration_kv.pywrites the snapshot pointers and the auth code to the OpenBao KV of the deployment. The OpenTofu graph gets only the snapshot references.core/effects/tf/incus/operator/restore-migration.shreads the pointers, restores each unit, and deletes the pointers after the restore. - Domain transfer:
registrar.tfgives the auth code toopenprovider_domain.operator, thennull_resource.drop_domain_transferdeletes the code from the KV. - DNS change: the deploy at the target holds the zone on the nameservers of the target (
dns-lane-zone.tf).await_delegationwaits until the parent zone delegates to them. Then the deploy publishes the records of the nodes. - Zone follow: when the source holds the zone at deSEC,
zone_follow.pyat the source copies the records of the target to the old zone until the old delegation is out of the caches of resolvers. It asks the nameservers of the target for a zone transfer that it signs with thetransfer_keyof the migration string.
The hosting provider guide gives the procedure of the export and of the restore. The contract templates give the switching clauses.
Stores that change:
| Provider | Store | Change |
|---|---|---|
| Source | Object store | One new restic snapshot for each application. |
| Source | Database of api |
The deployment is exported and suspended. |
| Source | Registrar | The domain is unlocked and has no DNSSEC keys. |
| Source | Zone at deSEC | The records follow the target for the cache time. |
| Target | All stores of the install flow | A new deployment. |
| Target | OpenBao KV | The pointers and the auth code, until the restore and the transfer end. |
| Target | Databases and files of the applications | The data of the source. |
| Target | Registrar | The domain is in the account of the target. |
Trust between the two providers
- The two providers have no direct connection for the import, and no common account. The operator carries the migration string. The string is a bearer capability: its holder can read all data of the deployment.
- The target does not trust the configuration of the source. It validates the configuration against the schema, and accepts only a module source that is on its trusted-source allowlist.
- The target removes the provider settings of the source from the string (
operator_settings,RUNTIME_SETTING_KEYS). - The target reads the snapshots directly from the object store of the source, with the credentials of the string.
- The target allows a zone transfer from the addresses in
transfer_sources, to the holder of thetransfer_key. - The string carries no registrar login.
Secrets that move
The migration string carries:
- for each backup unit, the S3 endpoint, bucket, access key and secret key of the snapshot repository, the password of the repository, and the snapshot reference,
- the keys that decrypt the data of the applications,
- the auth code of the domain, when the source has the domain in its registrar account,
- the TSIG key of the zone follow.
api does not keep the string, and does not write it to a log or to an audit event.
At the target, the pointers and the auth code are in the OpenBao KV of the deployment, and not in the OpenTofu state or in the Windmill job. The deploy deletes them after their use.
The threat model and data protection give the related risks and duties.
Downtime
The applications are down from the export until the target restores them. The export stops the services before the snapshot, thus no write is lost. The time has these parts: the snapshot, the transport of the string by the operator, the deploy at the target, the restore, and the DNS change. The code sets no limit on the full time, and the tree has no measured value. The limits that the code does set are:
- the dry evaluation of the import: 1800 seconds, then
build_timeout, - the cache time that the zone follow uses: at most 604800 seconds (
TTL_CEILINGinzone_follow.py).
To roll back, resume the source deployment: its data stays, and its services start again. After a domain transfer, the resume of the source fails.
Different versions at the two providers
- The migration string must name its module source. An import without a module source is refused, because the revision of the target can be different from the revision of the source.
- The target builds the schema of that module source, and applies its rename table to the configuration.
- The target deploys with the version boundary of the update flow: the pinned revision for the application modules, and the newer revision for
core/resourcesandcore/effects. - The dry evaluation refuses a configuration that the pinned revision cannot deploy with the method and the environments of the target. This occurs before
apistores the deployment. - A source node that runs an older revision does not give the keys of its applications. The export continues, and the string has no keys for that node.
Failure paths:
- If a snapshot or the suspend fails, the export starts the services again and the deployment is not exported. If a snapshot fails, the export also locks the domain again.
- If
apicannot read the auth code, the export fails and does not change the source. - A second export of a suspended deployment gives
conflict. - A string in an old form, or with a module source that is not on the allowlist, gives
invalid_configuration. - If the restore fails, the deploy fails. The pointers stay in the KV, thus a new deploy restores again.
- A domain transfer that the registry refuses shows as
transfer_failedinGET /domains.
Planned:
- Resource versions, which let the target reconcile a difference in the environments of the two providers (issue 634).
- The domain calls need the registrar command of
api, which exists foropenprovider. A second registrar is planned (issue 1908).
Configuration data flow
This page tells how an operator configuration becomes a deployment. The data model and hosting resources pages are the option references for the same parts. The concepts page defines the terms.
From a configuration to a deployment
flowchart
subgraph configuration
application-config
end
subgraph resource[resource module]
request
policy
end
subgraph application[application module]
application-options{{application-options}} --> application-config
application-config --> config-mapping
request{{request}} --> config-mapping
config-mapping(config-mapping)
end
subgraph fediversity[fediversity setup]
config-mapping -->|required resources| resource-mapping(resource-mapping)
policy{{policy}} --> provider-config -->|available resources| resource-mapping -->|configuration| deployment{deployment}
end
maintainer((maintainer)) -->|curates| resource
contributor((developer)) -->|curates| application
operator((operator)) -->|enters| configuration
hosting-provider((hosting provider)) -->|maintains| fediversity
In the diagram, a circle is an actor, a box with angled sides is a type, a rectangle is a value, a box with round corners is a function, and a diamond is a state. An arrow points to the part that uses the other part. An application module is a component that the operator can select.
- The operator selects the applications and sets their options in the panel.
The panel form comes from
frontendConfiguration, which has only the operator-facing components. The result is aconfiguration. - Each enabled component maps its options to the resources that it needs. The
implementationof a component, aconfig-mapping, returns one request for each resource, keyed by an instance name. For example, Forgejo requests a TLS certificate as"tls".ssl, its secrets as"age".secrets, and outbound mail as"forgejo-smtp".smtp. - The hosting provider declares an environment. Its
resourcesset the policy of each resource: which provider gives the resource, and with which limits. - The
implementationof the environment, aresource-mapping, collects the requests of all components. For each resource, theprocessfunction of the policy turns the requests into NixOS modules or OpenTofu resources. - The result is a deployment for an effect, for example a set of Incus containers that OpenTofu creates and NixOS configures.
environments.<env>.deployment does all of these steps for one deployment.
Resources
A resource is the interface between a component and a hosting provider.
The component sets the request, and the hosting provider sets the policy.
The request/policy resource pattern on the data model page
tells more, and the hosting resources page lists the
request options of each resource.
A resource is not a contract of RFC 189. A resource usually uses one or more such contracts to give what a request asks for. The Contracts (RFC 189) page tells how.
Where the code is
| Part | Location |
|---|---|
| The data model | core/lib/data-model.nix |
| The resources | core/resources/ |
| The components | core/components/ |
| The environments | core/setups/hosts-common.nix |
| The effects | core/effects/ |
Further reading
- The configuration data flow of the Fediversity architecture.
- The data model review of 2025-07-03.
Contracts (RFC 189)
This page tells how the contracts of RFC 189 connect the software that needs
a service to the software that gives it. A contract is the interface
between a consumer module and a provider module of a service, for example
smtp, postgresql, ssl or oidc.
A contract is not a part of the Fediversity data model. The data model has resources, the interface between a component and a hosting provider. A resource usually uses one or more contracts to give what a request asks for. The configuration data flow page tells how a configuration becomes a deployment through the resources.
For example, Forgejo asks the smtp contract for outbound mail with a sender
address. The mail relay that provides the contract answers with a host, a port,
a user name and a password file:
A contract has these parts, under config.contracts.<type>:
want.<consumer>.<instance>.request-- what a consumer asks for.providers.<name>-- the providers that can answer the request.defaultProvider-- the provider that answers when the consumer selects none. A want that no provider answers fails the evaluation.providerRequests.<provider>-- the requests that a provider reads.results.<consumer>.<instance>-- what the consumer reads back, for example a host, a port and a password file.
Each contract type defines the shape of its request and its result. The consumer and the provider agree only on these shapes, so a hosting provider can replace a provider without a change to the consumer. The contract reference lists the request and the result of each contract type, with its providers and an example.
Contracts across nodes
Each node of a deployment is a separate NixOS evaluation, so the nodes do not
share config.contracts. When the consumer and the provider are on different
nodes, crossNodeContract and crossNodeWant in core/lib/lib.nix evaluate
the contract once, and give the result to each node. A contract that only gives
coordinates, such as the address of a DNS server, does not need this.
What a cross-node contract costs tells when to use one.
Where the code is
| Part | Location |
|---|---|
| The contracts machinery | nix/contracts/lib/contracts/module.nix |
| The contract types | nix/contracts/lib/contracts/definitions/ |
| The cross-node helpers | core/lib/lib.nix |
The contracts guide of the vendored contracts tree tells how to write a consumer, a provider and a new contract type.
Further reading
- RFC 189, the contracts RFC, and its implementation in Self Host Blocks.
- nixpkgs pull request 506343, which adds default providers, providers outside NixOS, contracts across nodes, and introspection to make a form.
- Talks on contracts: NixCon NA 2024 and NixCon 2024.
Credentials register
This part lists the credentials of the system: where each one is, what makes it, how long it lives, how it changes, and what a change interrupts. The page gives names and locations only. The components that can read each credential are in the access model.
The word "Open" in a cell means: the code and the documentation give no procedure. The page gives a period or a procedure only when the code or a document holds it.
How the register is made
The tables that have the label "generated" come from the code at each build of the documentation (core/docs/md/technical-design/credentials.nix).
Each generated table has two inputs:
- The names, which the build reads from the code: the realised configuration of the nodes with the groups of
core/testers/groups.nix, the filecore/resources/secrets/secrets.nix, and the workflows in.forgejo/workflows/. - The facts that the code does not hold (the procedure and what it interrupts), which a person writes in
credentials.nix.
The build compares the two inputs.
The check core-docs fails when a name has no facts, or when facts have no name.
The other tables are written by hand in this file.
The specification of the secret rotation gives the requirements for rotation. Its table in "Key Entities" adds, for each secret, the backups that depend on the secret and how the consumers get a new value. This page is the source for the list of the credentials and for the state of their rotation.
Credentials that OpenBao rotates
OpenBao makes these credentials and changes them on a schedule. No person and no deploy writes them. A consumer reads the current value through its OpenBao agent. The lifetime of a value is the rotation period. The code does not state what a rotation interrupts: the column "Consumer refresh" of the specification gives how each consumer gets the new value.
Generated:
| Credential | Path that the consumer reads in OpenBao | Rotation period |
|---|---|---|
PostgreSQL role of the database hosting-provider-authelia |
postgresql/static-creds/hosting-provider-authelia (rotating) |
24h |
PostgreSQL role of the database hosting-provider-lldap |
postgresql/static-creds/hosting-provider-lldap (rotating) |
24h |
PostgreSQL role of the database hosting-provider-netbox |
postgresql/static-creds/hosting-provider-netbox (rotating) |
24h |
PostgreSQL role of the database hosting-provider-windmill |
postgresql/static-creds/hosting-provider-windmill (rotating) |
24h |
PostgreSQL role of the database operator-authelia |
postgresql/static-creds/operator-authelia (rotating) |
24h |
PostgreSQL role of the database operator-lldap |
postgresql/static-creds/operator-lldap (rotating) |
24h |
PostgreSQL role of the database operator-mastodon |
postgresql/static-creds/operator-mastodon (rotating) |
24h |
PostgreSQL role of the database operator-peertube |
postgresql/static-creds/operator-peertube (rotating) |
24h |
PostgreSQL role of the database operator-pixelfed |
postgresql/static-creds/operator-pixelfed (rotating) |
24h |
LDAP account authelia-bind, read by the node authelia |
ldap/static-cred/authelia-bind |
24h |
LDAP account authelia-bind, read by the node operator-authelia |
ldap-operator/_shared/static-cred/authelia-bind |
24h |
LDAP account authelia-bind of each deployment |
ldap-operator/<namespace>/static-cred/authelia-bind |
24h |
Valkey user app of the namespace hosting-provider-authelia, read by the node authelia |
database/static-creds/hosting-provider-authelia |
See the text below |
Valkey user app of the namespace hosting-provider-valkey, read by the node valkey |
database/static-creds/hosting-provider-valkey |
See the text below |
Valkey user app of the namespace operator-authelia, read by the node operator-authelia |
database/static-creds/operator-authelia |
See the text below |
Valkey user app of the namespace operator-mastodon, read by the node operator-mastodon |
database/static-creds/operator-mastodon |
See the text below |
Valkey user app of the namespace operator-peertube, read by the node operator-peertube |
database/static-creds/operator-peertube |
See the text below |
Valkey user app of the namespace operator-pixelfed, read by the node operator-pixelfed |
database/static-creds/operator-pixelfed |
See the text below |
Valkey user app of the namespace operator-valkey, read by the node operator-valkey |
database/static-creds/operator-valkey |
See the text below |
The rotation period of a Valkey namespace is not an option.
It is 24 hours in core/components/hosting-provider/openbao/valkey-engine.nix.
The engine rotates the user app of a namespace.
It connects as the user default, and it does not rotate that user: the password of default is the generated secret valkey-<namespace> in the next section.
The LDAP engine rotates only the accounts in the table.
It connects with the management account of lldap, and it does not rotate that account (the instances ldap-bind-* in the next section).
Generated secrets in the KV of OpenBao
A node declares each secret that it needs as a generateFiles request.
These facts apply to each row of the table:
- Where: the KV path is
<prefix>/secret/<instance>/<file>in the mountsecret. The prefix isvarsfor the hosting provider andvars/operator-deployments/<namespace>for a deployment. - Maker: the script of the request makes the value at the first deploy of the node (
nix/contracts/nixos/modules/services/security/vars-openbao.nix). - Lifetime: the code sets no end. A deploy keeps a value that is in KV.
- Change: a deploy with
VARS_FORCE_UPLOAD=<instance>orVARS_FORCE_UPLOAD=<instance>/<file>, or the commandupload-vars-openbao-forceon the node, makes a new value and writes it. This is a write, not a rotation procedure: it does not tell what the consumers do with the new value. Thus a row without a procedure for its consumers has "Open". - For some keys OpenBao keeps all versions, because a backup from before a change needs the old key. The column "KV keeps all versions" shows them.
The table shows the nodes of the hosting provider group and of one operator group of the testers. A file that is not secret (for example a public key) is not in the table.
Generated:
| Credential | Instances | Files | Nodes that request it | KV keeps all versions | Rotation | What a rotation interrupts |
|---|---|---|---|---|---|---|
| Password of the restic repositories of an application | authelia-backups-restic, bind-backups-restic, grafana-backups-restic, hosting-provider-authelia-cache-backups-restic, hosting-provider-valkey-cache-backups-restic, lldap-backups-restic, mastodon-backups-restic, netbox-backups-restic, operator-authelia-cache-backups-restic, operator-mastodon-cache-backups-restic, operator-peertube-cache-backups-restic, operator-pixelfed-cache-backups-restic, operator-valkey-cache-backups-restic, peertube-backups-restic, pixelfed-backups-restic, postgresql-backups-restic, windmill-backups-restic |
password |
hosting-provider/authelia, hosting-provider/bind, hosting-provider/lldap, hosting-provider/netbox, hosting-provider/postgresql, hosting-provider/telemetry, hosting-provider/valkey, hosting-provider/windmill, operator/authelia, operator/lldap, operator/mastodon, operator/peertube, operator/pixelfed, operator/valkey |
no | By hand: Password of the restic repositories | The procedure restarts openbao-agent-default. The repositories accept the old and the new password during the change |
| Password and salt of the encrypted copy of the media buckets | mastodon-media-backup, peertube-media-backup, pixelfed-media-backup |
password, salt |
operator/mastodon, operator/peertube, operator/pixelfed |
no | Open | Open |
| Secret key of an S3 key of garage | authelia-backups-s3, bind-backups-s3, grafana-backups-s3, hosting-provider-authelia-cache-backups-s3, hosting-provider-valkey-cache-backups-s3, lldap-backups-s3, netbox-backups-s3, openbao-backups-s3, postgresql-backups-s3, telemetry-s3, terraform-backend-backups-s3, windmill-backups-s3 |
secretKey |
hosting-provider/garage, hosting-provider/telemetry |
no | Open | Open |
| Admin token and RPC secret of garage | garage-admin |
adminToken, rpcSecret |
hosting-provider/garage |
no | Open | Open |
| Storage encryption key of authelia | authelia-oidc, operator-authelia-oidc |
storageEncryptionKey |
hosting-provider/authelia, operator/authelia |
yes | By hand: Authelia storage encryption key | Authelia is stopped during the change |
| OIDC and JWT secrets of authelia | authelia-oidc, operator-authelia-oidc |
jwtSecret, oidcClientSecret, oidcClientSecretDigest, oidcHmacSecret, oidcIssuerPrivateKey |
hosting-provider/authelia, operator/authelia |
no | Open | Open |
| Session secret of authelia | authelia-session |
sessionSecret |
hosting-provider/authelia, operator/authelia |
no | Open | Open |
Password of the user operator of the state backend |
terraform-backend-operator |
password |
hosting-provider/terraform-backend |
no | Open | Open |
| TSIG keys of the name server | acme-dns, octodns, operator-dns |
tsigSecret |
hosting-provider/bind |
no | Open | Open |
| DNSSEC key of the name server | dnssec |
key, private, state |
hosting-provider/bind |
no | Open | Open |
| Password of the management account of lldap | ldap-bind-hosting-provider, ldap-bind-operator |
bindPassword |
hosting-provider/lldap, operator/lldap |
no | Open | Open |
| JWT secret of lldap | ldap-jwt-hosting-provider, ldap-jwt-operator |
jwtSecret |
hosting-provider/lldap, operator/lldap |
no | Open | Open |
| Key seed of lldap | ldap-key-hosting-provider, ldap-key-operator |
keySeedEnv |
hosting-provider/lldap, operator/lldap |
yes | Never without loss: Keys that cannot change without data loss | lldap does not start with a new seed. With LLDAP_FORCE_UPDATE_PRIVATE_KEY=true it starts, and all users must set a new password |
| Secret key and token peppers of NetBox | netbox |
apiTokenPeppersFile, secretKeyFile |
hosting-provider/netbox |
no | Open | Open |
| Password of the PostgreSQL administrator | postgresql-admin |
password |
hosting-provider/postgresql |
no | Open | Open |
| Submission passwords of the mail server | operator-smtp, smtp |
autheliaPassword, lldapPassword, mastodonPassword, netboxPassword, peertubePassword, pixelfedPassword, telemetryPassword, windmillPassword |
hosting-provider/smtp, operator/smtp |
no | Open | Open |
| DKIM key of the mail server | operator-smtp-dkim, smtp-dkim |
fediversity.test_fediversity.key |
hosting-provider/smtp, operator/smtp |
no | Open | Open |
| Secret key of the Grafana of the hosting provider | telemetry-grafana |
secretKey |
hosting-provider/telemetry |
yes | By hand: Grafana secretKey |
Grafana restarts |
| Old secret keys of the Grafana of the hosting provider | telemetry-grafana-previous |
secretKeys |
hosting-provider/telemetry |
no | Written and emptied in the procedure of the row above | See the row above |
| Admin password of the Grafana of the hosting provider | telemetry-grafana |
adminPassword |
hosting-provider/telemetry |
no | Open | Open |
| Credentials of the telemetry and audit endpoints | audit-ingest-api, metrics-query, node-telemetry, telemetry-ingest |
authHeader, htpasswd, password |
hosting-provider/telemetry, operator/grafana |
no | Open | Open |
| Secret key of the Grafana of a deployment | grafana-secret |
secretKey |
operator/grafana |
no | Open | Open |
| Encryption key of the state backend | terraform-backend |
kmsKey |
hosting-provider/terraform-backend |
no | Open | Open |
Password of the Valkey user default of a namespace |
valkey-hosting-provider-authelia, valkey-hosting-provider-valkey, valkey-operator-authelia, valkey-operator-mastodon, valkey-operator-peertube, valkey-operator-pixelfed, valkey-operator-valkey |
password |
hosting-provider/valkey, operator/valkey |
no | Open | Open |
| Password of the superadmin of windmill | windmill-superadmin |
adminPassword |
hosting-provider/windmill |
no | Open | Open |
| Active Record encryption keys of Mastodon | mastodon-keys |
activeRecordEncryptionDeterministicKey, activeRecordEncryptionKeyDerivationSalt, activeRecordEncryptionPrimaryKey |
operator/mastodon |
yes | Never without loss: Keys that cannot change without data loss | The private keys of the accounts and the two-factor secrets do not decrypt |
SECRET_KEY_BASE and VAPID key of Mastodon |
mastodon-keys |
secretKeyBase, vapidPrivateKey |
operator/mastodon |
yes | By hand, with the effects that Keys that cannot change without data loss gives | No data is lost. All users must log in again, the links in password reset and confirmation emails stop, and the push subscriptions stop until the clients subscribe again |
| Secret of PeerTube | peertube-secret |
secret |
operator/peertube |
yes | Never without loss: Keys that cannot change without data loss | The two-factor authentication of all users that use it fails |
APP_KEY of Pixelfed |
pixelfed-app |
appKey |
operator/pixelfed |
yes | By hand: Pixelfed APP_KEY |
Pixelfed restarts two times. A user that did not use pixelfed during the change must log in again |
Old APP_KEY values of Pixelfed |
pixelfed-app-previous |
appKeys |
operator/pixelfed |
no | Written and emptied in the procedure of the row above | See the row above |
| Passport key of Pixelfed | pixelfed-passport |
privateKey |
operator/pixelfed |
no | Open | Open |
The change of an application encryption key has its own work item (fediversity/fediversity issue 2433).
Other secrets in the KV of OpenBao
No generateFiles request of a node of core makes these entries.
The api node and the panel node are not in the setups that the build of this page reads, thus their rows are written by hand.
| Credential | Access | Where | Maker | Lifetime | Rotation | What a rotation interrupts |
|---|---|---|---|---|---|---|
| Key of the target of the external copy of the backups | The garage node | vars/secret/backup-external-copy/<name> |
A person writes it with bao kv put (hosting provider guide) |
The code sets no end | Open | Open |
| Submission password for the upstream mail relay | The node that relays the mail | vars/secret/smarthost-upstream/<name> (core/resources/smtp/maddy-provider.nix) |
Not made by a node | The code sets no end | Open | Open |
Token of the api for windmill (WINDMILL_TOKEN) |
The api node | Instance windmill, file apiToken |
The windmill node, at run time (core/components/hosting-provider/windmill/windmill.nix) |
The code sets no end | Open | Open |
Service token between the panel and the api (API_SERVICE_TOKEN) |
The panel node and the api node | Instance panel-service, file token (panel/nix/component/panel.nix) |
The panel node, at its first deploy | The code sets no end | Open | Open |
Callback token of the authorization check (AUTHZ_CALLBACK_TOKEN) |
The panel node and the api node | Instance panel-authz, file token (panel/nix/component/panel.nix, api/nix/component/authz-token.nix) |
The panel node, at its first deploy | The code sets no end | Open | Open |
| Secret key of the panel | The panel node | Instance panel, file secret_key |
The panel node, at its first deploy | The code sets no end | Open | Open |
| API key of centrifugo | The api node | Instance api-centrifugo, file apiKey (api/nix/component/default.nix) |
The api node, at its first deploy | The code sets no end | Open | Open |
| Credentials that the api deploys with (state backend password, zones token) | The api node | Instance deployer (core/components/hosting-provider/deploy-credentials.nix) |
The CD publishes them from the secrets of the forge | The code sets no end | Open | Open |
| Tokens that a node writes at run time | The nodes that the access model gives | Instances deploy-tokens (incus), netbox (NetBox) and metrics (Mastodon, PeerTube, Pixelfed) |
The node that owns the instance, at run time | The code sets no end | Open | Open |
Credentials of OpenBao
| Credential | Access | Where | Maker | Lifetime | Rotation | What a rotation interrupts |
|---|---|---|---|---|---|---|
| Scoped tokens (list below) | The units of the openbao node |
/var/lib/openbao-init/tokens/<name> on the openbao node |
openbao-init |
Periodic token with a period of 768h |
Automatic: openbao-tokens-renew renews each token daily. openbao-init makes a token again when it is missing or not valid |
Open |
| Root token | An administrator on the openbao node |
Not stored: openbao-init revokes its root token when it exits |
openbao-init, or the command openbao-root-token |
Until openbao-init exits |
Does not apply | Does not apply |
| Unseal key | The openbao node, and the backup recipients |
Sealed to the TPM of the node. A copy is encrypted to fediversity.bootstrapBackupRecipients |
openbao-init |
The code sets no end | Open. When the recipients change, openbao-init encrypts the same key again (core/components/hosting-provider/openbao/escrow.nix) |
Open |
| CA of the PKI engine | OpenBao | The PKI mount of OpenBao (core/components/hosting-provider/openbao/pki-init.nix) |
The openbao node (pki-init.nix) |
maxLeaseTtl, 10 years by default. A certificate that it signs lives 30 days at most (roleMaxTtl) |
Open | Open |
| CA of the admin SSH login | OpenBao signs with it. Each node trusts its public key | The engine ssh-admin of OpenBao. The forge variable ADMIN_SSH_CA_KEYS holds the public keys |
The openbao node (ssh-certificates.nix) |
The code sets no end. A certificate that it signs lives 8 hours (certificateTtl) |
By hand: Rotate the CA | The procedure trusts the two keys until the old certificates end |
Generated, the names of the scoped tokens:
database-init, deployer-kv-prune, kv-key-metadata, ldap-engine, oidc-auth, operator-deployer-token, pki-init, pki-leaf, snapshot, spire-auth, spire-cert-auth, ssh-certificates, valkey-engine
Credentials of SPIRE
Each node logs in to OpenBao with an identity that SPIRE gives.
Without an upstream root, the SPIRE server signs its own CAs, and each CA lives for about one day.
With the upstream root, the root lives 10 years and each CA lives 24 hours.
The private key of the root is the forge secret SPIRE_ROOT_KEY in the section about the forge.
The specification of spire gives the custody and the rotation of the root in "The upstream root".
Keys of persons
| Credential | Access | Where | Maker | Lifetime | Rotation | What a rotation interrupts |
|---|---|---|---|---|---|---|
| SSH certificate of an administrator | The administrator | With the administrator | The engine ssh-admin |
8 hours | Automatic: the administrator gets a new certificate (Log in with a certificate) | Does not apply |
| SSH key of a contributor | The contributor, who has root on the machines of the development infrastructure. The key is also a recipient of the agenix secrets |
The public half is in core/keys/contributors/ |
The contributor | The code sets no end | By hand, no schedule: core/keys/README.md, "Adding a contributor" and "Removing a contributor". A removal needs a rekey, a deploy, and a rotation of the secrets |
Open |
Keys and password hash in ADMIN_EMERGENCY |
The holder of the offline emergency key, as the account emergency (wheel) on each node. Only for when no certificate is available |
The forge variable ADMIN_EMERGENCY (core/setups/nixos/users.nix); the private key offline |
A person | The code sets no end | Open | Open |
SSH key of the CD (cd) |
The CD jobs. The key is also a recipient of the agenix secrets | The public half is core/keys/cd-ssh-key.pub |
A person | The code sets no end | Open | Open |
| Host key of a system | The system. The key is a recipient of the agenix secrets that it reads | The public half is in core/keys/systems/ |
Open | The code sets no end | Open | Open |
Secrets of the development infrastructure
The files core/resources/secrets/<name>.age hold the secrets of the forge and its runners.
Each file is encrypted to the recipients that core/resources/secrets/secrets.nix gives.
A person makes each value, and the code sets no end for it.
A change of the recipients is a rekey (core/resources/secrets/README.md, "Rekeying"): it needs a person who is a recipient now, and it does not change the value.
Generated:
| File | Credential | Rotation | What a rotation interrupts |
|---|---|---|---|
anubis-forgejo-key.age |
Key of the anubis in front of the forge | By hand: core/resources/secrets/README.md, "anubis-forgejo-key" |
Each visitor of the forge solves one challenge again |
forgejo-database-password.age |
Password of the database user of the forge | By hand: core/resources/secrets/README.md, "forgejo-database-password" |
New database connections of the forge fail between the change in the database and the restart of forgejo |
forgejo-email-password.age |
Password of the mail account of the forge | By hand: core/resources/secrets/README.md, "forgejo-email-password" |
Open |
forgejo-runner-tokens-dev2.age |
Registration tokens of the runners of one machine | By hand: core/resources/secrets/README.md, "forgejo-runner-tokens-<machine>" |
Open |
forgejo-runner-tokens-dev3.age |
Registration tokens of the runners of one machine | By hand: core/resources/secrets/README.md, "forgejo-runner-tokens-<machine>" |
Open |
forgejo-runner-tokens-dev4.age |
Registration tokens of the runners of one machine | By hand: core/resources/secrets/README.md, "forgejo-runner-tokens-<machine>" |
Open |
forgejo-runner-tokens-fediversity-ci.age |
Registration tokens of the runners of one machine | By hand: core/resources/secrets/README.md, "forgejo-runner-tokens-<machine>" |
Open |
security-pgp-key.age |
OpenPGP key that reporters of vulnerabilities encrypt to | By hand: core/resources/secrets/README.md, "security-pgp-key and security-pgp-passphrase" |
Each reader of the reports imports the key again |
security-pgp-passphrase.age |
Passphrase of the OpenPGP key | By hand: core/resources/secrets/README.md, "security-pgp-key and security-pgp-passphrase" |
See the row above |
Secrets of the forge
The CD jobs and the other workflows read these secrets from the Forgejo Actions secrets of the repository.
A person with the rights to set secrets on the forge writes each one.
The code sets no end for a value, apart from the incus client certificate: the command in notes/cd-credentials.md makes it for 365 days.
.forgejo/workflows/cd-env.example.sh gives the same names for a deploy from a workstation.
The forge variable TF_BACKEND_HTPASSWD holds one line for each user of the state backend (cd, operator, and workstation-<person>), with the hash of the password only.
The rotation of the password of a workstation-<person> user is Open.
Generated:
| Secrets | Credential | Rotation | What a rotation interrupts |
|---|---|---|---|
SSH_PRIVATE_KEY |
SSH key that the CD jobs use to reach the nodes | Open | Open |
INCUS_CLIENT_CRT, INCUS_CLIENT_KEY |
Client certificate of the CD for the incus API | By hand: notes/cd-credentials.md, "Rotating it" |
The procedure adds the new certificate before it removes the old one |
TF_HTTP_PASSWORD |
Password of the user cd of the state backend. The variable TF_BACKEND_HTPASSWD holds its hash. The state backend node makes the password of the user operator |
Open | Open |
SMARTHOST_UPSTREAM_PASSWORD |
Password of the account of the upstream smarthost. The deploy of the hosting provider writes it to the KV entry that the relays read | Open | Open |
DNS_API_TOKEN, OPERATOR_DNS_API_TOKEN, OPERATOR_ZONES_MANAGER_TOKEN |
Tokens for the API of the DNS provider | Open | Open |
DNS_TSIG_SECRET, DNS_SECONDARIES |
TSIG keys: the key of a zone that the name server holds, and the transfer keys of the secondaries | Open | Open |
NETBOX_API_TOKEN |
Token for the API of NetBox | Open | Open |
INWX_USERNAME, INWX_PASSWORD, INWX_SHARED_SECRET, OPENPROVIDER_USERNAME, OPENPROVIDER_PASSWORD, PORKBUN_API_KEY, PORKBUN_SECRET_KEY |
Credentials of the registrars | Open | Open |
FORGEJO_BACKUP_RESTIC_PASSWORD, FORGEJO_BACKUP_S3_ACCESS_KEY, FORGEJO_BACKUP_S3_SECRET_KEY |
Password and S3 key of the offsite backup of the forge | By hand: set the value, then start the workflow cd-dev-forgejo (notes/cd-credentials.md) |
Open |
RELEASE_SIGNING_KEY |
SSH key that signs the release tags | By hand: Set up the signing key | The file .forgejo/workflows/release-allowed-signers keeps the old key for the old tags |
SPIRE_ROOT_KEY |
Private key of the upstream root of SPIRE. The variable SPIRE_ROOT_CERT holds the certificate |
By hand: the specification of spire, "The upstream root" |
Open |
SBOM_PACKAGE_TOKEN |
Token that publishes the SBOM package (SBOM) | Open | Open |
BOT_PAT, DEPLOY_KEY |
Token and SSH key that the automation workflows use on the forge (update.yaml, registry-terms.yaml, vulnerability-scan.yaml) |
Open | Open |
The workflows also read these names as secrets. They give an address, a name or a selector, not a credential. Generated:
ADMIN_SOURCES, BIND2_IPV4, BIND2_IPV6, BIND_IPV4, BIND_IPV6, CLUSTER_SOURCES, DNS_APEX_NAMESERVERS, DNS_DOMAIN, DNS_PROVIDER, DNS_SECONDARIES_BY_ADDRESS, DNS_SERVER_URL, NETBOX_CLUSTER_NAME, NETBOX_CUSTOM_FIELDS, NETBOX_PREFIX_SELECTOR, NETBOX_RANGE_SELECTOR, NETBOX_SERVER_URL, NETBOX_TAGS, NETBOX_TENANT, OPERATOR_DNS_PROVIDER, OPERATOR_DNS_SERVER_URL, OPERATOR_NETBOX_RANGE_SELECTOR
Signing key of the bridge cache
The incus host signs the paths that its binary cache serves to its containers.
- Where:
/var/lib/incus-bridge-cache/on the host. The key does not go out of the host. - Maker: the host.
- Lifetime:
fediversity.incus.bridgeCacheKey.rotationDays, 90 days by default. - Rotation: automatic. The timer
incus-bridge-cache-key-rotateruns one time each day. The containers trust the previous key forfediversity.incus.bridgeCacheKey.overlapDays, 30 days by default. - What a rotation interrupts:
nix-serverestarts.
The procedure after a compromise is in Signing key of the bridge cache.
Data subject requests
A person can ask to see, to correct, to erase or to get a copy of the personal data that a deployment or a hosting provider holds about them. This page tells, for each store that holds personal data, which party answers such a request, and which mechanism the code in this repository gives for it. Where the code gives no mechanism, the page says "no mechanism found".
This page is not legal advice. It does not tell if a request is valid, which data is in its scope, or how much time the answer can take. It tells only what the code can do.
The stores and their personal data are in Data protection. The part state-register has the full list of the state that the system stores. This page does not repeat these lists.
Who answers a request
The party that controls the data answers the request. Data protection gives the roles:
- The operator is the controller of the data of its deployment: the accounts and the content of its applications, and the users of the lldap and of the Authelia of its group.
- The hosting provider is the controller of its own data: the accounts of the panel, the users of the lldap of the hosting-provider group, the audit records, and the accounts of Grafana, NetBox and Windmill.
- The hosting provider is the processor of the operator for the data of a deployment. For some stores of a deployment, only the hosting provider has a tool, for example a command on a node or a backup. Then the operator answers the request, and the hosting provider does the step.
flowchart TD
request["Request of a person"] --> which{"Which data?"}
which -->|"Data of a deployment"| operator["Operator answers"]
which -->|"Data of the hosting provider"| provider["Hosting provider answers"]
operator --> app["Functions of the application"]
operator --> lldap["lldap of the group"]
operator --> config["Configuration in the panel, for example deleteData"]
operator -->|"asks for a step on a node, a log or a backup"| provider
provider --> panel["Panel account page and admin pages"]
provider --> api["Routes of api/"]
provider --> node["Commands on a node, logs, backups"]
Mechanisms that the code gives
| Mechanism | Who uses it | What it does |
|---|---|---|
| The account page of the panel | The account | /account/ shows the user name and the email address of the account. /account/history/ shows the audit events of the account (AccountDetail and AccountHistory in panel/src/panel/views.py). |
| The admin pages of the panel | A panel administrator | /admin/ shows, changes and removes an account (panel/src/panel/admin.py). Before a removal, the panel queues the destroy of each deployment of the account through api/. If api/ does not queue a destroy, the removal does not occur (_destroy_deployments in panel/src/panel/models.py). |
GET /audit-events of api/ |
The party that the authorization callback permits | It gives the audit events of one actor on all deployments (api/src/fediversity_api/routes/audit.py). With the panel, an account reads its own events, and a superuser reads the events of each account (reads_all_history in panel/src/panel/views.py). |
/domain_contacts/{contact_id} of api/ |
The operator, through the Domain contact form of the panel | GET gives the contact from the state, and PUT makes the contact at the registrar equal to the body (api/src/fediversity_api/routes/domains.py). DELETE deletes the customer at the registrar and the state, and refuses while a domain of the registrar account names the contact. |
DELETE /deployments/{id} of api/ |
The operator, through the panel | It destroys a deployment with all its data. See Know what a destroy keeps. |
deleteData of an application |
The operator, in the configuration of the deployment | With enable false and deleteData true, the next deployment deletes the database, the buckets, the generated secrets and the cache of the application (core/components/sharedOptions.nix). It deletes the data of all users of the application, not of one person. |
| The web UI of lldap | A member of lldap_admin of the group |
lldap serves its web UI at https://users.<domain>/ (the label is in core/components/shared/lldap/ui-subdomain.nix). A member of lldap_admin manages each user of the group there. Without lldap_admin, the web UI shows a user only their own account (core/resources/ldap/lldap-provider.nix). The management functions are functions of lldap, which is not in this repository. |
media-backup-<app> erase <bucket> <key> |
A person with a shell on the node of the application | It removes one object from the media backup and from all of its history (core/resources/garage/media-backup.sh). No route of api/ and no page of the panel runs this command. |
| Retention | No person | The logs, the audit events, the jobs of Windmill and the backups expire after a period. See Know which personal data the logs keep and Know how long the backups keep personal data. |
The panel and api/ have no route that finds, changes, erases or exports the data of one user of an application. The functions of the application do this work. This repository does not contain the code of the applications, thus this page does not describe these functions. Find them in the documentation of the application version that the deployment runs.
Data of a deployment
The operator answers a request about this data.
| Store | Access | Rectification | Erasure | Export for one person |
|---|---|---|---|---|
| The database, the buckets and the local files of Mastodon, Pixelfed or PeerTube | The functions of the application, by the user or by an administrator of the application. The code adds no mechanism. | The functions of the application. The code adds no mechanism. | The account deletion of the application. The operator settings ACCOUNT_DELETION and ACCOUNT_DELETE_AFTER of Pixelfed apply to it. deleteData erases the data of all users of the application. |
PeerTube: the operator can set export.users.enabled in settings (core/components/operator/peertube/default.nix). The code sets no value. Mastodon and Pixelfed: the functions of the application. The code adds no mechanism. |
| The cache of an application (the valkey of the group, or a local redis) | No mechanism found. | No mechanism found. | No mechanism found for one person. deleteData deletes the cache of the application as a whole. |
No mechanism found. |
| The users of the lldap of the group | The user reads their own account at https://users.<domain>/. A member of lldap_admin reads each user there. |
A member of lldap_admin changes the user there. |
A member of lldap_admin deletes the user there. |
No mechanism found. |
| The database of the Authelia of the group (consents, second factors, OIDC state) | No mechanism found. | No mechanism found. | No mechanism found. | No mechanism found. |
| The mail queue and the journal of the SMTP node of the group | No mechanism found. | No mechanism found. | No mechanism found. The journal expires. | No mechanism found. |
| The journal of the nodes of the deployment, and its copy in Loki | No mechanism found for one person. The lines of one deployment have the label deployment_id in Loki. |
No mechanism found. | No mechanism found for one person. The lines expire. | No mechanism found. |
| The media backup of an application | No mechanism found. A backup is only for a restore, see Erase personal data. | No mechanism found. | media-backup-<app> erase <bucket> <key> on the node, for each object. The hosting provider does this step. |
No mechanism found. |
| The restic backups (the databases, the local files, the caches) | No mechanism found. | No mechanism found. | No mechanism for one record. An erased record stays until the backup expires, see Erase personal data. restic forget and prune remove whole snapshots before then. |
No mechanism found. |
The *-backups buckets that a destroy keeps |
No mechanism found. | No mechanism found. | A timer deletes the bucket at the end of the grace period. An expires_at in the retention record deletes it earlier. |
No mechanism found. |
After a restore from a backup that is older than an erasure, the erasure must be done again. The operation "Record an erasure" of an application keeps the time and the reference of each erasure on the node, and a restore from an older snapshot lists them until "Confirm the erasures". See Do the erasures again after a restore.
The export of a deployment for a migration gives all the data of the deployment to a different hosting provider, and stops its applications. It is not an export for one person. See Export a deployment for a migration.
Data of the hosting provider
The hosting provider answers a request about this data. The persons are the staff of the operators and the staff of the hosting provider.
| Store | Access | Rectification | Erasure | Export for one person |
|---|---|---|---|---|
| The account of the panel | The account reads /account/. A panel administrator reads the account at /admin/. |
A panel administrator changes the account at /admin/. The account page has no form for the user name or the email address. |
A panel administrator removes the account at /admin/. The removal also removes the deployments of the account, after the panel queued their destroy. The panel has no page where an account removes itself. |
No mechanism found. |
The deployments of an account and their configuration, in the panel and in api/ |
The operator reads them in the panel. | The operator changes the configuration in the panel. | The operator deletes the deployment. The panel keeps the row of a deleted deployment (deleted_at in panel/src/panel/models.py) until the account is removed. |
No mechanism found. |
The audit_event table of api/ |
/account/history/ of the panel, or GET /audit-events. |
No mechanism found. | No mechanism found for one person. api/ removes the events that are older than its retention. |
No mechanism found. |
The audit lines in the tenant audit of Loki |
No mechanism found for one person. | No mechanism found. | No mechanism found. This tenant refuses the deletion API of Loki (deletion_mode = "disabled" in core/components/hosting-provider/telemetry.nix). The lines expire. |
No mechanism found. |
| The domain contact of an operator (the OpenTofu state and the registrar) | The operator reads it at Domain contact on the account page, through GET /domain_contacts/{contact_id}. |
The operator changes it in the same form, through PUT. A change of a name makes a new customer at the registrar. The call deletes the old customer, unless a domain names it. |
The operator erases it with "Erase contact" in the same form, through DELETE. The call refuses while a domain of the registrar account names the contact. The removal of the panel account erases it also. |
No mechanism found. |
| The users of the lldap of the hosting-provider group | The user reads their own account at https://users.<domain>/. A member of lldap_admin reads each user there. |
A member of lldap_admin changes the user there. |
A member of lldap_admin deletes the user there. |
No mechanism found. |
| The database of the Authelia of the hosting-provider group | No mechanism found. | No mechanism found. | No mechanism found. | No mechanism found. |
| The accounts of Grafana, NetBox and Windmill | The administration pages of each service. The code adds no mechanism. | The administration pages of each service. The code adds no mechanism. | The administration pages of each service. The code adds no mechanism. | No mechanism found. |
| The jobs of Windmill | No mechanism found for one person. | No mechanism found. | No mechanism found for one person. Windmill removes a completed job after services.fediversity-windmill.jobRetentionDays. |
No mechanism found. |
| The journal, Loki, the nginx access log and the audit lines of OpenBao | No mechanism found for one person. | No mechanism found. | No mechanism found for one person. The lines expire. | No mechanism found. |
| The backups of the databases of these services | No mechanism found. | No mechanism found. | No mechanism found. An erased record stays until the backup expires. | No mechanism found. |
The removal of a panel account first erases the domain contact of the account. A contact that a domain of the registrar account names stays, and the panel writes a warning to its log. The removal does not remove the audit events of its actor or the user in lldap. Each of these is a different store with its own row in this table.
Where no mechanism exists
The code gives no mechanism for these requests:
- An export of the data of one person, for all stores but the accounts of the applications. The export of a deployment is for the deployment as a whole (#3058).
- Access, rectification or erasure for one person in the logs, the telemetry, the jobs of Windmill and the audit records. Only the retention removes this data. The account history of the panel gives access to the audit events of one actor (#3062).
- Access, rectification or erasure in the database of Authelia and in the caches of the applications (#3061).
- Erasure of one record from a restic backup before it expires. The procedure removes whole snapshots (#3060).
- A page where a panel account removes itself, or changes its user name or email address (#3056).
- One command or one page that finds all the data of one person in all stores. A party that answers a request goes to each store (#3057).
#2194 tracks the compliance needs of customers in the EU, and these gaps belong to it.
Deployment topology
This page shows which nodes a deployment has, where each node runs, and which networks connect the nodes. It also shows where the trust boundaries of the threat model are in that topology. The glossary defines the terms.
The build makes the diagrams and the tables of this page from the code: the
group definitions (core/setups/groups), the components (core/components),
the constants of the bridge
(core/components/hosting-provider/incus-bridge.nix) and the attribute
families of core (core/render/catalog.nix). The tables show the groups
with the example apex example.org and with no settings from the environment.
Groups and nodes
A group is a set of machines that one deployment makes or changes.
core/setups/groups/default.nix defines four groups:
hosting-providerhas the shared services of a hosting provider.operatorhas the applications of one operator and the services that only that operator uses. Each deployment of an operator is one instance of this group.hypervisorhas the host that runs the Incus daemon. It is a group of its own because it hosts the instances that the deployment of thehosting-providergroup makes.devhas the forge and the runners of the continuous integration.
Each group has two rosters of machines, published and internal. In the
operator group, the published roster has the nodes of the applications and
the internal roster has the nodes of the services that the applications use.
Each node runs one component. In the tables, the column "Application" tells if an operator can
enable or disable the component, and the column "Provides" gives the resources
that the component supplies to other components (wiring.provides in
core/components/wiringProperties.nix).
Group dev
| Node | Roster | Component | Application | Provides |
|---|---|---|---|---|
dev2 |
published |
forgejo-runner |
no | -- |
dev3 |
published |
forgejo-runner |
no | -- |
dev4 |
published |
forgejo-runner |
no | -- |
fediversity-ci |
published |
forgejo-runner |
no | -- |
forgejo |
published |
forgejo |
no | -- |
Group hosting-provider
| Node | Roster | Component | Application | Provides |
|---|---|---|---|---|
authelia |
published |
authelia |
no | oidc |
bind |
published |
bind |
no | -- |
garage |
published |
garage |
no | objectStore |
lldap |
published |
lldap |
no | ldap |
netbox |
published |
netbox |
no | netbox |
openbao |
published |
openbao |
no | varsBackend, pki |
otel-collector |
published |
otel-collector |
no | otel |
overview |
published |
overview |
no | uiIndex |
postgresql |
published |
postgresql |
no | postgresql |
smtp |
published |
smtp |
no | smtp |
spire |
published |
spire |
no | spiffe |
telemetry |
published |
telemetry |
no | otelBackend |
terraform-backend |
published |
terraform-backend |
no | tfState |
valkey |
published |
valkey |
no | redis |
windmill |
published |
windmill |
no | -- |
Group hypervisor
| Node | Roster | Component | Application | Provides |
|---|---|---|---|---|
incus |
published |
incus |
no | instances |
Group operator
| Node | Roster | Component | Application | Provides |
|---|---|---|---|---|
authelia |
internal |
authelia |
no | oidc |
grafana |
internal |
grafana |
no | -- |
lldap |
internal |
lldap |
no | ldap |
smtp |
internal |
smtp |
no | smtp |
valkey |
internal |
valkey |
no | redis |
mastodon |
published |
mastodon |
yes | -- |
peertube |
published |
peertube |
yes | -- |
pixelfed |
published |
pixelfed |
yes | -- |
Notes for the tables:
- The nodes
apiandpanelare not in the tables. The leaves add them to thepublishedroster of thehosting-providergroup (api/default.nix,panel/default.nix). - The
hosting-providergroup has the nodebind2only when the environment gives its address (core/setups/groups/hosting-provider.nix). - The
operatorgroup has no database node and no object store node. Its applications use thepostgresqlandgaragenodes of the hosting provider (core/setups/groups/operator.nix). - The machine
incusof thehypervisorgroup and the machinesdev2,dev3,dev4andfediversity-ciof thedevgroup each have their own NixOS configuration incore/setups/hosts/.
Deployment methods
A deployment method decides how the nodes of a group come to exist and how they get their configuration. Flake attributes lists all methods and their commands. The table shows the methods that each group has.
| Group | Deployment methods |
|---|---|
dev |
k8s, node-effects, ssh, tf, tf-incus |
hosting-provider |
k8s, node-effects, ssh, tf, tf-incus |
hypervisor |
k8s, node-effects, ssh, tf, tf-incus |
operator |
k8s, k8s-operator, node-effects, ssh, tf, tf-incus, tf-incus-operator |
The methods give three variants of the topology:
- Machines that exist. The methods
sshandtfdeploy to machines that are there before the deployment. The methods provision nothing (core/setups/ssh-hosts.nix,core/setups/tf-hosts.nix). - Incus instances. The methods
tf-incusandtf-incus-operatormake one Incus instance for each node on the hypervisor, and then deploy to it (core/setups/tf-incus-hosts.nix,core/setups/tf-incus-operator-hosts.nix). - Kubernetes pods. The methods
k8sandk8s-operatorrun each node as a pod in a cluster (core/effects/k8s/effect.nix).
The method node-effects deploys nothing.
Incus instances
flowchart TB
internet(["Internet"])
operatorPerson(["Operator"])
admins(["Contributors and administrators"])
upstream(["Upstream sources and binary caches"])
backupTarget(["External backup target"])
subgraph dev["Group dev: machines that exist"]
dev_dev2["dev2<br/>component forgejo-runner"]
dev_dev3["dev3<br/>component forgejo-runner"]
dev_dev4["dev4<br/>component forgejo-runner"]
dev_fediversity_ci["fediversity-ci<br/>component forgejo-runner"]
dev_forgejo["forgejo"]
end
subgraph hypervisor["Group hypervisor: machine incus"]
daemon["Incus daemon<br/>API on port 8443"]
gateway["Bridge address 10.0.100.1<br/>dnsmasq and nix-serve"]
public["Public segment<br/>bridge pubbr0, second interface eth1 of a node"]
subgraph bridge["Bridge incusbr0: 10.0.96.0/20"]
subgraph provider["Group hosting-provider: 10.0.100.2-10.0.100.254"]
hosting_provider_authelia["authelia"]
hosting_provider_bind["bind"]
hosting_provider_garage["garage"]
hosting_provider_lldap["lldap"]
hosting_provider_netbox["netbox"]
hosting_provider_openbao["openbao"]
hosting_provider_otel_collector["otel-collector"]
hosting_provider_overview["overview"]
hosting_provider_postgresql["postgresql"]
hosting_provider_smtp["smtp"]
hosting_provider_spire["spire"]
hosting_provider_telemetry["telemetry"]
hosting_provider_terraform_backend["terraform-backend"]
hosting_provider_valkey["valkey"]
hosting_provider_windmill["windmill"]
leaves["api and panel<br/>nodes that the leaves add"]
end
subgraph operator["Group operator: one /28 of 10.0.104.0/21 for each deployment"]
subgraph operatorPublished["Roster published"]
operator_mastodon["mastodon"]
operator_peertube["peertube"]
operator_pixelfed["pixelfed"]
end
subgraph operatorInternal["Roster internal"]
operator_authelia["authelia"]
operator_grafana["grafana"]
operator_lldap["lldap"]
operator_smtp["smtp"]
operator_valkey["valkey"]
end
end
end
end
internet -->|"boundary 1"| public
public --> provider
public --> operatorPublished
internet -->|"boundary 2: ports 22 and 8443"| daemon
operatorPerson -->|"boundary 3"| leaves
operator -->|"boundary 4: ports of the first table below"| provider
operator -->|"ports of the second table below"| gateway
provider --> gateway
dev -->|"boundary 6"| daemon
admins -->|"boundary 7"| hypervisor
upstream -->|"boundary 8"| gateway
daemon -->|"makes the instances"| bridge
operator -->|"boundary 5"| hosting_provider_openbao
hosting_provider_garage -->|"boundary 9"| backupTarget
The diagram shows the hosting-provider group, one deployment of the
operator group, the hypervisor and the dev group. The numbers are the
numbers of the trust boundaries in the
threat model.
Instances
- One Incus daemon on the hypervisor runs the instances of the hosting
provider and of all operator deployments
(
core/setups/groups/operator.nix). - The name of an instance of an operator deployment is
<namespace>-<node>. The deployment of the hosting provider has an empty namespace (core/setups/tf-incus-hosts.nix). - An instance is a container by default. The setting
instanceTypeacceptscontainerandvirtual-machine(core/effects/common/effect-meta.nix). - A node that uses the
/nix/storeof the hypervisor must be a container. A deployment can usevirtual-machineonly if none of its nodes uses that store (core/effects/tf/incus/instance.tf). - A container gets a software TPM 2.0 by default (
enableTpmincore/effects/common/effect-meta.nix). The SPIRE agent of the node uses it to attest. - The deployment program does not use SSH into a container. It connects to
the Incus API on port 8443 of the hypervisor and uses
incus exec(core/setups/tf-incus-hosts.nix). - The nodes of the
hosting-providergroup that start deployments have a client credential for the Incus API (deployRunnerAppsincore/setups/hosts-common.nix).
Network segments
All instances are on one bridge of the hypervisor, with IPv4 only. The bridge
is the interface eth0 of each instance.
| Segment | Value | Use |
|---|---|---|
| Bridge subnet | 10.0.96.0/20 |
All instances on the hypervisor |
| Bridge address | 10.0.100.1 |
The hypervisor: gateway, resolver and substituter of the instances |
| Provider prefix | 10.0.96.0/21 |
The nodes of the hosting provider |
| DHCP range | 10.0.100.2-10.0.100.254 |
The leases of the nodes of the hosting provider |
| Operator prefix | 10.0.104.0/21 |
One /28 for each operator deployment, with static addresses |
- The dnsmasq service of the bridge gives each node of the hosting provider a
lease and a name (
core/components/hosting-provider/incus-bridge.nix). - Each operator deployment gets a
/28from NetBox. An Incus network ACL on each interface of such a node drops the traffic to and from the other operator deployments (core/components/hosting-provider/incus-bridge.nix). - A node of the hosting provider accepts internal traffic from the bridge
subnet. A node of an operator deployment accepts internal traffic from the
provider prefix and from the addresses of its own deployment
(
internalSourcesincore/setups/hosts-common.nix). - The public segment is a second interface,
eth1, of an instance. With the uplink modenat, which is the default of the Incus methods, an instance has no such interface (core/setups/wiring/public-uplink.nix). On the hypervisor, the bridgepubbr0on a VLAN interface is the parent of the public interfaces (core/setups/hosts/incus.nix). - A port of a node is open to the internal sources only, until the component
that serves the port publishes it (
core/setups/wiring/node-selection.nix,core/resources/firewall).
A node of an operator deployment can connect to these ports of the nodes of
the hosting provider, by the resource that the node of the hosting provider
supplies (core/components/hosting-provider/operator-reach.nix):
| Resource | TCP ports | UDP ports |
|---|---|---|
objectStore |
443, 3900 | -- |
oidc |
443 | -- |
otelBackend |
443 | -- |
pki |
443, 8083 | -- |
postgresql |
5432 | -- |
smtp |
587 | -- |
spiffe |
8081 | -- |
varsBackend |
8200 | -- |
The nodes on the bridge can connect to these ports of the hypervisor
(core/components/hosting-provider/hypervisor-reach.nix,
core/components/hosting-provider/operator-reach.nix):
| Source | TCP ports | UDP ports |
|---|---|---|
| A node of the hosting provider | 22, 53, 443, 5000, 8443 | 53, 67 |
| A node of an operator deployment | 53, 5000 | 53, 67 |
Kubernetes pods
flowchart TB
internet(["Internet"])
registry["Image registry"]
subgraph cluster["Kubernetes cluster"]
balancer["Load balancer or shared Gateway"]
subgraph namespace["Namespace of one operator deployment: default-deny NetworkPolicy"]
subgraph pod_authelia["Node authelia"]
pod_authelia_deployment["Deployment authelia<br/>one pod"]
pod_authelia_state[("PersistentVolumeClaim<br/>authelia-state")]
pod_authelia_deployment --- pod_authelia_state
end
subgraph pod_grafana["Node grafana"]
pod_grafana_deployment["Deployment grafana<br/>one pod"]
pod_grafana_state[("PersistentVolumeClaim<br/>grafana-state")]
pod_grafana_deployment --- pod_grafana_state
end
subgraph pod_lldap["Node lldap"]
pod_lldap_deployment["Deployment lldap<br/>one pod"]
pod_lldap_state[("PersistentVolumeClaim<br/>lldap-state")]
pod_lldap_deployment --- pod_lldap_state
end
subgraph pod_smtp["Node smtp"]
pod_smtp_deployment["Deployment smtp<br/>one pod"]
pod_smtp_state[("PersistentVolumeClaim<br/>smtp-state")]
pod_smtp_deployment --- pod_smtp_state
end
subgraph pod_valkey["Node valkey"]
pod_valkey_deployment["Deployment valkey<br/>one pod"]
pod_valkey_state[("PersistentVolumeClaim<br/>valkey-state")]
pod_valkey_deployment --- pod_valkey_state
end
subgraph pod_mastodon["Node mastodon"]
pod_mastodon_deployment["Deployment mastodon<br/>one pod"]
pod_mastodon_state[("PersistentVolumeClaim<br/>mastodon-state")]
pod_mastodon_deployment --- pod_mastodon_state
end
subgraph pod_peertube["Node peertube"]
pod_peertube_deployment["Deployment peertube<br/>one pod"]
pod_peertube_state[("PersistentVolumeClaim<br/>peertube-state")]
pod_peertube_deployment --- pod_peertube_state
end
subgraph pod_pixelfed["Node pixelfed"]
pod_pixelfed_deployment["Deployment pixelfed<br/>one pod"]
pod_pixelfed_state[("PersistentVolumeClaim<br/>pixelfed-state")]
pod_pixelfed_deployment --- pod_pixelfed_state
end
end
end
subgraph provider["Nodes of the hosting provider, not in the cluster"]
openbao["openbao"]
spire["spire"]
end
internet -->|"boundary 1"| balancer
balancer -->|"Service of a node"| namespace
registry -->|"image of a node"| namespace
namespace -->|"boundary 5"| openbao
namespace -->|"attestation of the SPIRE agents"| spire
The diagram shows one deployment of the operator group with the method
k8s-operator. core/effects/k8s/effect.nix makes these objects:
- Each node is one Deployment with one pod. The pod runs an image of the NixOS
configuration of the node (
core/setups/nixos/k8s-pod.nix). - Each node has the PersistentVolumeClaim
<node>-stateon/var/lib. The other paths of the pod start again from the image. - Each role of a node is one more Deployment,
<node>-<role>. A role with aserviceruns the image of that service as an unprivileged user. - A NetworkPolicy lets no connection in to the pods of the namespace. One more
NetworkPolicy for each node lets in what the
firewallresource of the node permits. - A node with ports for sources outside of the cluster has a Service of the
type
LoadBalancer. - The method
k8s-operatorappends the namespace of the deployment to the name of the Kubernetes namespace. It publishes the names of a node at the address of its Service. It can also connect the nodes to a sharedGatewaywith aTLSRouteand anHTTPRoute. - The OpenBao and SPIRE servers stay outside of the cluster. A pod has no TPM, thus the SPIRE agent of a pod attests with the token of its service account.
The deployment does not make the cluster, the image registry, the RuntimeClass, the storage class and the load balancer. They must be there before the deployment.
Trust boundaries in the topology
| Boundary | Where it is in the topology |
|---|---|
| 1. Internet to the public services | The public segment, or the load balancer of a cluster, to the published ports of a node. |
| 2. Internet to the administrative surface | SSH on port 22 of a machine, and the Incus API on port 8443 of the hypervisor. |
| 3. Operator to the hosting provider | The nodes panel and api of the hosting-provider group. |
| 4. Operator deployment to the hosting-provider mesh | The bridge between an operator deployment and the nodes of the hosting provider. |
| 5. Node to the secret store | Each node to the node openbao of the hosting-provider group. |
| 6. Forge to CI and CD | The machines of the dev group to the machines that they deploy. |
| 7. Contributors and administrators to the nodes | SSH to each machine and to each node. |
| 8. Upstream and caches to the Nix store | The substituters of the hypervisor, and the nix-serve service of the hypervisor for the instances. |
| 9. Garage node to the external backup target | The node garage of the hosting-provider group to an S3 service of a different provider. |
The five functions
This part tells how the system does each of the five functions of the scope.
Install applications
The operator selects applications in the form of panel. core makes the form
data: a JSON Schema of the options of each application, and the catalog of the
applications. api gives them to the browser (/schema, /applications).
When the operator deploys, the browser sends the configuration to api
(/deployments/submit/{id}). api asks panel if this account can change this
deployment, validates the configuration, puts a build in the queue, and then
runs the deployment program of core. The deployment program makes the
machines on the Incus hypervisor, the DNS records, the certificates and the
secrets, and it installs the NixOS configuration of each machine.
Details: Make a deployment in the user guide of the panel, the design of the API, and Add an application for the application side.
Configure applications
The options of an application are the options of its component in the data
model of core (Operator configuration). panel shows
them as a form. A change of the
configuration is a new submit: api validates the new configuration and
deploys it. core also gives a table of option renames, and api uses the
table to move a stored configuration to the current options
(/schema/migrations).
Details: the schema and migration sources of the API, and the data model.
Update applications
The version of an application comes from the revision of the code that a
deployment uses, and that revision pins nixpkgs. Thus an update is a deploy
with a newer revision. Each deployment has an update policy in api
(/deployments/update_policy/{id}): manual, automatic or window. With
manual, the operator starts the deploy. With the other two policies, api
starts the deploy when it finds a new default source, immediately or in a
maintenance window.
Details: the update policy of the API.
Manage users
There are two sets of users:
- The accounts of the operators are in
panel. An operator logs in with the single sign-on of the hosting provider or with a local account. The hosting provider manages the accounts in the administration pages ofpanel.panelkeeps which account owns which deployment, andapiaskspanelbefore it does an action. - The users of the applications are in the deployment of the operator. Each
deployment has its own lldap directory and its own Authelia single sign-on.
coreconnects each application to them through theldapandoidcresources. The operator adds users in lldap, and the groups in lldap give the roles in the applications.
Details: Manage the users of your deployment for the operator, Manage users and groups for the hosting provider, and the architecture page of the panel for the accounts.
Switch an operator to and from a different hosting provider
A move has a source and a target, and each is an installation of Fediversity.
- At the source,
apiexports the deployment (/migration-string/{id}). Thecore/effects/migrationeffect stops the services of each application and makes a snapshot of its data. The result is a migration string. It holds a pointer to the snapshot of each application, and the transfer code of the domain when the registrar account of the source holds the domain. - At the target,
apiimports the string (/migration-import/{id}) and deploys the configuration. Thecore/effects/migration-restoreeffect restores each application from the snapshot of the source. The target can transfer the domain to its own registrar account.
The configuration of an operator can name its own module source. Thus the operator can move away from a hosting provider that uses a different revision of the code.
Details: Export a deployment for a migration and Restore the apps of a migration in the hosting-provider guide, the export of a migration in the design of the API, and the switching and exit clauses of the contract templates.
Metering and accounting
This part tells what the system measures about a deployment of an operator, where each number comes from, and where a hosting provider reads it. It also tells what the system does not measure.
The system gives the quantities. It does not calculate a price and it does not make an invoice. The only amounts of money in the system are the costs that the registrar gives for a domain.
The sources
The system has three sources, and each one has its own store.
| Source | What it gives | Store | Where to read it |
|---|---|---|---|
The builds of api/ |
The periods in which a deployment ran one configuration | The build table of the state store of api/ |
GET /usage, field periods |
The domain events of api/ |
What the registrar account of the hosting provider charged for the domain of a deployment | The domain_event table of the same state store |
GET /usage, field domain_events |
| The telemetry of the hosting provider | The storage, the compute and the network traffic of a deployment, and figures of its applications | Mimir, on the telemetry node |
Grafana, or a query on Mimir |
flowchart LR
subgraph api["api/"]
builds["build table"]
events["domain_event table"]
usage["GET /usage"]
builds --> usage
events --> usage
end
deploy["Deploy job"] -- "result and domain.json" --> builds
deploy --> events
snapshot["Snapshot of the registrar account"] -- "renew, release" --> events
subgraph telemetry["Telemetry of the hosting provider"]
collector["otel-collector"]
mimir["Mimir"]
grafana["Grafana"]
collector --> mimir --> grafana
end
storage["PostgreSQL and garage nodes"] -- "size of each database and bucket" --> collector
hypervisor["Incus hypervisor"] -- "use of each instance" --> collector
apps["Application nodes"] -- "application metrics" --> collector
provider["Hosting provider"]
usage --> provider
grafana --> provider
The two sources of api/ and the telemetry have no link in the code. api/
does not read Mimir, and the telemetry does not read the state store of api/.
The deployment id is the common key: it is deployment_id in the result of
GET /usage and it is the label deployment_id of the metrics.
Usage periods
GET /usage (usage_list, api/src/fediversity_api/routes/usage.py) gives
the usage periods. usage_list of api/src/fediversity_api/procedures.py
calculates them at each call from the builds that succeeded
(list_succeeded_builds of api/src/fediversity_api/state.py). api/ does
not store a period.
- Each build that succeeded and is not a destroy starts a period when it
finishes. The next build of the deployment that succeeds ends the period. A
destroy also ends it. The last period of a deployment that is not destroyed
has
endset tonull. - A build that failed or was canceled changes no period.
- A period has the
deployment_id, thebuild_id, thestart, theend, theconfigurationand themodule_sourceof its build, and the flagsuspended. A suspended deployment stops its instances and keeps its volumes. - A period has the
domainthat its configuration serves the applications below, and theregistered_domainthat the deployment holds at the registrar account of the hosting provider in that period, if any. api/keeps the builds of a deployment after its destroy. Thus the periods of a destroyed deployment stay available.
The params since and until select the periods that overlap that time.
api/ does not cut a period to these times. The param id selects
deployments, and a caller can give it more than one time.
A period follows the builds and not the instances. It tells which configuration a deployment ran and when. It does not tell how much storage or compute the deployment used. The telemetry gives these quantities.
Domain events
The field domain_events of the same result tells what the registrar account
charged for the domains of the deployments. api/src/fediversity_api/domain_events.py
records the events in the domain_event table.
| Kind | What records it | Cost |
|---|---|---|
register, transfer |
The end of a deploy that registered the domain or transferred it into the account | The cost that the registrar gave for the order |
adopt |
The end of a deploy that took a domain that the account held already | None |
renew |
The unit fediversity-api-domain-renewals, when the expiry in the snapshot of the account moved forward |
The renewal price of one year, for each year |
release |
The same unit, when the domain left the account | None |
An event has its domain, its deployment_id, its cost in minor units of
its currency, and the expiration_date of the registration after the event.
The cost is null when the registrar charged nothing or when api/ does not
know the charge.
api/ records renew and release only when registrarSnapshot of the
NixOS module of api/ (api/nix/configuration.nix) points to the snapshot of
the registrar account. The unit runs when the snapshot changes.
The events stay after the destroy of their deployment. The page
api/docs/design/usage.md has the full rules.
Who can read the usage
GET /usage has the authorization of all routes of api/: api/ asks the
authorization callback of the front-end, and the callback gives the
deployments that the caller can see (api/src/fediversity_api/authz.py).
api/ then limits the result to these deployments.
When the panel is the front-end, the rule of usage_list is that the caller
owns the deployments (AUTHZ_PROCEDURE_RULES in panel/src/panel/views.py):
- The callback gives the deployments of one account, also for a superuser. Thus one call gives the usage of one operator.
- The callback includes the deployments that the operator deleted. The panel
keeps the row of a deleted deployment for this purpose, until the removal of
the account of the operator (
panel/src/panel/models.py). - A backend of the hosting provider can call
api/with the service token ofapi/and the headerX-On-Behalf-Ofwith the user name of an account. The callback then decides as for that account.
The panel has no page that shows the usage. No view of the panel calls
usage_list; the panel only answers the authorization callback for it. A
hosting provider reads GET /usage with its own tool.
Resource use in the telemetry
The telemetry gives the quantities that the usage periods do not have. Each
metric below has the label deployment_id, and the metrics are in Mimir.
| Quantity | Metrics | Source |
|---|---|---|
| Size of each PostgreSQL database | pg_database_size_bytes |
The PostgreSQL exporter on the node that runs PostgreSQL (core/components/hosting-provider/node-metrics.nix), each 30 seconds |
| Size of each garage bucket | garage_bucket_bytes, garage_bucket_objects, garage_bucket_unfinished_multipart_upload_bytes |
core/components/hosting-provider/garage-usage-exporter.py, which asks the admin API of garage at each scrape, each 60 seconds |
| Use of each instance | The incus_ metrics of the hypervisor, for example incus_cpu_seconds_total and incus_filesystem_size_bytes, which the dashboard core/components/operator/grafana/dashboards/resources.json shows |
The scrape incus of the otel-collector (core/setups/hosts-common.nix), each 30 seconds |
| Host metrics of each node | The system_ metrics: CPU, memory, load, disk, filesystem, network and paging |
The unit otel-node-metrics of the node (core/components/hosting-provider/node-metrics.nix), each 30 seconds |
| Figures of an application | The nodeinfo_ gauges, for example nodeinfo_users_total, and the Prometheus endpoints that an application gives |
The scrape job app-metrics of the otel-collector (core/components/hosting-provider/app-metrics-discovery.nix) |
The label deployment_id comes from a name that the deployment cannot change
after the fact:
- For a database, a bucket and an instance, a relabel rule takes it from the
name of the database, the bucket or the instance
(
core/components/hosting-provider/deployment-relabel.nix). A database, a bucket or an instance of the hosting provider has nodeployment_id. - For the host metrics of a node of an operator deployment, the
telemetrynode sets it from the path of the request, which opens only with the credential of that deployment (core/components/hosting-provider/deployment-ingest.nix). - For the application metrics, it comes from the KV path of the endpoint
entry (
core/components/hosting-provider/app-metrics-discovery.py). A deployment can write only below its own id. Adeployment_idlabel that an application gives becomesexported_deployment_id.
The hosting provider reads these metrics in Grafana or with a query on Mimir.
The dashboard "Deployment storage"
(core/components/hosting-provider/dashboards/deployment-storage.json) shows
the size of the databases and the buckets of each deployment. No dashboard
shows the usage periods or the domain events.
Mimir keeps the metrics for services.fediversity-telemetry.retentionDays
(core/components/hosting-provider/telemetry.nix). After that time, the
resource use of a past period is not available. The usage periods and the
domain events have no retention: api/ does not remove a build or a domain
event.
Know the use of the deployments for billing in the hosting provider guide gives the procedure and example queries.
What the system does not do
- It has no prices, no tariffs and no invoices. A domain event has the cost that the registrar gave, and no other amount is in the system.
- It does not add the resource use to the usage periods. The hosting provider joins the two on the deployment id.
- It does not aggregate the metrics for a billing period. The hosting provider does this with a query.
Logs, traces and audit records
This part lists each channel that records what the software and its users do: the audit events, the logs, the traces and the job records. For each channel it gives the personal data, the purpose of the data, the store, the persons who can read it, the retention, the control that keeps credentials out of it, and the test that shows the control.
The page describes what the code does. It does not say what the law requires or permits. Where a legal question applies, the page says "Open". The hosting provider decides it, see the hosting provider guide.
The stores themselves (location, owner, encryption, backup, erasure) are in the state register. The roles and the directory groups are in the access model.
The tables that have the label "generated" come from the code at each build of the documentation (core/docs/md/technical-design/observability-records.nix).
Words on this page
- "None" means that the code was examined and has no such control, test or limit.
- "Open" means that no source in the tree gives the fact, or that the question is a legal one.
- "A person with
rooton the node" is a person who can open a shell asrooton that node. The access model says who that is.
How a record goes from a node to the stores
flowchart TD units["Units of a node"] journal["Journal of the node"] nodeLogs["Node log shipper (otel-node-logs)"] auditShipper["Audit shipper (fediversity-audit-shipper)"] collector["otel-collector"] lokiDefault["Loki, default tenant"] lokiAudit["Loki, audit tenant"] tempo["Tempo"] grafana["Grafana"] apiTable["audit_event table of api/"] api["api/"] units --> journal api --> apiTable api --> journal journal --> nodeLogs journal --> auditShipper nodeLogs --> lokiDefault auditShipper --> lokiAudit collector --> lokiDefault collector --> tempo lokiDefault --> grafana lokiAudit --> grafana tempo --> grafana
The nginx of the telemetry node sets the tenant of each request (X-Scope-OrgID), thus a client cannot select a tenant (core/components/hosting-provider/telemetry.nix).
Retention
The hosting provider sets the retention periods. Each period below is the default of a setting, and the hosting provider can change it in its configuration. The reason for a period, and the law that applies to it, are not in the code: Open.
Generated:
| Records | Setting | Default | Source |
|---|---|---|---|
The audit_event table of api/ |
services.fediversity-api.auditRetentionDays |
365 days. 0 keeps all events. |
api/nix/configuration.nix |
The tenant audit of Loki |
services.fediversity-telemetry.auditRetentionDays |
365 days. 0 keeps all lines. |
core/components/hosting-provider/telemetry.nix |
The tenant fake of Loki, Tempo and Mimir |
services.fediversity-telemetry.retentionDays |
90 days | core/components/hosting-provider/telemetry.nix |
| The journal of each node | MaxRetentionSec and SystemMaxUse of services.journald.settings.Journal |
30day, and at most 512M |
core/setups/nixos/common.nix |
| The journal lines that a node of the hosting provider sends to Loki | services.fediversity-node-logs.priority |
The lines at level info and higher |
core/components/hosting-provider/node-logs.nix |
| The completed jobs of Windmill | services.fediversity-windmill.jobRetentionDays |
30 days | core/components/hosting-provider/windmill/windmill.nix |
The build logs and the operation_run table of api/ |
services.fediversity-api.logRetentionDays |
30 days. 0 keeps all logs and runs. |
api/nix/configuration.nix |
The operation_run table of api/ |
The constant OPERATION_RUNS_KEPT |
The last 100 runs of each deployment | api/src/fediversity_api/state.py |
| The nginx access log of the forge node | services.logrotate.settings.nginx |
182 daily files | core/components/dev/forgejo/forgejo.nix |
Limits that are not a setting of this software:
- The nginx access log of a node other than the forge node: the
logrotaterules of nixpkgs keep 26 weeks of files (the comment incore/setups/nixos/nginx.nix). - The rows of the
buildtable ofapi/: None. No job removes old rows.logRetentionDaysremoves only the log of a row. - The disk queue of the audit shipper: None. The module sets no limit of size or of age.
- The logs of the CI jobs and the change log of NetBox: Open.
Channels
Audit events of the api
api/ records each action on a deployment as one audit event (api/src/fediversity_api/audit.py).
- Store: the
audit_eventtable of the database ofapi/, and one JSON line on the loggerfediversity_api.audit. The line goes to the journal, and then to Loki, see Audit lines in Loki. - Readers: an operator reads its own events through the panel (
recent_actions,GET /audit-events). A superuser of the panel reads the events of each account (reads_all_history). A person with access to the database reads the table. - Retention:
services.fediversity-api.auditRetentionDays.api/removes the older events when it starts and each time that it records an event (prune_audit_eventsinapi/src/fediversity_api/state.py). The events of a deleted deployment stay until they are older than the retention. - Control that keeps credentials out: an event holds only the fields of the table below. For a request that is not valid,
reasongives the location of the error and not the value. For an import, the event holds only the result.api/cuts a longreasonof the authorization callback, and cutsrequest_idto 128 characters. - Test:
api/src/fediversity_api/tests/test_audit.py:test_an_invalid_request_records_where_without_the_value,test_an_invalid_configuration_records_where_without_the_value,test_an_import_records_only_its_result,test_an_unknown_action_stores_nothing_of_the_caller,test_a_long_reason_of_the_callback_is_cut. The retention has the teststest_a_new_event_removes_the_events_older_than_the_retention,test_the_retention_is_a_settingandtest_zero_keeps_all_events. - Protection from changes: each event has a hash of the event and of the event before it.
fediversity-api-audit-verifyexamines the chain (verify_audit_chain). The tests aretest_a_changed_event_is_found,test_a_removed_event_is_foundandtest_a_prune_keeps_the_chain_correct.
The fields of an event, with the purpose of each. Generated:
| Field | From | Personal data | Purpose | Serves to trace abuse |
|---|---|---|---|---|
actor |
_record |
Yes. The OpenID Connect sub of the account, or the primary key of an account without single sign-on. |
To tell who did an action on a deployment. | Yes |
actor_issuer |
_record |
Yes, together with actor. The OpenID Connect issuer of the account. |
To tell which identity provider knows the actor. | Yes |
internal |
_record |
No. | To tell an action of the system from an action of a person. | No |
action |
_record |
Yes, together with actor. |
To tell what occurred. | Yes |
deployment_id |
_record |
Indirectly. It links the event to an operator. For domain_contact_put and domain_contact_delete, it is the ID of a contact of the operator. |
To find the deployment that an action changed. | Yes |
build_id |
_record |
Indirectly. It links the event to a build of a deployment. | To find the build that an action started. | No |
request_id |
_record |
No. nginx makes a random value for each request. | To tell which audit events come from one request. | Yes. It connects the events of one request. |
result |
_record |
Yes, together with actor. |
To tell if api/ permitted the action. |
Yes |
reason |
_record |
Possibly. It is a text from the code or from the authorization callback. | To tell why api/ refused an action. |
Yes |
domain |
_record |
Possibly. A domain name can contain the name of a person. | To tell who claimed a domain. | Yes |
domain_terms |
_record |
No. It is a version of the terms. | To show which terms the actor accepted. | No |
id |
The store | No. | To identify the event. | No |
created_at |
The store | Yes, together with actor. |
To tell when the action occurred. The retention uses it. | Yes |
seq |
The store | No. | To detect a removed event. | No |
hash |
The store | No. | To detect a changed event. | No |
An audit event does not hold the client address. To connect an event to a client address, a person must find the journal line or the access log line of the same request, and the access log holds only the network of the client.
api/ records the first refused call without a login in each interval of 300 seconds, and then one event with the number of the other calls. Thus an anonymous caller cannot fill the table.
Audit lines in Loki
The audit shipper of a node (core/components/hosting-provider/audit-shipper.nix) reads the journal of the unit of a component, keeps the lines that match the pattern of the component, and sends them to the audit tenant of Loki. Generated:
| Source | Unit that writes the lines | Pattern of an audit line | Tenant of Loki | Other tenants |
|---|---|---|---|---|
api |
fediversity-api.service |
[A-Z]+:fediversity_api\.audit: |
audit |
None. The node log shipper drops these lines, thus they do not go to the tenant fake. |
- Personal data and purpose: the same fields as the audit event.
- Store: the audit tenant of Loki. Before the shipper sends a line, the line is in its disk queue in
/var/lib/fediversity-audit-shipper. The shipper does not stop its retries, thus a line stays in the queue while thetelemetrynode does not accept it. - Readers: each account that can log in to the Grafana of the hosting provider, thus the members of
fediversity-adminsandfediversity-staff. Grafana has one organization and the datasourceLoki audithas no permission of its own, thus aViewercan query the tenant. A person withrooton the node ofapi/reads the queue. - Retention:
services.fediversity-telemetry.auditRetentionDays. The tenant refuses the deletion API of Loki, thus only the retention removes a line. The setting ofapi/does not apply to these lines. - Control that keeps credentials out: the control of the audit event. The shipper adds none.
- Test: None for the content of a shipped line.
The deploy credentials can change or delete the tenant, see the hosting provider guide.
Journal of each node
- Personal data: what each unit writes.
sshd, Authelia and the nginx error log write full client IP addresses and user names.maddywrites email addresses. The applications write what their log level gives. - Purpose: to find the cause of a failure on a node (the comment in
core/setups/nixos/common.nix). A purpose for each field: Open. - Store: the journal of the node. A node of the hosting provider sends the lines at the level of
services.fediversity-node-logs.priorityand higher to the default tenant of Loki (core/components/hosting-provider/node-logs.nix). A node of an operator deployment sends the lines at levelwarningand higher, and thetelemetrynode sets the labeldeployment_id(core/components/hosting-provider/deployment-ingest.nix). - Readers: a person with
rooton the node reads the journal. Each account that can log in to the Grafana of the hosting provider reads the copy in Loki, for all deployments. - Retention: see Retention. The lines of a deleted deployment stay in Loki until the retention removes them.
- Control that keeps credentials out: each unit can write a credential, because a request line or an error message of an application can hold one. Thus the control is in the pipeline and not in each unit. The node log shipper replaces the text that has the shape of a credential with
[masked]before a line goes to Loki (core/components/hosting-provider/node-logs-masks.nix). The shapes are: the query string of a request line or of a URL, the user and the password of a URL, the value of anAuthorizationheader, a bearer token, the value of a key whose name containspassword,passwd,secret,token,api_keyorcredential, a JSON Web Token, and a token of OpenBao. A credential that has none of these shapes goes to Loki. The shipper does not read a message that is not UTF-8. The journal of the node keeps the full line: None for the journal. - Test: the check
node-logs-masks(core/components/hosting-provider/node-logs-masks-test.nix) sends an example of each shape through the masks. The checktest-monitoring-storage(core/components/hosting-provider/monitoring-test.py, "the node log shipper masks a credential") writes values to the journal of a node and reads Loki.core/setups/hypervisor-config-test.nixexamines the retention settings of the journal.
Audit device of OpenBao
OpenBao writes each request and each response as one JSON line to its standard output (core/components/hosting-provider/openbao/openbao.nix).
- Personal data: the client IP address, the path and the operation of the request, and the display name and the entity of the token, for example the name that an OIDC login gives to an administrator.
- Purpose: Open. No source in the tree gives the purpose of a field.
- Store: the journal of
openbao.service, and then the default tenant of Loki. These lines do not go to the audit tenant. - Readers: the readers of the journal and of the default tenant, see Journal of each node.
- Retention: that of the journal and of the default tenant. The lines do not have the retention of the audit tenant.
- Control that keeps credentials out: OpenBao writes secret values and tokens as HMAC-SHA256 hashes, and
elide_list_responsesremoves the keys of a list response. - Test: the check
integrations-openbao-pki(core/checks/integrations/openbao-pki/nixosTest.nix, "The audit device writes each request to the journal with hashed values").
nginx access log
Each node that runs nginx writes one line for each request to /var/log/nginx/access.log (core/setups/nixos/nginx.nix).
- Personal data: the network of the client (the address with its last part removed:
/24for IPv4,/48for IPv6), the method, the path and the protocol of the request, the referrer without its query string, the user agent, and the user name of HTTP basic authentication. On the vhosts withauth_basic(telemetry ingest, metrics query, audit path) this user name identifies the credential of one deployment, not a person. On other vhosts it is the text that the client sends. - Purpose: Open. No source in the tree gives the purpose of a field.
- Store: files on the node. The forge node does not record the calls of the CI runners (
core/components/dev/forgejo/forgejo.nix). - Readers: a person with
rooton the node. The node log shipper reads the journal and not this file, thus the lines do not go to Loki. - Retention: see Retention.
- Control that keeps credentials out: the log format holds no query string, of the request or of the referrer, and no password of HTTP basic authentication. A credential in a path or in the user agent goes to the file.
- Test: the check
nginx-access-log(core/setups/nixos/nginx-access-log-test.nix) sends requests with a query string, a referrer and a user name with a password, and examines the lines. It examines the mask of an IPv4 address. For the mask of an IPv6 address: None.
Request logs of the api and the panel
api/ and the panel run on uvicorn with its access log on (api/src/fediversity_api/http_main.py, panel/nix/configuration.nix). Each request gives one line with the address of the peer, the method, the path with its query string, and the status.
- Personal data: the query string can hold an identity, for example the actor in
GET /audit-eventsand the account in the history page of the panel. - Purpose: Open.
- Store: the journal of the unit, and then the default tenant of Loki, because the lines are at level
info. - Readers and retention: those of the journal, see Journal of each node.
- Control that keeps credentials out: in Loki, the masks of the node log shipper, which replace the query string of a request line, see Journal of each node. In the journal of the node: None.
- Test: the check
node-logs-maskshas a request line ofuvicornas an example.
Jobs of Windmill
Windmill keeps the arguments, the result and the log of each completed job.
- Personal data: a deploy job holds the configuration of the deployment, which can hold email addresses and user names, and the deploy log.
- Purpose: the job runs the deploy, and
api/reads the log of the job to show it to the operator. A purpose for a completed job: Open. - Store: the database of Windmill, see the state register.
- Readers: the administrators of Windmill. An operator reads the log of the builds of its own deployments through
api/(build_logsinapi/src/fediversity_api/procedures.py). - Retention:
services.fediversity-windmill.jobRetentionDays. - Control that keeps credentials out, deploy job:
api/removes the secret keys from thedeploy_envthat it sends to the job. Nothing removes a value from the text of the deploy log: None. - Control that keeps credentials out, node-effect job (
f/fediversity/node_effect): the arguments and the result of this job can hold bearer credentials, and Windmill keeps them in clear.api/deletes the job when it is completed, with a success or a failure, before the result goes back to the caller. Whenapi/cannot delete a job, it writes the ID of the job to its log, and a person must delete the job (_run_effect_jobinapi/src/fediversity_api/node_effect.py). Until then, the credentials stay in the job. - Test:
api/src/fediversity_api/tests/test_deploy_env.py(SecretKeysStayOffTheWindmillPayload) for the deploy job.api/src/fediversity_api/tests/test_migration.pyfor the node-effect job:test_windmill_route_runs_the_job_and_deletes_it,test_a_failed_job_reports_its_error_and_is_deleted,test_a_timed_out_job_fails_and_is_deleted,test_a_job_that_cannot_be_deleted_is_logged_by_id.
Build logs and operation output in the api
- Personal data: the
actorof each run of an operation (operation_runtable). The text of a build log and of an operation output can hold what the deploy tools and the operation write. - Purpose:
api/shows the result of a build or of an operation to the operator. - Store: the
buildandoperation_runtables of the database ofapi/. With Windmill,api/reads the build log from the job. Without Windmill,api/keeps the last 64 KiB of the output of a failed build in thebuildtable. - Readers: the operator of the deployment, through
api/. A person with access to the database. - Retention:
api/keeps the last runs of each deployment, see Retention, and deletes the runs after the deployment is destroyed. Withservices.fediversity-api.logRetentionDays,api/also removes the log of a build and each run of an operation that finished before that number of days (prune_logsinapi/src/fediversity_api/state.py). It does this when it starts, and each time that it stores a build log or a run. The default is 30 days.0keeps all logs and runs. The rows of thebuildtable have no limit, because the usage records come from them: None. - Control that keeps credentials out:
api/does not keep the output of an operation that hassensitiveset. For the text of a build log, see the deploy job in Jobs of Windmill: the same runner writes the two logs. - Test:
api/src/fediversity_api/tests/test_operations.py:test_the_output_of_a_sensitive_operation_is_not_kept,test_the_deployed_revision_decides_if_the_output_stays.api/src/fediversity_api/tests/test_log_retention.pyfor the retention. For the build log:api/src/fediversity_api/tests/test_log_credentials.py(FailedBuildLogHoldsNoSecret).
Traces
- Sources: Grafana sends its own spans when
tracingEndpointis set. NetBox sends the spans of its requests, its HTTP calls and its database queries whenfediversity.otel.endpoints.netboxis set (core/components/hosting-provider/netbox/otel.nix). Windmill has the settings but its open-source build sends no spans (core/components/hosting-provider/windmill-otel.nix). - Personal data: a span can hold a request path. The attributes that the instrumentation libraries set: Open.
- Purpose: Open.
- Store: Tempo.
- Readers: each account that can log in to the Grafana of the hosting provider.
- Retention:
services.fediversity-telemetry.retentionDays. - Control that keeps credentials out: None. The collector has only the
batchprocessor (core/components/hosting-provider/otel-collector.nix). - Test: None.
Debug exporter of the collector
When services.fediversity-otel-collector.backends is not set, the collector has only the debug exporter with verbosity = "detailed". It then writes each record that it receives (logs, metrics and spans) to its own journal. The readers, the retention and the missing controls are those of the journal.
Records of Grafana and NetBox
- Grafana keeps its accounts and its sessions in its own database, see the state register.
- NetBox keeps a change log with the user of each change. No file in the tree sets its retention or its readers: Open.
Logs of the CI jobs
The forge keeps the log of each CI job. The workflows in .forgejo/workflows/ give secrets to the CD jobs as environment variables.
- Personal data: Open.
- Store, readers and retention: Open. No file in the tree sets them.
- Control that keeps credentials out: Open. No file in the tree sets a mask.
- Test: None.
Fields that serve to trace abuse
To trace abuse, a person must know who did a thing, from where and when. These fields give that:
- The audit events of
api/: the fields with "Yes" in the last column of the table of the fields. They give the account, the action, the deployment and the time. They do not give a client address. - The journal: the full client IP address and the user name in the lines of
sshdand Authelia, with the time. - The audit device of OpenBao: the client IP address and the display name of the token, with the path and the time.
- The nginx access log: only the network of the client. It cannot identify one address.
If these fields are sufficient for a duty of the hosting provider to trace abuse, and how long the hosting provider must or can keep them: Open.
Open items
| Channel | Item | State |
|---|---|---|
| All | The legal basis and the reason of each retention period | Open |
| Audit lines in Loki | A test of the content of a shipped line | None |
| Audit lines in Loki | A limit on the disk queue of the shipper | None |
| Audit lines in Loki | A limit on which Grafana account reads the tenant | None |
| Journal | A control for a credential that has no known shape, and for the journal of the node | None |
| Journal | A purpose for each field | Open |
| Audit device of OpenBao | The retention of the audit tenant | None |
| Audit device of OpenBao | A purpose for each field | Open |
| Jobs of Windmill | A purpose for a completed job | Open |
| nginx access log | A purpose for each field | Open |
| nginx access log | A test of the mask of an IPv6 client address | None |
Request logs of api/, panel |
A purpose | Open |
Request logs of api/, panel |
A control that keeps credentials out of the journal of the node | None |
| Jobs of Windmill | A control on an encoded or a derived credential in the text of a deploy log or a build log | None |
| Jobs of Windmill | An alert when the sweep cannot delete a node-effect job | None |
Build logs in api/ |
A retention of the rows of the build table |
None |
| Traces | The attributes of a span, and the purpose | Open |
| Traces | A control that keeps credentials out, and a test | None |
debug exporter |
A control that keeps credentials out | None |
| Change log of NetBox | The retention and the readers | Open |
| Logs of the CI jobs | The personal data, store, readers, retention and mask | Open |
Requirements of hosting providers and design review
This page lists the requirements that the sources of the project record for hosting providers. For each requirement, it gives the source and the place in the technical design that answers it. It also gives the record of the review of the design.
The specifications of core, of
api and of
panel hold the
requirements that the project wrote for the code. They are not statements of
hosting providers, thus this page does not repeat them. The part traceability
connects them to the design.
Sources
The repository of the code holds no statement that a hosting provider made. The sources below are outside of this repository.
| Source | What it is | Who made the statements | Public |
|---|---|---|---|
| Front-end interview | The summary of an interview of 2026-10-12 that collected requirements for the front end of the panel. The file is meeting-notes/2026-10-12-meeting-summary-front-end-req.md in the meta repository of the project. |
One person of the hosting provider that is a partner of the project | Yes. This page gives a summary only. |
| Architecture notes | The notes architecture-docs/architecture.md and architecture-docs/data-model-requirements.md in the meta repository of the project. These notes are older than the present code. |
The project | Yes. This page gives a summary only. |
| White paper | The white paper of the project, "Server apps commodified: digital autonomy for the masses", in the white-paper repository of the project. |
The project | Yes. This page gives the goals in summary. |
The front-end interview is the only source that records the words of a hosting provider. It has these limits:
- It has the statements of one person of one hosting provider.
- Its subject is the front end of the panel. It does not cover the infrastructure, the metering or the move between hosting providers.
- The person is also a member of the project. The record does not tell which statements the person made as a hosting provider.
- Some statements are ideas for a later version. The tables below mark them.
No source records requirements from other hosting providers.
Requirements from the front-end interview
The column "Answer" tells if the design answers the requirement: "Yes", "In part" or "No". "No" means that the code and the pages of the design show no function for the requirement.
Users and access
| ID | Requirement | Answer | Place in the design |
|---|---|---|---|
| I-1 | The panel is primarily for operators, who are the customers of a hosting provider. | Yes | Scope and the actors. |
| I-2 | The hosting provider is responsible for the authentication. A login with a user name and a password is sufficient. | Yes | Manage users: an operator logs in with the single sign-on of the hosting provider or with a local account. The part access-model has the details. |
| I-3 | The operator makes user accounts and gives the users access to the applications. | Yes | Manage users: each deployment has its own lldap directory and its own Authelia single sign-on. |
| I-4 | An operator makes the first user accounts in less than 30 minutes. | No | The design gives the steps (Manage the users of your deployment). No test measures the time. |
| I-5 | A role with which one operator manages other operators. | Yes | The operators' operator account can manage users' access. |
| I-6 | The hosting provider can see audit logs and the actions of its customers. The interview put this out of its scope. | In part | api records audit events, and the hosting provider selects the store. See Choose the store and the retention of the audit logs and Know which personal data the logs keep. The panel shows to an operator the history of a deployment and of the account (Deploy). |
Use of the panel
| ID | Requirement | Answer | Place in the design |
|---|---|---|---|
| I-7 | The first version operates in a browser on a workstation, preferably Firefox. | Yes | panel is a web application (Containers). The tests of the panel use Firefox (panel/nix/tests.nix). |
| I-8 | The panel obeys the accessibility standards of the W3C. | In part | An automated test with axe-core examines the rules of WCAG 2.1 level A and AA (Accessibility). No person did a manual audit (Publish the accessibility statement). |
| I-9 | The first release has English, Dutch, French and German. | Yes | LANGUAGES in panel/src/panel/settings.py has these four languages. See Translations. |
| I-10 | The panel can show all languages, with text from left to right and from right to left. | No | The panel has no language with text from right to left. |
| I-11 | Later: access from a mobile application. | No | The design has no mobile application. |
| I-12 | The operator changes all settings in the web interface, and then one action deploys the full environment. | Yes | Install applications: the browser sends the full configuration of the deployment to api in one submit, and api puts one build in the queue. |
| I-13 | Idea: before the deploy, the panel shows a list of the changes, and possibly the cost of the deploy. | No | The panel shows no cost of a deploy. The pages of the design describe no list of the changes before a deploy. |
| I-14 | An onboarding sequence: upload a company logo, select the company colors, select the applications. | In part | The operator selects the applications in the form (Install applications). The panel has no onboarding sequence and no setting for a logo or for colors. |
| I-15 | Slow tasks, such as the registration of a domain, start first and continue while the operator makes the other choices. | No | The deploy starts when the operator clicks "Deploy" (Deploy). |
Applications and releases
| ID | Requirement | Answer | Place in the design |
|---|---|---|---|
| I-16 | The operator sees all available applications and can turn each application on or off. | Yes | Install applications: core makes the catalog of the applications, and the form of panel shows it. |
| I-17 | The operator can set limits of use and colors for each application. | In part | The options of an application are in the form (Configure applications, Operator configuration). The design has no general setting for limits of use or for colors. |
| I-18 | Only the project adds an application to the catalog. A hosting provider can turn an application on or off. | In part | The catalog comes from the components of the data model of core (Add an application). The design has no setting with which a hosting provider removes an application from the catalog. |
| I-19 | The panel manages DNS, mail and three to four applications. | Yes | The components of a deployment of an operator list the applications. For DNS, see Assign a domain to a deployment. For mail, see the smtp resource in the system context. |
| I-20 | The project makes one main release each month, on the first day of the month. Fixes can come between releases. | In part | The project makes a release each month, on day 8, and can make one more release in the same month for a fix (SECURITY.md, Make a release). |
| I-21 | The operator sees which changes a release has. | In part | The notes of a release give the titles of its pull requests (Make a release). The panel does not show them. |
Requirements that the project wrote for hosting providers and operators
These statements are from the project. No source records that a hosting provider gave them.
| ID | Requirement | Source | Place in the design |
|---|---|---|---|
| P-1 | An operator selects applications, and the system deploys them to the domain of the operator. | Architecture notes, white paper | Install applications. |
| P-2 | An operator can change the configuration of an application in a web interface. The defaults are sufficient for most operators. | Architecture notes, white paper | Configure applications. |
| P-3 | A hosting provider can give the operator a selection of update policies. | Architecture notes | Update applications: each deployment has the update policy manual, automatic or window. |
| P-4 | When the options of an application change in an update, the configuration of the operator moves to the new options. | White paper | Configure applications: the table of option renames. |
| P-5 | An operator can make accounts for other persons and manage their rights. | Architecture notes | Manage users. |
| P-6 | An operator can move a deployment, with its configuration and its data, to a different hosting provider. | Architecture notes, white paper | Switch an operator to and from a different hosting provider. |
| P-7 | A move is possible when the two hosting providers do not use the same version of the code. | Architecture notes | Switch an operator to and from a different hosting provider: the module source of an operator. |
| P-8 | From a hypervisor, the system makes the resources that an application needs: machines, DNS records, storage, databases, mail and secrets. | White paper | Install applications and the containers. |
Design review
No source records a review of this technical design. The repository of the
code, the meta repository and the white-paper repository hold no list of
reviewers, no date of a review and no result.
A code review of a pull request on the forge is not a review of the design by hosting providers, thus this page does not count it.
Quality attributes
This page gives, for each quality attribute of the full system, the target, what the code does, how a test shows it, and what the code does not do. A target is a value that a document or the code of this repository states. Where the repository states no target, this page says so. This page does not set a target.
The checks that this page names are attributes of the flake of core. To run
one, use nix build ./core#checks.x86_64-linux.<name>. The page
Flake attributes describes each check.
Summary
| Attribute | Target in the repository | Test of the target |
|---|---|---|
| Availability | None | None |
| Recovery point | 24 hours plus the duration of the backup run, as a result of the schedule | The alert rule backup-stale and the restore checks |
| Recovery time | None | None; the restore drill measures one part |
| Downtime of an upgrade | None | None |
| Downtime of a move between providers | "In the order of a day", with no data loss (Concepts) | None for the time; checks for the data |
| Number of deployments for each provider | "At least 1,000 operators" (Concepts) | None |
| Resources of the smallest installation | None | None |
The specifications in core/specs/, api/specs/ and panel/specs/ have
success criteria for each feature. No success criterion gives a time, a
percentage or a quantity for an attribute of this page.
Availability
Target. The repository states no availability target: no uptime percentage and no maximum time that a service can be down. The contract templates have a row "Availability and backup" that the hosting provider fills in (Contract templates).
What the code does.
- Each stateful service runs on one node. The section
Know the limits of a single node
of the hosting provider guide lists the services. For example, garage has
replication_factor = 1(core/resources/garage/server.nix), and the stores of the telemetry node havereplication_factor = 1(core/components/hosting-provider/telemetry.nix). - Grafana alert rules tell the hosting provider about a failure
(
core/components/hosting-provider/telemetry/alert-rules.nix).unit-failedandunit-stuckfind a unit that failed or that does not finish its start.node-absentfinds a node that sent host metrics before and sends none now.ingest-silentfinds that no node sends metrics.disk-filling,disk-filling-trendanddisk-fullfind a disk that gets full.cert-expiring,cert-expiring-criticalandrenewal-failedfind a certificate that is not renewed. The section Monitor the deployments of the guide tells how to use them. - A node counts its boots (
core/setups/nixos/boot.nix). The boot loader marks a new generation as good only after the checkboot-check-gatewaygets an answer from the default gateway. A generation that fails the check reboots, and after the configured number of tries the boot loader starts the last good generation. - With the deploy method
k8s, a pod role has areplicasoption (core/resources/pod-roles/default.nix). When the pod of a new deploy does not become ready, the Deployment continues to run the previous image (core/specs/009-deployment-methods/k8s.md, SC-003).
How it is tested. core/components/hosting-provider/telemetry/alert-rules-test.nix
and core/components/hosting-provider/monitoring-test.py test the alert rules.
The check integrations-boot-counting tests the boot counting. The check
deployment-k8s-hosts runs the role lldap with two replicas. No test
measures availability.
What it does not do. There is no replica, no standby and no automatic failover for a stateful service. When a node fails, its services are down until the hosting provider repairs the node or restores it from a backup. The alert rules report a failure; they do not repair it.
Recovery point and recovery time
Target. The hosting provider guide gives the recovery point objective (RPO) of each backup as 24 hours plus the duration of the backup run. This value is a result of the default schedule. Each failed run adds 24 hours. The section Know how much data a restore can lose has the table of the backups, their schedule options and their restore points. The repository states no recovery time objective (RTO).
What the code does.
- Each off-site backup has a timer with
OnCalendar = "daily"andPersistent = trueas a default (mkOffsiteBackupincore/components/operator/app-resources.nix). The hosting provider can change the schedule with the options that the table of the guide names. backupRetentionincore/components/operator/app-resources.nixgives how long the backups keep their data: 30 days for the backups of an application, and 7 daily, 4 weekly and 6 monthly snapshots for the backups that restore the infrastructure.- The media backup copies the buckets of an application
(
core/resources/garage/media-backup.nix). The external copy copies the backup data to a second location (core/resources/garage/external-copy.nix). garage makes snapshots of its metadata (core/resources/garage/metadata-snapshots.nix). - The alert rule
backup-failedfires when a backup run fails. The rulebackup-stalefires when the last good run of a backup is more than 36 hours old. - The restore drill (
core/components/shared/restore-drill.nix, optionfediversity.restoreDrill.enable) restores the newest database backup into a scratch database each day. It records the duration in the metricfediversity_restore_drill_duration_secondsand the time of the last good run infediversity_restore_drill_last_success_timestamp_seconds. The alert rulesrestore-drill-failedandrestore-drill-staleuse these.
How it is tested.
| Check | What it restores |
|---|---|
integrations-app-database-restore |
The database of an application |
integrations-media-backup |
The media of an application |
integrations-backup-external-copy |
The backups from the external copy |
integrations-bootstrap-backup |
OpenBao and the states of the terraform backend, on the same node and on a new node |
integrations-garage-snapshots |
The metadata of garage |
The nix-unit tests in core/setups/backup-retention-test.nix test the
retention. No test measures the age of the newest backup at the time of a
failure; the rule backup-stale monitors it on a live installation.
What it does not do. No backup is continuous, thus a restore loses the
changes after the newest good run. The duration that the restore drill
records is the restore time of one database only. It does not include the
time to make a new node, to restore files and media, or to find the failure.
No test measures the time of a full restore. After a restore, the hosting
provider must do the erasures of personal data again; the part
data-subject-requests covers this subject.
Downtime of an upgrade
Target. The repository states no target for the downtime of an upgrade.
What the code does.
- A deploy builds the new configuration of a node and activates it with
switch-to-configuration switch(core/effects/common/deploy.sh). systemd restarts the units that changed. Thus a changed service is down for the time of its restart. - A service with state uses
systemd.stateMigrations. The unit<name>-migrate-pre.servicemakes a backup and runs the steps for the version range before the service starts. A failed step restores the backup and keeps the service stopped. A downgrade and a skipped release series are refused with exit status 78. Examples arecore/components/hosting-provider/openbao/migrations.nixandcore/components/hosting-provider/netbox/migrations.nix. - Before an upgrade of an application,
mkUpgradeSnapshotincore/components/operator/app-resources.nixmakes a backup while the application is stopped. When the backup cannot start, it tries again each 30 seconds. AfterUPGRADE_BACKUP_TIMEOUTseconds (default 600) it refuses the upgrade with exit status 78. The application then stays stopped with its data at the old version. - The API applies updates as the update policy of the deployment tells it:
manual,automaticorwindow(api/src/fediversity_api/update_policy.py,api/src/fediversity_api/automatic_updates.py). Withwindow, an operator selects the period of the day in which an update can start. A window is at least 15 minutes and at most 24 hours.
How it is tested. The check integrations-state-migrations tests the
upgrade steps, the refusals and the restore after a failed step. The checks
integrations-postgresql-major and integrations-postgres-upgrade-dump test
an upgrade of PostgreSQL to a new major version. A check
integrations-<service>-series for each service with state, for example
integrations-openbao-series and integrations-garage-series, tests an
upgrade between release series. api/src/fediversity_api/tests/test_update_policy.py
tests the update policy. These tests show that an upgrade keeps the data. No
test measures the downtime.
What it does not do. There is no rolling upgrade and no second instance that takes the requests during an upgrade of a node. The downtime of a service is the time of its restart plus the time of its upgrade backup and its migration steps. That time depends on the quantity of data. A refused or failed upgrade keeps the service stopped until a person corrects the cause.
Downtime of a move between providers
Target. Concepts states: "Migrations involve service unavailability in the order of a day (due to DNS propagation) but no data loss." The repository states no more exact value.
What the code does.
- On the source,
migration-quiesce stopstops the services of the applications, and the export makes the data available to the target (core/components/shared/migration-quiesce.nix,core/effects/migration/run.sh). The source deployment is suspended. - On the target, the import restores the data and starts the applications
(
core/effects/migration-restore/). - The applications are down from the export until the target has restored them and the DNS records point to the target. The sections Export a deployment for a migration and Restore the apps of a migration of the guide give the procedure. To go back, the hosting provider resumes the source deployment.
- The DNS time depends on the TTL of the records. The record of a node and the
NS records of the apex have a TTL of 300 seconds (
nodeTtlandapexNsTtlincore/setups/groups/hosting-provider.nix). The default TTL of a zone from NetBox is 86400 seconds (core/effects/octodns/netbox-zone.py). deSEC does not accept a TTL below 3600 seconds (core/effects/octodns/octodns_fediversity_desec.py).
How it is tested. The checks integrations-migration-restore,
integrations-migration-restore-boot-wait and deployment-migration-quiesce
test the export, the restore and the stop of the services. The check
deployment-registrar-transfer tests the transfer of a domain.
api/src/fediversity_api/tests/test_migration.py, test_migration_restore.py
and test_migrate.py in the same directory test the API side. These tests
show that the data arrives. No test measures the downtime, and no test shows
the value "in the order of a day".
What it does not do. A move is not live: the applications do not run on the source and on the target at the same time. The downtime includes the copy of the data, which depends on the quantity of data and on the network between the two hosting providers. The code does not measure it.
Number of deployments for each provider
Target. Concepts states: "We target small- to medium-scale hosting providers with 20+ physical machines, able to scale to at least 1,000 operators." The repository states no other quantity.
What the code does.
- Each operator deployment gets a
/28prefix from the NetBox prefix for operator deployments (core/effects/tf/incus/operator/bridge-isolation.tf). Thus the size of that parent prefix, which the hosting provider selects, limits the number of deployments. - The operator deployments share the PostgreSQL node, the garage node and the other nodes of the hosting provider (Contract templates gives the isolation). The capacity of these single nodes limits the number of deployments.
- The API builds at most
schema_build_concurrencyschemas at the same time, with a default of 2 (api/src/fediversity_api/schema_build.py). It runs at most one deploy for each deployment at a time, because the deploy job uses the deployment ID as its concurrency key (api/src/fediversity_api/deployment.py). - The alert rules
disk-filling,disk-filling-trendanddisk-fulltell the hosting provider when a disk gets full.
How it is tested. No test. No check makes a large number of deployments, and the repository has no load test.
What it does not do. The code does not spread the load of one stateful service across nodes, and it has no limit that refuses a new deployment when the capacity is used. The value of 1,000 operators is a design goal that no measurement supports.
Resources of the smallest installation
Target. The repository states no minimum for memory, processors or disk, and no test finds one.
What the code does. The groups in core/setups/groups/ define the nodes
of an installation. The table shows the nodes of each group. A published node
has a public name; an internal node does not.
| Group | Published nodes | Internal nodes |
|---|---|---|
hosting-provider |
authelia, bind, garage, lldap, netbox, openbao, otel-collector, overview, postgresql, smtp, spire, telemetry, terraform-backend, valkey, windmill |
-- |
hypervisor |
incus |
-- |
operator |
mastodon, peertube, pixelfed |
authelia, grafana, lldap, smtp, valkey |
- The nodes of the group
hosting-providerexist once for each hosting provider. The front end adds the nodes of the API and the panel; they are not in this table. - The nodes of the group
operatorexist for each operator deployment. A deployment has the nodes of the applications that the operator enables and the internal nodes that support them. - The root disk of an Incus instance has a default size of
50GiB(instanceRoot.sizeincore/components/hosting-provider/incus.nix), which Incus applies only when the storage pool can hold a quota. - The code of
coresets no memory limit and no processor limit on an instance.
The part deployment-topology describes the nodes and where they run.
How it is tested. No test. The NixOS tests run each node in a virtual machine or a container with the resources of the test, which are not a statement about a production installation.
What it does not do. The code does not check that a host has sufficient resources before a deploy.
Related attributes
- Security. The threat model gives the assets, the
trust boundaries, the
threats and controls and the
accepted risks and open gaps.
This page does not repeat them. The part
access-modelcovers who can do what. - Observability. The nodes push metrics, logs and traces to the telemetry
node of the hosting provider
(
core/components/hosting-provider/telemetry.nix). The section Monitor the deployments of the guide covers the dashboards and the alert rules. - Data protection. The page Data protection covers the retention and the erasure of personal data.
State lifecycle
This page tells what happens to the state of a running system when the code of the project changes, and when a service gets a new version. State is the data that a deploy does not make again: databases, buckets, secrets, the OpenTofu state, and the records of api/ and panel/.
The rule for each type of change
| Type of change | Mechanism | What the mechanism does not do |
|---|---|---|
| An option of the operator configuration is renamed | The rename table of api/, with the log core/renames-log.json |
It changes only the stored configuration. It moves no deployed object. |
| A value of the configuration changes | The deploy writes the value into a configuration file or the environment of a unit, and the unit starts again | It does not change a value that the application copied into its own state, for example a default of a new account |
| An OpenTofu resource gets a new address | A moved block |
It does not change the object. It cannot serve an address with a key that is different for each deployment. |
| An object exists, but the state does not have it | An import block |
It does nothing for an address that the state has. |
| A resource is dropped from the configuration | The apply destroys the object. Some resources have a guard or a retire step. | It does not delete an object that is not in a state. |
| An application is disabled | The data stays. deleteData deletes it. |
Without deleteData, nothing is deleted. With it, the backup bucket stays for the grace period. |
| A deployment is destroyed | tofu destroy of the operator graph, with destroy-time steps |
It does not delete the backups at once, the domain registration, or the records of api/ and panel/. |
| A service gets a new version | systemd.stateMigrations, from the migrations.nix of the service |
It moves no object. It runs a schema command for some services only. It restores automatically for some services only. |
The schema of api/ or panel/ changes |
Alembic revisions that api/ applies, Django migrations that panel/ applies, and a version record |
No migration goes back. The dump from before the upgrade is the only way back. |
The sections below give the details and the files.
Two types of OpenTofu state
The code uses OpenTofu in two layers:
- The deployment graphs are in
core/effects/tf/. Their state is in theterraform-backendservice of the hosting provider. - The on-node configurations are generated from Nix and applied by a unit on the node, for example
core/resources/postgresql/provision.nix,core/resources/ldap/provision.nix,core/resources/garage/provision.nixandcore/components/hosting-provider/openbao/runtime/module.nix. Their state is aterraform.tfstatefile on the node.
In the two layers, the state is what connects the code to the objects. Thus a change that must move or adopt an object is a block in the OpenTofu configuration, or code written for that change.
An option is renamed
The panel and api/ store the configuration of a deployment as JSON. When an option of the operator configuration gets a new path, a stored configuration still has the old path.
core/lib/renames-for-source.nix makes a table of { from; to; } entries from the option tree: each rename that is declared with lib.mkRenamedOptionModule or lib.mkAliasOptionModule, and each rename of a key in a pinned upstream JSON Schema, which a settings-renames.json file beside the component declares. core/renames-log.json keeps the entries of earlier revisions, each with the since version that added it. api/package.nix puts the table in the package as renames.json, and its build fails for a rename that the log does not have.
core/renames-log.json has 0 entries.
Before api/ validates a stored configuration, _migrate_for_validation in api/src/fediversity_api/procedures.py moves each value from its old path to its new path (api/src/fediversity_api/migrate.py). A deployment row records the version of the log that its configuration agrees with (renames_version in api/src/fediversity_api/state.py), thus only the newer entries apply. A rename that cannot apply is reported as unmigrated, and the value stays where it is.
This mechanism changes only JSON. If the renamed option is part of the name of a deployed object, the next apply sees a different object: see the next sections.
A stored deployment also records the source of its modules. api/ stores a pin of its own revision for a deployment without a source, thus a rebuild of api/ does not change the schema of a stored deployment until its next deploy. See "Default-source baseline freeze" in api/docs/design/schema-and-sources.md.
A value changes
For most values of the operator configuration, and for the environment variables of api/ and panel/, the deploy writes the value into a configuration file or into the environment of a unit. When the file or the environment changes, the unit starts again. Thus the next deploy applies the new value.
Some applications keep a value in their own state, and that value wins over the deployed value. The code prevents this for these values:
- Pixelfed writes a setting into its
config_cachetable at the first read, and from then on the row wins.core/components/operator/pixelfed/node.nixsetsENABLE_CONFIG_CACHE = false, thus the value of the.envfile applies. - PeerTube reads
local-production.jsonin its configuration directory over the deployed configuration. Its web edit of the configuration writes that file.core/components/operator/peertube/node.nixturns off the web edit, and thepreStartofpeertube.serviceremoves the file. - The secrets of the operator are in OpenBao KV (
api/src/fediversity_api/operator_secrets.py). When the rename table renames the field of a secret, a submit copies the value from the old name to the new name. When the operator clears a secret, the next deploy deletes its KV entry with all its versions (core/effects/tf/operator-provision/kv-clear.tf).
These values apply only to new objects, or stay with the application, by design:
| Value | What keeps the old value |
|---|---|
PeerTube user.video_quota*, defaults.*, instance.default_nsfw_policy, user.default_channel_name |
PeerTube copies the value into a user, channel or video when it makes it. The new value applies to new objects only. |
PeerTube admin.email |
PeerTube makes the root account one time, with this address. The contact address of the instance follows. |
Mastodon AUTHORIZED_FETCH |
A value wins and locks the toggle of the admin. With no value, Setting.authorized_fetch in the database decides. |
Mastodon HCAPTCHA_* |
The keys follow. The captcha is on only when the keys are set and an admin turns it on. |
Authelia default_2fa_method, totp.*, regulation.ban_time |
Authelia keeps the method, the enrollment and the ban of each user. |
api/ OPENBAO_KV_MOUNT, OPENBAO_KV_DEPLOYMENTS |
The entries stay at the old path, and api/ reads the secrets as not set. A change needs a move of the entries by hand. |
api/ API_DATABASE_URL |
For an empty database, import_sqlite_state in api/src/fediversity_api/state.py copies the SQLite file in, not the data of the database before. A move between two PostgreSQL databases is by hand. |
panel/ OIDC_ISSUER |
The actors of the audit records and the UserSocialAuth rows keep the old issuer. |
The docs build generates this table and the list above from core/docs/md/technical-design/state-lifecycle.nix. The build fails when an option of a row is not in core/components/operator/operator-facing-options.csv, or when a file that a row names no longer has the code that the row describes.
A resource gets a new address
When the address of a resource changes and nothing else is done, OpenTofu destroys the object of the old address and creates an object at the new address. For an object that holds data, the new object is empty.
A moved block prevents this: it tells OpenTofu that the object of the old address is the object of the new address. The tree has these:
core/effects/tf/operator-provision/garage.tfhas the onlymovedblock in a.tffile, for aterraform_dataresource with a new name.core/lib/tf-labels.nix(movedBlocks) generates themovedblocks of the on-node configurations, from the labels<prefix>_<key>that the states of deployed nodes have, to the labels of the current code.core/resources/garage/provision.nixchanges its state file withjqbefore the apply, because amovedblock between two providers needs a provider that reads the old type.core/components/hosting-provider/incus.nixrepairs its state file in thepreStartof the unit, before themovedblocks apply.
The limits:
- A
movedblock and aremovedblock accept only static addresses. The comment beforegarage_bucket.bucketincore/effects/tf/operator-provision/garage.tfgives the result: a resource whosefor_eachkeys change with each deployment cannot get such a block. - No
.tffile in the tree has aremovedblock. - A
movedblock does not rename the object itself. A database, a bucket or a node with a new name is a new object.
An object exists, but the state does not have it
An import block adopts the object, so that the apply does not create it again:
- The on-node configurations of PostgreSQL, lldap and garage adopt an object that exists and is not in the state, for example a database that
ensureDatabasesmade. core/effects/tf/incus/operator/registrar.tfadopts a domain claim in OpenBao, a domain that is in the registrar account already, and a name server group.
notes/forward-recovery.md gives the rules for an apply that failed: there is no rollback, and the same apply runs again.
A resource is dropped
When a resource leaves the configuration, the next apply destroys its object. This is the default of OpenTofu in the two layers:
- In an on-node configuration, an object that leaves the configuration is dropped. For PostgreSQL, a grant is revoked, and a database is dropped with its data (
core/resources/postgresql/provision.nix). - In a deployment graph, the destroy-time provisioners of a resource release the objects that are not in the state, for example DNS records.
Some resources have a different rule:
| Resource | Rule | File |
|---|---|---|
| A garage bucket of a deployment | lifecycle { destroy = false }: OpenTofu only removes it from the state. terraform_data.retire_bucket then empties and deletes the bucket, or keeps a *-backups bucket for the grace period. |
core/effects/tf/operator-provision/garage.tf |
| A domain registration | prevent_destroy: a plan that destroys or replaces it is refused. A replacement is a second registration, not a rename. The registration has a state key of its own. |
core/effects/tf/openprovider/domain.nix, core/effects/tf/inwx/domain.nix, core/effects/tf/porkbun/domain.nix |
| The secrets engine mounts of OpenBao | prevent_destroy, set through resourceMeta. |
core/components/hosting-provider/openbao/runtime/module.nix, core/components/hosting-provider/openbao/database-init.nix, core/components/hosting-provider/openbao/pki-init.nix, core/components/hosting-provider/openbao/ssh-certificates.nix, core/components/hosting-provider/openbao/valkey-engine.nix |
The cert auth mount of OpenBao in the state of spire-cert-auth |
A removed block with lifecycle.destroy = false: the state forgets the mount, and openbao-spire-auth adopts it by its path. |
core/components/hosting-provider/openbao/spire-auth.nix |
An lldap user or membership in retain |
A removed block with lifecycle.destroy = false: the object leaves the state and stays in lldap. When it comes back to the configuration, its import block adopts it again. |
core/resources/ldap/provision.nix |
The Incus network ACL of an operator deployment in the module bridge_acl |
A removed block with lifecycle.destroy = false: the state forgets the ACL and Incus keeps it. The hypervisor declares one ACL for each slot of the bridge, and a deploy credential that is limited to the project of the deployments cannot delete an ACL. |
core/effects/tf/incus/operator/bridge-isolation.tf |
| The node that keeps the OpenTofu state | refuse_backend_replace: an apply that deletes or replaces it is refused with status 3 before it changes anything. A plan gets a warning. |
core/effects/tf/common/bootstrap-apply.sh |
The docs build generates this table. It fails when a .nix or .tf file below core/ has prevent_destroy = true or destroy = false and no row names the file, and when a file of a row no longer has that code.
Gaps:
- An object that is in no state has no deletion path. The on-node configuration of PostgreSQL says so for its own objects: an object that is in neither the configuration nor the state is not touched, thus a name that left the configuration before the first apply stays.
- Only the node of the state backend has a guard against a replacement. For each other node, a change that replaces the node gives a new node, and the data on the old root disk is gone unless a volume or a backup holds it.
An object outside OpenTofu is dropped
A script or a unit makes some objects, not an OpenTofu resource. Such code follows a drop only when it lists the objects that exist, compares them with the objects that the code states, and removes the others. These do:
| Object | What removes it | File |
|---|---|---|
| The Incus trust of a deploy runner | The trusted certificates and pending tokens with no deploy runner of the host | core/components/hosting-provider/incus-deploy-tokens.nix |
| A maddy account | A compare of the accounts with the configuration | core/resources/smtp/maddy-accounts.nix |
| A NetBox service user and its token | The users of the group fediversity-managed that the deploy does not state |
core/components/hosting-provider/netbox-provision-token.py |
| An OpenBao token role | A compare with the roles of the configuration | core/components/hosting-provider/openbao/runtime/tokens.nix |
| A credential file on a node | phase_prune_credentials |
core/effects/common/deploy.sh |
A KV entry below vars/secret/deployer/ |
The keys that the code does not write, with all their versions | core/components/hosting-provider/openbao/deployer-kv-prune.nix |
| A Windmill API token | The unit deletes each token that it replaces | core/components/hosting-provider/windmill/api-token.nix |
| A Windmill script | The scripts of the folder that the module does not declare, with each version | core/components/hosting-provider/windmill/script-sync.nix |
| A PeerTube plugin | The plugin rows and node_modules entries that the deploy does not state |
core/components/operator/peertube/oidc-plugin-install.nix |
| A DNS mail record | A destroy-time step for each name | core/effects/tf/dns-lane/mail-records.tf |
| A Valkey user or rule | ACL SETUSER ... reset, and the users that the configuration does not state |
core/components/shared/valkey/valkey.nix |
| A Grafana alert rule | The retired UIDs in deleteRules |
core/components/hosting-provider/telemetry.nix, core/components/hosting-provider/telemetry/retired-alert-rules.nix |
The restic repository of a backup that stops while the bucket stays, of core/components/operator/acme-backup.nix, core/components/operator/local-cache-backup.nix |
The repository is deleted when its newest object is older than the retention period | core/resources/garage/retired-repository.sh |
| The registrar auth code of a domain transfer | drop_stale_domain_transfer, at a submit whose configuration registers a different domain, or no domain |
api/src/fediversity_api/migration_kv.py |
The docs build generates this table. It fails when a file of a row no longer has the code that removes the object. The list of backups with a sweep comes from the calls of mkRetiredRepository. The build does not find a new removal that has no row.
Gaps:
- When the rename table renames the field of a secret of the operator, the KV entry of the old name stays.
- When a role leaves
core/components/operator/mastodon/role-groups.nix, the holders keep it on Mastodon (core/components/operator/mastodon/ldap-roles.rb). No path of the configuration gets to this. - The sweep of retired restic repositories serves only the backups in the table above. Other restic repositories of a backup that stops stay, for example the repository of the dump before a PostgreSQL upgrade.
An application is disabled
The option deleteData of an application is in core/components/sharedOptions.nix. Its default is false.
- When
enablebecomes false anddeleteDatais false, the data stays: the database, the buckets, the generated secrets and the cache. A newenableuses the data again. - When
enableis false anddeleteDatais true, the next deployment deletes the data.purgedApplicationsincore/setups/wiring/node-selection.nixfinds these applications.core/setups/wiring/supporting-requests.nixleaves their requests out, thus the operator graph deletes their buckets, databases and keys, and the valkey node deletes their cache.core/effects/tf/operator-provision/openbao.tfdeletes their KV directories (purged_kv_dirs, fromcore/setups/wiring/purged-kv.nix). - A
*-backupsbucket stays for the grace period, as at a destroy.
The operator documentation of the option is in the operator configuration, and its role for erasure is in Data protection.
A deployment is destroyed
A destroy of a deployment runs tofu destroy on the operator graph. It removes the nodes. core/effects/tf/operator-provision/postgresql.tf drops the databases and the login roles of the deployment on the shared PostgreSQL node: their names have the namespace of the deployment as a suffix, and this graph is the only actor that runs again when the deployment ends. terraform_data.after_instances in core/effects/tf/operator-provision/openbao.tf deletes what remains below the KV prefix of a namespaced deployment after the instances are gone. terraform_data.retire_bucket empties and deletes each bucket that is not a backup bucket.
The destroy keeps this state:
| State | Why | How long |
|---|---|---|
Each <app>-backups bucket, with the new alias retained-<YYYYMMDD>-<id prefix> |
A restore stays possible, and the name of the bucket is free for a new deployment. | The grace period: fediversity.garage.server.retainedBackups.gracePeriodDays of the garage node, default 7 days |
| The KV entries that the bucket cannot be read without, and a retention record in OpenBao | Without the restic password, the backup cannot be read. The record tells the garage node what to delete. | The same grace period. core/resources/garage/sweep-retained-buckets.sh then deletes them with the bucket. |
| The domain registration | prevent_destroy. A registration that is lost cannot be made again as before. |
Until the registration ends at the registrar |
The row of the deployment in the panel, with deleted_at |
api/ can still bill the usage (panel/src/panel/models.py). |
Until the account of the operator is removed |
The builds and the audit_event rows in the database of api/ |
The usage periods of a destroyed deployment stay available, and the audit log records each deployment_delete. |
See Data protection |
| An object of a destroy step that failed | The destroy-time provisioners run with on_failure = continue, so that a destroy of a deployment that is not complete can finish. |
Until a person removes it. The run lists the object and exits with status 4. |
The check core/checks/integrations/destroy-reports-left-behind covers the last row. The operator steps for a retained bucket are in Know what a destroy keeps. An export for a move to a different hosting provider must be complete before the destroy: no key of the operator can read a retained bucket.
A destroy of all nodes of the hosting provider also destroys the node that keeps the OpenTofu state. Thus the run first moves the state to a local file (core/checks/integrations/destroy-detaches-state).
A service gets a new version
Each service that keeps state has a systemd.stateMigrations.<name> entry, in most cases in a file migrations.nix beside the service. The module is nix/contracts/nixos/modules/system/state-migrations.nix. An entry gives the version of the service in the current code. A unit <name>-migrate-pre runs before the service, compares that version with the version in a record file on the node, and decides:
flowchart TD
start["The unit `<name>-migrate-pre` starts"] --> record{"Is there a record of the version?"}
record -- "no" --> fresh["Record the version. Run `onFreshInstall` if the entry has one and the state is new."]
record -- "yes, the same version" --> none["Do nothing"]
record -- "yes, a later version than the code" --> down{"What is the value of `onDowngrade`?"}
down -- "refuse" --> stop["Exit with status 78. The service stays stopped."]
down -- "warn or ignore" --> older["Record the older version. Run nothing."]
record -- "yes, an earlier version than the code" --> skip{"Is the step larger than `maxSkip` permits?"}
skip -- "yes" --> stop
skip -- "no" --> backup["Run `backup`, then `steps` and `onUpgrade`"]
backup --> service["The service starts"]
service --> post["The unit `<name>-migrate-post` runs `onUpgradePost` and records the version"]
The rules of the module:
- The backup.
backupruns one time for each upgrade, before each other command of the upgrade. - The downgrade.
onDowngradeisrefuseby default. Exit status 78 tells systemd not to start the unit again. Withwarnorignore, the unit records the older version and runs nothing: these are services that also read the state of a later version. - The largest step.
maxSkipis not set by default, and then each step to a later version is permitted. Withmajor, a step across more than one major release is refused. Withminor, a step across more than one minor release is also refused. - The restore. When the entry has a
restorecommand, the unit records an upgrade that is not complete. A deploy of the version from before the upgrade then runsrestore. A deploy of a third version is refused. Withoutrestore, the way back is a manual restore of the backup. - No record. When
freshInstallTestisnulland the node has no record, the unit records the version and does not migrate. Thus the first deploy of an entry onto state that exists does not make a backup.
What an entry does not do:
- It moves no object and changes no OpenTofu state.
- In most entries, it runs no schema command: the service migrates its own schema when it starts. The column "Upgrade command" of the table shows the entries that do run a command. For example,
core/components/operator/mastodon/migrations.nixruns the database migrations of Mastodon, and the post-deployment migrations only after the services are up again. - No entry has a command for one specific version: the list
stepsis empty in each entry. - An entry whose version is a release series, for example
2.5, sees no change at a patch release. Thus a patch release makes no backup.
These files below core/components and core/resources define an entry. core/setups/nixos/postgresql.nix and core/setups/nixos/mysql.nix define the entries of PostgreSQL and MariaDB.
core/components/dev/forgejo/migrations.nixcore/components/hosting-provider/audit-shipper-migrations.nixcore/components/hosting-provider/bind-migrations.nixcore/components/hosting-provider/netbox/migrations.nixcore/components/hosting-provider/openbao/migrations.nixcore/components/hosting-provider/spire/migrations.nixcore/components/hosting-provider/telemetry/migrations.nixcore/components/hosting-provider/terraform-backend/migrations.nixcore/components/hosting-provider/windmill/migrations.nixcore/components/operator/mastodon/migrations.nixcore/components/operator/peertube/migrations.nixcore/components/operator/pixelfed/migrations.nixcore/components/shared/authelia/migrations.nixcore/components/shared/lldap/migrations.nixcore/components/shared/valkey/migrations.nixcore/resources/garage/migrations.nixcore/resources/smtp/maddy-migrations.nix
The table shows the entries of the nodes of core with the default applications and the configuration of the tests. The docs build generates it from config.systemd.stateMigrations of each node of the groups hosting-provider and operator of core/testers/groups.nix. The column "Backup before the upgrade" is written by hand in core/docs/md/technical-design/state-lifecycle.nix, and the build fails when it is not in agreement with the backup script of the entry. A row is one entry, or the entries with the same name and the same values on more than one node. The version of each entry is the version of the package, or its release series (garage, for example), or its major version (PostgreSQL). An entry with a restic snapshot makes it on a node that has the offsite backup for that service. Without the offsite backup, the entry makes a local copy or a dump, or no backup.
| Entry | Nodes | Version | Largest step | Downgrade | Backup before the upgrade | Automatic restore | Upgrade command |
|---|---|---|---|---|---|---|---|
authelia |
hosting-provider/authelia, operator/authelia |
4.39.28 |
no limit | refuse |
A restic snapshot | yes | no |
bind |
hosting-provider/bind |
9.20.30 |
no limit | warn |
A restic snapshot | yes | no |
garage |
hosting-provider/garage |
2.5 |
no limit | refuse |
A local copy | no | no |
grafana |
hosting-provider/telemetry |
13.3.0 |
no limit | refuse |
A restic snapshot | yes | no |
lldap |
hosting-provider/lldap, operator/lldap |
0.6.3 |
no limit | refuse |
A restic snapshot | yes | no |
maddy |
hosting-provider/smtp, operator/smtp |
0.9.6 |
no limit | warn |
A local copy | no | no |
mastodon |
operator/mastodon |
4.7.4 |
one minor release | refuse |
A restic snapshot | yes | yes |
netbox |
hosting-provider/netbox |
4.7.3 |
one major release | refuse |
A restic snapshot | yes | no |
openbao |
hosting-provider/openbao |
2.7 |
no limit | refuse |
A local copy | no | no |
peertube |
operator/peertube |
8.3.2 |
no limit | refuse |
A restic snapshot | yes | no |
pixelfed |
operator/pixelfed |
0.12.9 |
no limit | refuse |
A restic snapshot | yes | yes |
postgresql |
hosting-provider/postgresql |
18 |
no limit | refuse |
A restic snapshot of all clusters | no | yes |
redis-<name> |
hosting-provider/valkey, operator/valkey |
9.1 |
no limit | warn |
A restic snapshot | yes | no |
spire-server |
hosting-provider/spire |
1.15 |
no limit | warn |
A local copy | no | no |
terraform-backend |
hosting-provider/terraform-backend |
0.2.4 |
no limit | refuse |
A restic snapshot | yes | no |
windmill |
hosting-provider/windmill |
1.839.0 |
no limit | refuse |
A restic snapshot | yes | no |
These nodes of that configuration have no entry: hosting-provider/otel-collector, operator/grafana.
The table does not read the overview tag, the hypervisor and the development hosts. These entries are not on a node of that configuration, thus the table does not show them:
fediversity-audit-shipperofcore/components/hosting-provider/audit-shipper-migrations.nix. It refuses a downgrade and makes a local copy of its state directory.forgejoofcore/components/dev/forgejo/migrations.nix.mysqlofcore/setups/nixos/mysql.nix, for a node that runs MariaDB.
Gaps:
- The
postgresqlentry itself does not limit the step. The guard of the PostgreSQL module, below, permits only a step of one major version.
PostgreSQL has one more rule. Each major version keeps its cluster in a directory of its own, thus a new major would start with an empty cluster. core/setups/nixos/postgresql.nix sets the major in the code. When the major goes up by one, its guard runs pg_upgrade --copy from the old cluster. For each other change of the major, it keeps PostgreSQL stopped with exit status 78. MariaDB has a rule of its own in core/setups/nixos/mysql.nix: the module runs mariadb-upgrade after each start of the server, because an upgrade is complete only after that command.
The maintainer steps for an update, and the reasons for each service, are in Update nixpkgs. Each entry has a check in core/checks/integrations/, for example garage-series and openbao-series.
The schema of the api or the panel changes
The two components keep their records in a PostgreSQL database of the hosting provider. Each applies its own migrations, and each has a systemd.stateMigrations entry that only records the version, refuses a downgrade, and makes the backup.
| Component | Entry | Version | Migrations | Definition |
|---|---|---|---|---|
api/ |
fediversity-api |
12 |
Alembic revisions in api/src/fediversity_api/state_migrations/versions |
api/nix/component/migrations.nix |
panel/ |
panel |
9 |
Django migrations in panel/src/panel/migrations |
panel/nix/component/migrations.nix |
api/applies the Alembic revisions when it opens its store (_migrateinapi/src/fediversity_api/state.py). A PostgreSQL advisory lock keeps a second process out of the same migration.api/refuses a database at a revision that its release does not know. The revisions only go forward.panel/runsmanage migratein thepreStartofpanel.service(panel/nix/configuration.nix), each time the package changes. Django does not refuse a database that later migrations changed, thus the version record is the only guard against a downgrade. The panel has no migrations that go back.- The version of each entry is the number of the last migration file. Thus the version changes only when the schema changes. For the panel, the migrations of the Django apps from nixpkgs are not in the version.
- The backup comes from
mkUpgradeBackupForincore/components/operator/app-resources.nix: on a node with the offsite backup ofcore/components/shared/postgres-backup.nix, a dump of the database before the upgrade. Without that backup, the entry makes no backup. - The largest step is not limited: the entries do not set
maxSkip. Alembic and Django apply each migration between the two versions in sequence.
The two entries are not in the table of the section before, because the nodes of api/ and panel/ are not nodes of core.
An effect fails
An effect can fail, or a person or a timeout can stop it, at any point. No effect goes back to the state from before the run. Each effect is recovered forward: run it again. For a deployment graph, notes/forward-recovery.md gives the rules and tells how to read a failure.
| Effect | When it fails or is stopped | Run again | Automatic retry or wait | Objects left behind | File |
|---|---|---|---|---|---|
A deployment graph (core/effects/tf/, core/effects/k8s/) |
The state records the steps that are done. A dead run can leave its lock. A state write that the backend rejects is kept as errored-<time>.tfstate. |
Yes: the same apply continues from the state (notes/forward-recovery.md). |
The client of the HTTP backend tries a request again 2 times, after 1 to 30 s. Before an apply, the run waits for the backend at most 1800 s. With TF_RECLAIM_OWN_LOCK=1, the run releases a lock of its own identity. |
Yes: a destroy-time step that fails gives status 4 | core/effects/tf/common/bootstrap-apply.sh, core/effects/tf/common/effect.nix |
| An Incus host of a graph | As for a deployment graph | Yes | The step waits for incus exec in the new instance 30 times, each 5 s apart, and then for its address 30 times, each 2 s apart. |
As for a deployment graph | core/effects/tf/incus/host/await-incus.sh |
| A Kubernetes rollout | A rollout that does not become ready is undone, and the apply fails | Yes | The step waits for the rollout at most 600 s. The copy of an image is tried 3 more times. | As for a deployment graph | core/effects/k8s/effect.nix |
A deploy over SSH (ssh) |
The node runs the old or the new configuration | Yes: it copies the closure and switches again | It waits for the target 30 times, each 5 s apart. When another switch holds the lock, it tries again 60 times, each 5 s apart. | No | core/effects/common/deploy.sh, core/effects/ssh/run.sh |
A deploy into an Incus instance (incus) |
The node runs the old or the new configuration. After a failed switch, /sbin/init points to the old closure again. |
Yes: it copies the closure and switches again | It waits for the target 30 times, each 5 s apart | No | core/effects/common/deploy.sh, core/effects/incus/run.sh |
An install (nixos-anywhere) |
The disks can be wiped, or hold a part of the install | Yes, but a run that installs wipes the disks again. A target that runs the expected system is skipped, and its disks stay. | None | No | core/effects/nixos-anywhere/install.sh |
An export of a migration (migration) |
The services that the run stopped start again | Yes: it makes a new snapshot | None | No | core/effects/migration/run.sh |
A restore of a migration (migration-restore) |
The restore can be partly done | It restores again. The script does not say that this is safe. | It waits for the instance to boot at most 900 s | No | core/effects/migration-restore/run.sh |
An operation of an application (app-operation) |
A deploy that restarts the node makes the operation fail | It depends on the command | None. The command stops after 300 s. | No | core/effects/app-operation/run.sh |
A retry of the objects left behind (left-behind) |
A removal that fails keeps its record, and the other removals continue | Yes: each removal has the guards of its step | None. It is itself the retry, and a person starts it from api. |
Keeps the records that it could not remove | core/effects/left-behind/retry.sh |
A sync of a zone (octodns) |
The zone can hold a part of the changes | Yes: a sync writes only the difference | None | No | core/effects/octodns/effect.nix |
A node effect that api starts (an export, a restore, an operation, a retry) |
api cancels a job that is not done after 3600 s, and the effect fails |
As for the effect | None | As for the effect | api/src/fediversity_api/node_effect.py, api/src/fediversity_api/windmill.py |
The docs build generates this table. It fails when a file of a row no longer has the code that does what the row says. The build does not find a new retry or a new effect that has no row.
Gaps:
- No effect tries a failed run again by itself. The retries in the table are inside one run.
- A restore of a migration and some operations of an application do not say that a second run is safe.
What this page does not cover
- The list of each place where state is kept is in the part
state-register. - The backups, their retention and their restore are in the hosting provider guide.
- A move of an operator between hosting providers is in the part
capability-flows.
State register
This part lists each store of state of the software: where it is, which component owns it, whose personal data it holds, how long it keeps the data, how it is protected, and how to erase data from it. It gives the facts for the record of processing that the data protection page assigns to the operator and to the hosting provider. It is not legal advice, and it gives no legal classification.
How the register is made
The tables on this page are generated at the documentation build from two inputs:
- The declaration
core/docs/md/technical-design/state-register-stores.nix. It gives, for each store, the purpose, the personal data, the retention, the encryption, the backup and the procedure to erase, with the files that these statements come from. - The code. The build reads the databases, the buckets, the valkey namespaces and the
persistentPathsthat the components request, the tables that the migrations ofapi/create (api/src/fediversity_api/state_migrations/versions/), and the models of the panel (panel/src/panel/models.py). The components are those of an example deployment with each application enabled.
The build compares the two inputs. The check core-docs fails when:
- an item in the code is in no store of the declaration,
- the declaration names an item or a source file that does not exist,
- a component owns no store and is not in the list of components without a store.
The check sees only the items that the code declares as data. It does not see a state directory that a module sets on a node, for example /var/lib/maddy. The declaration gives these paths by hand.
"Open" in a table means that no source in the tree gives the fact. The hosting provider finds it in its setup or in the upstream documentation, see the unknowns of the data protection page.
Which store holds which
Some stores are inside other stores. A statement about the outer store, for example its encryption, also applies to the stores inside it.
flowchart TD pools["Incus storage pools on the hypervisor"] nodes["Paths on the nodes"] postgresql["PostgreSQL server"] garage["Garage"] databases["Databases of the applications and the services"] buckets["Media and telemetry buckets"] backups["Backup buckets"] pools --> nodes nodes --> postgresql nodes --> garage postgresql --> databases garage --> buckets garage --> backups databases -- "backup" --> backups nodes -- "backup" --> backups
Location and owner
The column "Declared in the code" gives the items that the build found in the code for the store. The databases of the panel and of api/ and their backup buckets are not in the example deployment, thus the build does not check them.
| Store | Owner | Node and path | Purpose | Declared in the code |
|---|---|---|---|---|
api/ database |
api |
The api database on the PostgreSQL node, or a SQLite file. |
The deployments, their builds and operations, their domains, the SBOMs of their nodes, and the objects that a destroy-time step did not remove. | table of api/: deployment, build, operation_run, zone_follow, domain_transfer, domain_event, domain_contact, sbom_component, node_sbom, node_sbom_component, left_behind |
| Application databases | mastodon, peertube, pixelfed |
One database for each application of a deployment, on the PostgreSQL node of the hosting provider. | The records of the application. | bucket: mastodon-backups, peertube-backups, pixelfed-backups; database: mastodon, peertube, pixelfed |
| Application files on the node | peertube, pixelfed |
/var/lib/peertube on the PeerTube node and /var/lib/pixelfed/storage/app on the Pixelfed node. |
The files that the application does not keep in a bucket. | -- |
| Application media buckets | mastodon, peertube, pixelfed |
Buckets on the Garage node of the hosting provider, with a prefix for each operator. | The media that the application serves. | bucket: mastodon, peertube-videos, peertube-playlists, pixelfed |
| Audit records | api |
The audit_event and audit_chain_anchor tables of the api database. api/ also writes each event to the journal, and the tenant audit of Loki keeps that copy. |
The record of who did which action on a deployment. | table of api/: audit_event, audit_chain_anchor |
| Authelia | authelia |
The authelia database on the PostgreSQL node, and the authelia namespace of valkey for the sessions. Each group has its own Authelia node. |
The single sign-on of a group of nodes. | bucket: authelia-backups; database: authelia; valkey namespace: authelia |
| Backups | each node | The *-backups buckets on the Garage of the hosting provider. A group can also copy the backups to a target outside the site. |
The restore of a service. | -- |
| DNS zones | bind |
/var/lib/named on the BIND node. |
The DNS zones that the hosting provider serves. | bucket: bind-backups |
| Garage | garage |
/var/lib/garage on the Garage node of the hosting provider, with the metadata snapshots in /var/lib/garage/snapshots. It holds each bucket of this register. |
The object storage, and the target of the backups. | -- |
| Grafana of the hosting provider | telemetry |
A SQLite file on the telemetry node. | The accounts of the OIDC logins and their preferences, the service accounts, the silences, the annotations and the dashboards that are not provisioned. | bucket: grafana-backups |
| Grafana of a deployment | grafana |
The file grafana.db in a runtime directory below /run on the Grafana node of the deployment. |
The local records of the Grafana that shows the metrics of a deployment to its operator. | -- |
| Incus storage pools | incus |
The storage pools of fediversity.incus.storagePools on the hypervisor. |
The root file systems of the nodes. Each store of this register that is a path on a node is in a pool. | -- |
lldap |
lldap |
The lldap database on the PostgreSQL node. Each group has its own lldap node. |
The directory of the users and the groups of a group of nodes. | bucket: lldap-backups; database: lldap |
| Journal and access logs | each node | The journal of each node, and the access log of nginx on each node that runs nginx. | The debugging of a node. | -- |
smtp |
/var/lib/maddy on the SMTP node of the group. |
The queue of the mail that the applications and the shared services send. | -- | |
| NetBox | netbox |
The netbox database on the PostgreSQL node. |
The address management (IPAM) of the hosting provider. | bucket: netbox-backups; database: netbox |
| OpenBao | openbao |
The raft storage in /var/lib/openbao/raft on the OpenBao node. The secrets of an instance are below the KV path secret/data/vars/secret/<instance>/. |
The secrets that the components generate and the secrets that the operator brings. | bucket: openbao-backups |
| Panel database | panel |
The panel database on the PostgreSQL node. A panel without a DATABASE_URL uses a SQLite file below /var/lib. |
The accounts of the operators, their settings and their deployments. | model of the panel: Deployment, DomainContact, OperatorSettings |
| PostgreSQL server | postgresql |
The PostgreSQL node of the hosting provider. It holds each database of this register that is not SQLite. | The database server that the components share. | bucket: postgresql-backups |
| SPIRE | spire |
/var/lib/spire-server on the SPIRE node. |
The identities of the workloads. | -- |
| Telemetry (Loki, Tempo, Mimir) | telemetry |
The loki, tempo and mimir buckets on Garage. |
The logs, the traces and the metrics of the nodes. | bucket: loki, tempo, mimir |
| OpenTofu state backend | terraform-backend |
/var/lib/private/terraform-backend on the node of the state backend. |
The OpenTofu states of the deployments. | bucket: terraform-backend-backups; path: /var/lib/private/terraform-backend |
| Valkey namespaces | valkey |
/var/lib/redis-<namespace> on the valkey node of the group. An application of a group that has no valkey node uses a local redis on its own node. |
The cache and the job queue of an application. | valkey namespace: mastodon, peertube, pixelfed, valkey |
| Windmill | windmill |
The windmill database on the PostgreSQL node. |
The jobs that deploy. | bucket: windmill-backups; database: windmill |
Personal data and retention
The personal data of each log, trace and audit channel, with its readers and its controls, is in Logs, traces and audit records.
The retention settings that an operator can set for an application are in Personal data per application.
| Store | Personal data, and of whom | Retention of the live store |
|---|---|---|
api/ database |
Of the operators: the configuration of each deployment, the build logs, the output and the actor of each operation, and the identifier and the handle of each domain contact. | services.fediversity-api.logRetentionDays for the build logs and the runs of the operations: 30 days by default, 0 keeps them. The operation_run table keeps at most the last 100 runs of each deployment. No job removes old rows of the build table. A row of the left_behind table stays after its deployment is gone, until a retry removes the object or an operator forgets the row. For the other tables the code sets no period. |
| Application databases | Of the users of the application: the accounts and their email addresses, the posts, the direct messages, the follows and the sessions. For PeerTube also the videos and their metadata, the comments and the watch history. The applications also keep content that they get from other instances. | Until the user or a moderator deletes the data. The code sets none of the retention settings of the applications. |
| Application files on the node | Of the users of the application: for example avatars, thumbnails, captions, profile headers and custom emoji. /var/lib/peertube also holds the logs of PeerTube. |
Until the application deletes the file. The code sets no period. |
| Application media buckets | Of the users of the application: the uploads, for example images, videos and avatars. Mastodon also keeps cached media of other instances. | Until the application deletes the object. The code sets none of the media retention settings of the applications. |
| Audit records | Of the operators and the administrators: the actor of each action. | services.fediversity-api.auditRetentionDays for the table and services.fediversity-telemetry.auditRetentionDays for the tenant audit, each 365 days by default. |
| Authelia | Of the users of the group: the consents, the second factors, the identity verification tokens, the OIDC state and the sessions. | The code sets no period. The code also does not set the session lifetime, the inactivity limit and the regulation. The operator can set them. |
| Backups | That of the store that each backup is from. See its row. | See the backup of each row. After a destroy, a *-backups bucket stays for the grace period of Garage. |
| DNS zones | None from the software: the records hold the names and the addresses of the services. | Open: no source in the tree gives it. |
| Garage | That of the buckets on it. See their rows. | That of the buckets on it. Garage keeps one copy of each object (replication_factor = 1). |
| Grafana of the hosting provider | Of the administrators: the accounts that the first OIDC login makes. | The code sets no period. |
| Grafana of a deployment | Of the operators: the accounts that an OIDC login makes. | Until the next restart of the service. |
| Incus storage pools | That of the stores on the nodes. See their rows. | Until a destroy removes the node. |
lldap |
Of the users of the group: the user names, the email addresses, the names, the avatars and the group memberships. The first administrator gets the email of the configuration. |
Until a member of lldap_admin deletes the user. |
| Journal and access logs | Of the users, the operators and the administrators: client addresses and user names (sshd, nginx errors, Authelia), email addresses (maddy), the audit lines of OpenBao, and what each application writes to its log. | The journal: 30 days and at most 512 MB (MaxRetentionSec, SystemMaxUse). The access log of nginx: logrotate keeps 26 weeks. |
| Of the senders and the recipients: the addresses, the headers and the content of the mail that passes, for example password resets and notifications. | The code sets no retention on the queue. | |
| NetBox | Of the administrators: their accounts. If the IPAM records hold personal data is open. | Open: no source in the tree gives it. |
| OpenBao | Of the administrators: the names that OIDC logins give them. If a secret is personal data is open. | Until deleteData or a destroy removes the KV entries. The code sets no period. |
| Panel database | Of the operators: the accounts, with the email address and the user name from the OIDC login, the sessions, the domain contacts of the operator with their labels and handles, and the deployments with their configuration and the domain contact of each role. The panel keeps the deployments that the operator deleted, for the billing. | Until a panel administrator removes the account. The session lifetime is the default of Django, and no job removes expired sessions. |
| PostgreSQL server | That of the databases on it. See their rows. | That of the databases on it. |
| SPIRE | Open: no source in the tree gives it. | Open: no source in the tree gives it. |
| Telemetry (Loki, Tempo, Mimir) | A copy of the journal of each node of the hosting provider, and the journal lines of priority warning and higher of each node of an operator deployment. See the row of the journal for the personal data. |
services.fediversity-telemetry.retentionDays, 90 days by default. |
| OpenTofu state backend | Of the domain contacts: the state with the key <base>-<contact>-domain-contact holds the contact that an operator saves through api/: the name, the address, the telephone number and the email address, not hidden. If the other states hold personal data is open. |
The code sets no period. The state of a contact stays until DELETE /domain_contacts/{contact_id} of api/ erases it. |
| Valkey namespaces | Of the users of the application: what the application caches, for example queued jobs and timelines. | The code sets no retention on the cache. |
| Windmill | Of the administrators: their accounts. Of the operators: the arguments, the result and the log of each job, which are the configuration of a deployment, its deploy log and the SBOMs of its nodes. | Windmill removes a completed job after services.fediversity-windmill.jobRetentionDays, 30 days by default. |
Encryption, backup and erasure
The table gives the encryption that the software adds to a store. The encryption of the disks is not in the code. The contract templates state that the disks of the hypervisors are not encrypted.
The procedures of the hosting provider are in the hosting provider guide: Know how long the backups keep personal data, Erase personal data and Know what a destroy keeps.
| Store | Encryption at rest | Backup | Procedure to erase |
|---|---|---|---|
api/ database |
None in the code. | A restic repository in the api-backups bucket. The backup keeps 30 days (--keep-within 30d). |
DELETE /deployments/{id} destroys a deployment. DELETE /domain_contacts/{contact_id} removes the handle of a contact. DELETE /deployments/left-behind/{entry_id}/{deployment_id} forgets one left-behind object. |
| Application databases | None in the code. | A restic repository in the <app>-backups bucket. The backup keeps 30 days (--keep-within 30d). |
The account deletion of the application. To erase all data of an application, set enable to false and deleteData to true, or destroy the deployment. |
| Application files on the node | None in the code. | A restic repository in the peertube-backups or the pixelfed-backups bucket. The backup keeps 30 days (--keep-within 30d). |
Open: no source in the tree gives it. |
| Application media buckets | None in the code. | The media backup, in the prefix media/ of the <app>-backups bucket. It keeps 30 days of history by default (fediversity.mediaBackup.instances.<app>.retentionDays). |
The application deletes the object. media-backup-<app> erase <bucket> <key> also removes the object from the media backup and from its history. |
| Audit records | None in the code. | With the api database, in the api-backups bucket. The backup keeps 30 days (--keep-within 30d). |
Open: no source in the tree gives it. |
| Authelia | Authelia encrypts its database with a storage key (storageEncryptionKeyFile). |
A restic repository in the authelia-backups bucket. The backup keeps 30 days (--keep-within 30d). |
Open: no source in the tree gives it. |
| Backups | The media backups and the restic backups are encrypted. | None in the code. Garage has no backup of its objects. | The restic backups have no erase command. An erased record stays in them until they expire. The media backup has media-backup-<app> erase. |
| DNS zones | None in the code. | The records of the apex zone on the primary, in the bind-backups bucket. The backup does not hold the zones of the operators, the zones of the challenge lanes or the DNSSEC keys (core/components/hosting-provider/bind/backup.nix). The backup keeps the snapshots of --keep-daily 7 --keep-weekly 4 --keep-monthly 6. |
Open: no source in the tree gives it. |
| Garage | None in the code. | None in the code for the objects. Garage makes snapshots of its metadata on the same node. A group can copy the backups to a target outside the site. | See the rows of the buckets. A destroy keeps each *-backups bucket for a grace period (fediversity.garage.server.retainedBackups.gracePeriodDays, 7 days by default), then a timer deletes it. |
| Grafana of the hosting provider | Grafana encrypts the secrets in its database with a secret key. | The SQLite database, in the grafana-backups bucket. The secret key of the database is in OpenBao KV, not in the backup (core/components/hosting-provider/telemetry/backup.nix). The backup keeps the snapshots of --keep-daily 7 --keep-weekly 4 --keep-monthly 6. |
Open: no source in the tree gives it. |
| Grafana of a deployment | None in the code. | None. | A restart of the service. |
| Incus storage pools | None in the code. | None for a pool. See the rows of the stores on the nodes. | A destroy of a deployment removes its nodes. |
lldap |
None in the code. | A restic repository in the lldap-backups bucket. The backup keeps 30 days (--keep-within 30d). |
A member of lldap_admin deletes the user at https://users.<domain>/. |
| Journal and access logs | None in the code. | None. Loki keeps a copy, see the row of the telemetry. | Open: no source in the tree gives it. |
| None in the code. | None. | Open: no source in the tree gives it. | |
| NetBox | None in the code. | Two restic repositories in the netbox-backups bucket: one for the database (core/components/hosting-provider/netbox/default.nix) and one for the files that users give to NetBox (core/components/hosting-provider/netbox/uploads-backup.nix). The backup keeps the snapshots of --keep-daily 7 --keep-weekly 4 --keep-monthly 6. |
Open: no source in the tree gives it. |
| OpenBao | The unseal key is sealed to the TPM, and its copy on the disk is encrypted to age recipients. | A raft snapshot and the unseal key, in the openbao-backups bucket. The unseal key is encrypted to the admin recipients (core/components/hosting-provider/openbao/backup.nix). The backup keeps the snapshots of --keep-daily 7 --keep-weekly 4 --keep-monthly 6. |
deleteData of an application deletes its generated secrets and the secrets that the operator brought. A destroy removes the KV entries of the deployment. |
| Panel database | None in the code. | A restic repository in the panel-backups bucket. The backup keeps 30 days (--keep-within 30d). |
A panel administrator removes the account. The panel first erases the domain contacts of the account, and queues the destroy of each deployment of the account. |
| PostgreSQL server | None in the code. | Each database has its own backup. Before an upgrade of the major of PostgreSQL, the server also makes a snapshot of its data directory in the postgresql-backups bucket. |
See the rows of the databases. |
| SPIRE | None in the code. | None. | Open: no source in the tree gives it. |
| Telemetry (Loki, Tempo, Mimir) | None in the code. | None. | Open: no source in the tree gives it. |
| OpenTofu state backend | None in the code. | The states and the kmsKey that encrypts them, in the terraform-backend-backups bucket. The key is encrypted to the admin recipients (core/components/hosting-provider/terraform-backend/backup.nix). The backup keeps the snapshots of --keep-daily 7 --keep-weekly 4 --keep-monthly 6. |
For a contact: DELETE /domain_contacts/{contact_id} of api/ deletes the customer in the registrar account and then the state. The call refuses while a domain of the account names the contact. A change of a name deletes the old customer, unless a domain names it. |
| Valkey namespaces | None in the code. | A restic repository for each namespace, in the cache backups bucket of the group. A local redis goes to the <app>-backups bucket. The backup keeps 30 days (--keep-within 30d). |
deleteData of the application deletes its cache. |
| Windmill | None in the code. | A restic repository in the windmill-backups bucket. The backup keeps the snapshots of --keep-daily 7 --keep-weekly 4 --keep-monthly 6. |
Open: no source in the tree gives it. |
Components without a store
| Component | Why it has no store |
|---|---|
otel-collector |
It sends each signal that it gets to the store of that signal on the telemetry node. |
overview |
It serves a static page that lists the web UIs of the deployment. |
Sources
| Store | Sources |
|---|---|
api/ database |
api/src/fediversity_api/state.py, api/src/fediversity_api/left_behind.py, api/src/fediversity_api/state_migrations/versions/0001_baseline.py, api/src/fediversity_api/state_migrations/versions/0010_left_behind.py, api/nix/component/default.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Application databases | core/components/operator/app-resources.nix, core/components/sharedOptions.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Application files on the node | core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Application media buckets | core/components/operator/app-resources.nix, core/resources/garage/default.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Audit records | api/src/fediversity_api/state.py, api/src/fediversity_api/settings.py, api/nix/configuration.nix, core/components/hosting-provider/telemetry.nix, core/docs/md/guides/hosting-provider/logs.md, core/docs/md/guides/hosting-provider/backups.md |
| Authelia | core/components/shared/authelia/default.nix, core/components/shared/authelia/database.nix, core/components/shared/authelia/sessions.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Backups | core/components/operator/app-resources.nix, core/components/shared/postgres-backup.nix, core/docs/md/compliance/contract-templates.md, core/docs/md/guides/hosting-provider/backups.md |
| DNS zones | core/components/hosting-provider/bind.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Garage | core/resources/garage/server.nix, core/resources/garage/server-options.nix, core/resources/garage/metadata-snapshots.nix, core/resources/garage/migrations.nix, core/docs/md/guides/hosting-provider/backups.md |
| Grafana of the hosting provider | core/components/hosting-provider/telemetry.nix, core/components/hosting-provider/telemetry/backup.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Grafana of a deployment | core/components/operator/grafana/node.nix, core/docs/md/compliance/data-protection.md |
| Incus storage pools | core/components/hosting-provider/incus.nix, core/docs/md/guides/hosting-provider/access.md |
lldap |
core/components/shared/lldap/default.nix, core/components/shared/lldap/database.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Journal and access logs | core/setups/nixos/common.nix, core/setups/nixos/nginx.nix, core/components/hosting-provider/node-logs.nix, core/docs/md/guides/hosting-provider/logs.md |
core/components/shared/smtp/default.nix, core/resources/smtp/maddy-migrations.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
|
| NetBox | core/components/hosting-provider/netbox.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| OpenBao | core/components/hosting-provider/openbao/openbao.nix, core/components/hosting-provider/openbao/backup.nix, core/components/hosting-provider/openbao/deployer-policies.nix, core/components/sharedOptions.nix, core/docs/md/compliance/threat-model.md, core/docs/md/guides/hosting-provider/backups.md |
| Panel database | panel/src/panel/models.py, panel/nix/component/default.nix, panel/nix/configuration.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| PostgreSQL server | core/setups/wiring/postgresql-roster.nix, core/components/hosting-provider/postgresql/default.nix, core/docs/md/guides/hosting-provider/backups.md |
| SPIRE | core/components/hosting-provider/spire/spire.nix, core/docs/md/guides/hosting-provider/backups.md |
| Telemetry (Loki, Tempo, Mimir) | core/components/hosting-provider/telemetry.nix, core/components/hosting-provider/telemetry/object-store.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/logs.md, core/docs/md/guides/hosting-provider/monitoring.md, core/docs/md/guides/hosting-provider/backups.md |
| OpenTofu state backend | core/components/hosting-provider/terraform-backend/state-dir.nix, core/components/hosting-provider/terraform-backend/backup.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md, core/docs/md/guides/hosting-provider/domain-transfers.md |
| Valkey namespaces | core/components/shared/valkey/valkey.nix, core/components/shared/valkey/default.nix, core/components/sharedOptions.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
| Windmill | core/components/hosting-provider/windmill/windmill.nix, core/components/hosting-provider/windmill/default.nix, core/docs/md/compliance/data-protection.md, core/docs/md/guides/hosting-provider/backups.md |
Traceability of the capabilities
The deliverable is a technical design for a hosting panel with five capabilities: to install applications, to configure them, to update them, to do user management, and to switch customers to and from other hosting providers. This part connects each capability to the requirements in the specifications, to the code, to the tests and to the docs pages. It also shows where the code has no requirement.
How to read the tables
The specifications are in the directories core/specs, api/specs and panel/specs.
A specification gives its requirements as numbered items, for example FR-001.
The overview of the specifications gives the specifications of core.
A capability has more than one function.
Each row of the tables is one function.
The list of functions is made by hand in core/docs/md/technical-design/traceability.nix.
The build of the docs stops when a row gives a path that is not in the repository, or a requirement that is not in the spec file.
The build does not examine whether a test proves a requirement.
The build calculates the status of a row with these rules, in this sequence:
no spec: the row has no requirement.planned: each requirement of the row has a marker(planned, #<number>)in its spec file. The status gives the issues.partial, planned: some requirements of the row have that marker.partial: the row has a gap, or the row has no test, or the status of a cited spec file is notImplemented. A spec file with the statusPartially implementeddoes not say which of its requirements are open, thus each row that cites it ispartial.built: all other rows.
A requirement that the code does not have gets the marker (planned, #<number>), where the number is the issue for the work.
At this revision, no requirement in the three directories has the marker.
A spec file that has the section Known Deviations gives there the differences between the code and the design, and the issue of each difference when one exists.
The table of the specification files gives these issues.
The build does not read the state of an issue, thus an issue in the table can be closed.
Requirements, tests and status
| Capability | Function | Spec and requirement | Test | Status |
|---|---|---|---|---|
| Install | The catalog of the applications that an operator can select | core/specs/003-applications-catalog/spec.md FR-001, FR-002, FR-003, FR-004, FR-005; api/specs/003-application-catalog/spec.md FR-001, FR-002, FR-003, FR-004 |
core/checks/applications-catalog.nix, api/src/fediversity_api/tests/test_http_application_list.py |
built |
| Install | The operator submits a deployment, and a job builds and deploys it | panel/specs/002-deployment-lifecycle/spec.md FR-001, FR-002; api/specs/001-deployments-and-builds/spec.md FR-001, FR-003, FR-011; api/specs/005-job-execution/spec.md FR-001, FR-002, FR-003, FR-004, FR-005; core/specs/004-deployment-programs/spec.md FR-001 |
panel/src/panel/tests/test_deployment_form.py, panel/checks/js-tests/deployment-submit.test.js, panel/checks/apps-panel, api/src/fediversity_api/tests/test_deployment.py, api/src/fediversity_api/tests/test_http_deployment.py, api/checks/apps-api, core/checks/deployment/tf-incus-operator |
built |
| Install | The components of the applications Mastodon, PeerTube and Pixelfed | core/specs/008-components/operator/mastodon.md FR-001, FR-002; core/specs/008-components/operator/peertube.md FR-001, FR-002; core/specs/008-components/operator/pixelfed.md FR-001, FR-002 |
core/checks/apps, core/components/operator/mastodon/test-script.py, core/components/operator/peertube/test-script.py, core/components/operator/pixelfed/test-script.py |
built |
| Install | The domain of a deployment: the claim, the DNS records and the certificates | api/specs/001-deployments-and-builds/spec.md FR-004; core/specs/009-deployment-methods/tf-incus-operator.md FR-018, FR-019; core/specs/007-resources/ssl.md FR-001, FR-003, FR-006; panel/specs/002-deployment-lifecycle/spec.md FR-003, FR-013, FR-015, FR-019 |
api/src/fediversity_api/tests/test_apex_claims.py, api/src/fediversity_api/tests/test_domains.py, panel/src/panel/tests/test_domain_page.py, core/checks/deployment/domain-claim-probe |
built |
| Install | panel shows the addresses of the deployed instances |
api/specs/001-deployments-and-builds/spec.md FR-004, FR-006; panel/specs/004-instances-overview/spec.md FR-001, FR-003 |
api/src/fediversity_api/tests/test_instances.py, panel/src/panel/tests/test_instance_list.py |
built |
| Configure | The form of the configuration comes from the options of the application modules | core/specs/002-configuration-schema/spec.md FR-001, FR-003, FR-008, FR-010; api/specs/002-schema-and-sources/spec.md FR-001, FR-006, FR-007; panel/specs/003-configuration-form/spec.md FR-001, FR-002 |
core/lib/json-schema-test.nix, core/lib/application-schemas-test.nix, api/src/fediversity_api/tests/test_http_schema.py, panel/src/panel/tests/test_schema.py |
built |
| Configure | A stored configuration follows the renames of options | core/specs/002-configuration-schema/spec.md FR-005; api/specs/002-schema-and-sources/spec.md FR-008, FR-009, FR-010; panel/specs/003-configuration-form/spec.md FR-003 |
core/lib/renames-for-source-test.nix, api/src/fediversity_api/tests/test_migrate.py, panel/checks/apps-panel |
built |
| Configure | The operator gives secrets that the configuration does not keep | core/specs/002-configuration-schema/spec.md FR-011; panel/specs/003-configuration-form/spec.md FR-009 |
core/lib/operator-secrets-test.nix, api/src/fediversity_api/tests/test_operator_secrets.py, panel/src/panel/tests/test_deployment_secrets.py |
built |
| Configure | A new deployment starts from the defaults that the modules declare | core/specs/002-configuration-schema/spec.md FR-003; panel/specs/003-configuration-form/spec.md FR-007 |
api/src/fediversity_api/tests/test_config_schema.py, panel/src/panel/tests/test_deployment_form.py |
built |
| Update | An update policy for each deployment | api/specs/001-deployments-and-builds/spec.md FR-014, FR-015, FR-016; panel/specs/002-deployment-lifecycle/spec.md FR-009, FR-010, FR-011, FR-012 |
api/src/fediversity_api/tests/test_update_policy.py, panel/src/panel/tests/test_update_policy.py, panel/checks/js-tests/update-policy.test.js |
built |
| Update | The source of the modules that a deployment builds from | api/specs/002-schema-and-sources/spec.md FR-004, FR-005, FR-011 |
api/src/fediversity_api/tests/test_source_allowlist.py, api/src/fediversity_api/tests/test_self_pin_warm.py |
built |
| User management | An operator account and its login in panel |
panel/specs/001-accounts-and-ownership/spec.md FR-001, FR-002 |
panel/src/panel/tests/test_login.py, panel/src/panel/tests/test_oidc.py |
built |
| User management | A deployment has one owner, and api authorizes each call for that owner |
panel/specs/001-accounts-and-ownership/spec.md FR-003, FR-004, FR-006; panel/specs/005-api-authorization/spec.md FR-001, FR-002, FR-003; api/specs/004-authorization/spec.md FR-001, FR-002 |
panel/src/panel/tests/test_account_api_access.py, api/src/fediversity_api/tests/test_authz_callback.py, api/src/fediversity_api/tests/test_authz_required.py |
built |
| User management | The removal of an operator account | panel/specs/001-accounts-and-ownership/spec.md FR-007 |
panel/src/panel/tests/test_account_removal.py |
built |
| User management | The hosting provider manages the operator accounts in the administration pages of panel |
-- | -- | no spec |
| User management | A directory of the users and groups of a deployment | core/specs/008-components/shared/lldap.md FR-001, FR-003, FR-006; core/specs/007-resources/ldap.md FR-002, FR-008, FR-009 |
core/checks/integrations/lldap-series, core/checks/integrations/lldap-provision, core/resources/ldap/provider-test.nix |
built |
| User management | Single sign-on of the applications | core/specs/008-components/shared/authelia.md FR-001, FR-006; core/specs/007-resources/oidc.md FR-006, FR-013; core/specs/008-components/operator/mastodon.md FR-004; core/specs/008-components/operator/peertube.md FR-004; core/specs/008-components/operator/pixelfed.md FR-006 |
core/checks/integrations/authelia-series, core/components/operator/mastodon/oidc-test.nix, core/resources/oidc/policy-test.nix |
built |
| User management | The groups of the directory give the roles in an application | -- | core/resources/ldap/provider-test.nix, core/components/operator/mastodon/test-script.py, core/components/operator/peertube/test-script.py, core/components/operator/pixelfed/test-script.py |
no spec |
| Switch | The backups of the databases and the media of the applications, and the operations that start a backup or a restore | api/specs/001-deployments-and-builds/spec.md FR-018; core/specs/007-resources/garage.md FR-020, FR-022, FR-023, FR-027; core/specs/008-components/operator/mastodon.md FR-013; core/specs/008-components/operator/peertube.md FR-012; core/specs/008-components/operator/pixelfed.md FR-014 |
core/checks/integrations/media-backup, core/checks/integrations/app-database-restore, core/checks/deployment/app-operations, api/src/fediversity_api/tests/test_operations.py |
built |
| Switch | The export of a deployment to a different hosting provider | -- | api/src/fediversity_api/tests/test_migration.py, api/checks/migration-kv-test.nix, core/checks/deployment/migration-quiesce, panel/checks/js-tests/migration-export.test.js |
no spec |
| Switch | The import of a deployment from a different hosting provider, and the restore of its data | core/specs/009-deployment-methods/tf-incus-operator.md FR-031, FR-032; panel/specs/002-deployment-lifecycle/spec.md FR-025; api/specs/004-authorization/spec.md FR-014 |
core/checks/integrations/migration-restore, api/src/fediversity_api/tests/test_migration_restore.py, api/src/fediversity_api/tests/test_import_check.py, panel/checks/js-tests/migration-import.test.js |
partial |
| Switch | The transfer of a domain to and from the registrar of the hosting provider | core/specs/010-effects/registrar-providers.md FR-011, FR-012, FR-028, FR-029, FR-031; core/specs/009-deployment-methods/tf-incus-operator.md FR-033; panel/specs/002-deployment-lifecycle/spec.md FR-020, FR-021 |
core/checks/deployment/registrar-transfer, api/src/fediversity_api/tests/test_domain_transfer_in.py, api/src/fediversity_api/tests/test_domain_take_out.py, api/src/fediversity_api/tests/test_domain_handover.py |
partial |
Code and docs
A path that is a directory contains the code or the test.
| Capability | Function | Code | Docs |
|---|---|---|---|
| Install | The catalog of the applications that an operator can select | core/applications-catalog.nix, api/src/fediversity_api/catalog.py, api/src/fediversity_api/routes/applications.py |
Add an application |
| Install | The operator submits a deployment, and a job builds and deploys it | panel/src/panel/views.py, api/src/fediversity_api/routes/deployments.py, api/src/fediversity_api/deployment.py, api/src/fediversity_api/_runner.py |
Make a deployment |
| Install | The components of the applications Mastodon, PeerTube and Pixelfed | core/components/operator/mastodon, core/components/operator/peertube, core/components/operator/pixelfed |
Add an application |
| Install | The domain of a deployment: the claim, the DNS records and the certificates | api/src/fediversity_api/domain_claims.py, api/src/fediversity_api/apex_claims.py, api/src/fediversity_api/routes/domains.py, core/resources/dns, core/resources/ssl |
-- |
| Install | panel shows the addresses of the deployed instances |
api/src/fediversity_api/instances.py, panel/src/panel/views.py, panel/src/panel/templates/instance_list.html |
-- |
| Configure | The form of the configuration comes from the options of the application modules | core/lib/json-schema.nix, core/lib/schema-for-source.nix, api/src/fediversity_api/schema.py, api/src/fediversity_api/routes/schema.py |
Operator configuration, Schema and migration sources |
| Configure | A stored configuration follows the renames of options | core/lib/renames-for-source.nix, api/src/fediversity_api/renames.py, api/src/fediversity_api/migrate.py |
Schema and migration sources |
| Configure | The operator gives secrets that the configuration does not keep | core/lib/operator-secrets.nix, api/src/fediversity_api/operator_secrets.py, panel/src/panel/secrets.py |
-- |
| Configure | A new deployment starts from the defaults that the modules declare | core/lib/json-schema.nix, core/lib/schema-for-source.nix, panel/src/panel/views.py |
-- |
| Update | An update policy for each deployment | api/src/fediversity_api/update_policy.py, api/src/fediversity_api/automatic_updates.py |
Update policy |
| Update | The source of the modules that a deployment builds from | api/src/fediversity_api/self_source.py, api/src/fediversity_api/source_allowlist.py |
Schema and migration sources |
| User management | An operator account and its login in panel |
panel/src/panel/oidc.py |
Architecture of panel |
| User management | A deployment has one owner, and api authorizes each call for that owner |
panel/src/panel/models.py, panel/src/panel/tokens.py, api/src/fediversity_api/authz.py, api/src/fediversity_api/callback.py |
Data model |
| User management | The removal of an operator account | panel/src/panel/models.py, panel/src/panel/admin.py |
-- |
| User management | The hosting provider manages the operator accounts in the administration pages of panel |
panel/src/panel/admin.py |
-- |
| User management | A directory of the users and groups of a deployment | core/components/shared/lldap, core/resources/ldap/lldap-provider.nix, core/resources/ldap/provision.nix |
Manage the users of your deployment, Manage users and groups |
| User management | Single sign-on of the applications | core/components/shared/authelia, core/resources/oidc/authelia-oidc-provider.nix |
-- |
| User management | The groups of the directory give the roles in an application | core/resources/ldap/application-role-groups.nix, core/resources/ldap/roles-consumer.nix, core/resources/ldap/publish-roles.nix |
-- |
| Switch | The backups of the databases and the media of the applications, and the operations that start a backup or a restore | core/components/shared/postgres-backup.nix, core/components/shared/operations/backup.nix, api/src/fediversity_api/operations.py, core/resources/garage/media-backup.nix, core/resources/garage/media-backup.sh, core/resources/garage/restic-s3.nix |
-- |
| Switch | The export of a deployment to a different hosting provider | api/src/fediversity_api/routes/migration.py, api/src/fediversity_api/migration.py, core/effects/migration/effect.nix, core/effects/migration/run.sh, core/components/shared/migration-quiesce.nix |
Export of a migration, Export a deployment for a migration, Switching and exit clauses |
| Switch | The import of a deployment from a different hosting provider, and the restore of its data | api/src/fediversity_api/routes/migration.py, api/src/fediversity_api/import_check.py, api/src/fediversity_api/migration_kv.py, core/effects/migration-restore/effect.nix, core/effects/migration-restore/run.sh |
Restore the apps of a migration |
| Switch | The transfer of a domain to and from the registrar of the hosting provider | core/effects/tf/registrars.nix, api/src/fediversity_api/registrar.py, api/src/fediversity_api/routes/domains.py |
-- |
Gaps
These functions are in the code, but the specifications do not fully cover them.
A row with the status no spec has no numbered requirement at all.
| Capability | Function | Gap |
|---|---|---|
| User management | The hosting provider manages the operator accounts in the administration pages of panel |
No file in panel/specs has a requirement for the administration pages. |
| User management | The groups of the directory give the roles in an application | core/specs/007-resources/ldap.md has no requirement for the roles. |
| Switch | The export of a deployment to a different hosting provider | The design is prose in api/docs/design/migrations.md. No spec file has a numbered requirement for the export. |
| Switch | The import of a deployment from a different hosting provider, and the restore of its data | No file in api/specs has a numbered requirement for the import procedure. |
Results of the grant
The grant agreement states the results of each capability in the section "Planned features", and two groups of milestones that help: "Front-end" and "Security".
The text is in the amendment agreement/Amendment - AMD-101136078-2-1.pdf in the meta repository of the project.
This table quotes each result and gives the functions of this page that cover it.
The connection between a result and a function is made by hand in core/docs/md/technical-design/traceability.nix.
The build stops when the table names a function that is not a row of this page.
no row means that no function of this page covers the result. It does not mean that the code does not have the result.
The milestones are not capabilities, thus they have no row.
One function covers only a part of its result: panel shows the addresses of the applications on a page, and the code sends no message to the operator when a deploy is done.
| Planned feature | Result that the grant states | Functions of this page |
|---|---|---|
| Install applications | "Operators will be able to easily select desired applications to install (i.e. deploy), ideally in bulk." | The catalog of the applications that an operator can select; The operator submits a deployment, and a job builds and deploys it |
| Install applications | "Selected applications will be deployed to the operator's own domain." | The domain of a deployment: the claim, the DNS records and the certificates |
| Install applications | "The operator will be notified how to access their applications, once these are ready to use." | panel shows the addresses of the deployed instances |
| Install applications | "Operators indicate to be satisfied with the implemented solution." | no row |
| User management | "Operators have access to an administrator account on their deployed applications." | The groups of the directory give the roles in an application |
| User management | "Operators can create users accounts for their deployed applications from a central place, e.g. using LDAP." | A directory of the users and groups of a deployment |
| User management | "Log-in experience unified across applications using single sign-on (SSO)." | Single sign-on of the applications |
| User management | "Operators indicate to be satisfied with the implemented solution." | no row |
| Switch hosting provider | "Operators can back up their application data." | The backups of the databases and the media of the applications, and the operations that start a backup or a restore |
| Switch hosting provider | "Operators can trivially move deployments to a different hosting provider." | The export of a deployment to a different hosting provider; The import of a deployment from a different hosting provider, and the restore of its data; The transfer of a domain to and from the registrar of the hosting provider |
| Switch hosting provider | "Migrations gracefully handle Fediversity version differences." | The import of a deployment from a different hosting provider, and the restore of its data; A stored configuration follows the renames of options |
| Update applications | "Operators can set when to update their applications" | An update policy for each deployment |
| Update applications | "Operators are guided through new options on updates." | no row (issue 3054) |
| Update applications | "Updating deployment options automated." | A stored configuration follows the renames of options |
| Configure applications | "Configuration uses a batteries-included approach." | A new deployment starts from the defaults that the modules declare |
| Configure applications | "Relevant configuration options are available for operators to tweak, presented using a user-friendly web interface, that further aid the user in case updates change the available options." | The form of the configuration comes from the options of the application modules; A stored configuration follows the renames of options |
| Front-end (milestone) | "API specification published" | no row |
| Front-end (milestone) | "API available" | no row |
| Front-end (milestone) | "UX prototype finished" | no row |
| Front-end (milestone) | "web host application implements API" | no row |
| Security (milestone) | "Credentials handled" | no row |
| Security (milestone) | "TLS certificates handled" | no row |
| Security (milestone) | "Hypervisor users segregated" | no row |
| Security (milestone) | "Inter-node access authenticated" | no row |
The specification files
This table lists each file in the three directories that has a minimum of one numbered requirement.
The column Planned markers counts the issues that the markers (planned, #<number>) of the requirements name.
The column Issues of the known deviations gives the issues that the section Known Deviations of the file names.
The last column gives the capabilities whose rows cite the file. A file with -- in that column covers a subject that is not one of the five capabilities, for example the data model or the rotation of secrets.
| Spec file | Title | Status | Requirements | Planned markers | Issues of the known deviations | Capabilities that cite it |
|---|---|---|---|---|---|---|
core/specs/001-data-model/spec.md |
Data model and extension seam | Implemented | 10 | 0 | -- | -- |
core/specs/002-configuration-schema/spec.md |
Configuration model and JSON Schema | Implemented | 13 | 0 | issue 2132 | Configure |
core/specs/003-applications-catalog/spec.md |
Applications catalog | Implemented | 5 | 0 | -- | Install |
core/specs/004-deployment-programs/spec.md |
Stateless deployment programs | Implemented | 9 | 0 | -- | Install |
core/specs/005-setups-and-wiring/spec.md |
Setups and wiring | Implemented | 14 | 0 | -- | -- |
core/specs/006-testers-and-docs/spec.md |
Test harness, checks and documentation | Implemented | 9 | 0 | -- | -- |
core/specs/007-resources/dns.md |
Resource dns |
Implemented | 8 | 0 | -- | -- |
core/specs/007-resources/firewall.md |
Resource firewall |
Implemented | 11 | 0 | -- | -- |
core/specs/007-resources/garage.md |
Resource garage |
Implemented | 38 | 0 | -- | Switch |
core/specs/007-resources/generateFiles.md |
Resource generateFiles |
Implemented | 12 | 0 | -- | -- |
core/specs/007-resources/ldap.md |
Resource ldap |
Implemented | 18 | 0 | -- | User management |
core/specs/007-resources/netbox.md |
Resource netbox |
Implemented | 9 | 0 | -- | -- |
core/specs/007-resources/network.md |
Resource network |
Implemented | 11 | 0 | issue 2131 | -- |
core/specs/007-resources/nixos-module.md |
Resource nixos-module |
Implemented | 4 | 0 | -- | -- |
core/specs/007-resources/oidc.md |
Resource oidc |
Implemented | 17 | 0 | -- | User management |
core/specs/007-resources/otel.md |
Resource otel |
Implemented | 6 | 0 | -- | -- |
core/specs/007-resources/pod-roles.md |
Resource pod-roles |
Implemented | 15 | 0 | -- | -- |
core/specs/007-resources/postgresql.md |
Resource postgresql |
Implemented | 19 | 0 | -- | -- |
core/specs/007-resources/redis.md |
Resource redis |
Implemented | 11 | 0 | -- | -- |
core/specs/007-resources/secrets.md |
Resource secrets |
Implemented | 9 | 0 | -- | -- |
core/specs/007-resources/smtp.md |
Resource smtp |
Implemented | 14 | 0 | -- | -- |
core/specs/007-resources/spec.md |
Resource family | Implemented | 13 | 0 | -- | -- |
core/specs/007-resources/spiffe.md |
Resource spiffe |
Implemented | 10 | 0 | -- | -- |
core/specs/007-resources/ssl.md |
Resource ssl |
Implemented | 10 | 0 | -- | Install |
core/specs/008-components/dev/forgejo-runner.md |
Component forgejo-runner |
Implemented | 12 | 0 | -- | -- |
core/specs/008-components/dev/forgejo.md |
Component forgejo |
Implemented | 17 | 0 | issue 29 | -- |
core/specs/008-components/dev/git-pages.md |
Component git-pages |
Implemented | 8 | 0 | issue 2126 | -- |
core/specs/008-components/hosting-provider/bind.md |
Component bind |
Implemented | 13 | 0 | -- | -- |
core/specs/008-components/hosting-provider/garage.md |
Component garage |
Implemented | 4 | 0 | -- | -- |
core/specs/008-components/hosting-provider/incus.md |
Component incus |
Implemented | 26 | 0 | -- | -- |
core/specs/008-components/hosting-provider/netbox.md |
Component netbox |
Implemented | 12 | 0 | -- | -- |
core/specs/008-components/hosting-provider/openbao.md |
Component openbao |
Implemented | 38 | 0 | issue 2270 | -- |
core/specs/008-components/hosting-provider/overview.md |
Component overview |
Implemented | 3 | 0 | -- | -- |
core/specs/008-components/hosting-provider/postgresql.md |
Component postgresql |
Implemented | 5 | 0 | -- | -- |
core/specs/008-components/hosting-provider/spire.md |
Component spire |
Implemented | 20 | 0 | -- | -- |
core/specs/008-components/hosting-provider/telemetry.md |
Components telemetry and otel-collector |
Implemented | 26 | 0 | -- | -- |
core/specs/008-components/hosting-provider/terraform-backend.md |
Component terraform-backend |
Implemented | 15 | 0 | -- | -- |
core/specs/008-components/hosting-provider/windmill.md |
Component windmill |
Implemented | 15 | 0 | -- | -- |
core/specs/008-components/operator/grafana.md |
Component grafana |
Implemented | 7 | 0 | -- | -- |
core/specs/008-components/operator/mastodon.md |
Component mastodon |
Implemented | 16 | 0 | -- | Install, User management, Switch |
core/specs/008-components/operator/peertube.md |
Component peertube |
Implemented | 13 | 0 | -- | Install, User management, Switch |
core/specs/008-components/operator/pixelfed.md |
Component pixelfed |
Implemented | 18 | 0 | -- | Install, User management, Switch |
core/specs/008-components/shared/authelia.md |
Component authelia |
Implemented | 12 | 0 | -- | User management |
core/specs/008-components/shared/lldap.md |
Component lldap |
Implemented | 10 | 0 | -- | User management |
core/specs/008-components/shared/smtp.md |
Component smtp |
Implemented | 4 | 0 | -- | -- |
core/specs/008-components/shared/valkey.md |
Component valkey |
Implemented | 9 | 0 | -- | -- |
core/specs/008-components/spec.md |
Components | Implemented | 21 | 0 | -- | -- |
core/specs/009-deployment-methods/k8s.md |
Deployment method k8s |
Implemented | 37 | 0 | -- | -- |
core/specs/009-deployment-methods/spec.md |
Method family deployment-methods |
Implemented | 26 | 0 | -- | -- |
core/specs/009-deployment-methods/ssh.md |
Deployment method ssh |
Implemented | 17 | 0 | -- | -- |
core/specs/009-deployment-methods/tf-incus-operator.md |
Deployment method tf-incus-operator |
Implemented | 37 | 0 | -- | Install, Switch |
core/specs/009-deployment-methods/tf-incus.md |
Deployment method tf-incus |
Implemented | 23 | 0 | -- | -- |
core/specs/009-deployment-methods/tf.md |
Deployment method tf |
Implemented | 11 | 0 | issue 2220 | -- |
core/specs/010-effects/image.md |
Effect tf-incus-image |
Implemented | 15 | 0 | issue 2131 | -- |
core/specs/010-effects/incus-bootstrap.md |
Effect dev-tf-incus-bootstrap |
Implemented | 9 | 0 | -- | -- |
core/specs/010-effects/netbox-seed.md |
Effect netbox-seed |
Implemented | 12 | 0 | -- | -- |
core/specs/010-effects/octodns.md |
Effect octodns-zone |
Implemented | 16 | 0 | -- | -- |
core/specs/010-effects/registrar-providers.md |
Registrar providers of the operator path | Partially implemented | 38 | 0 | issue 1908 | Switch |
core/specs/010-effects/registrars.md |
Effects registrar-* |
Implemented | 15 | 0 | issue 2131 | -- |
core/specs/010-effects/spec.md |
Effect family effects |
Implemented | 14 | 0 | issue 2129 | -- |
core/specs/011-secret-rotation/spec.md |
Secret rotation | Partially implemented | 12 | 0 | -- | -- |
core/specs/012-license-report/spec.md |
License report | Implemented, with the decisions in License decisions (core/docs/md/compliance/license-decisions.md) |
11 | 0 | -- | -- |
api/specs/001-deployments-and-builds/spec.md |
Deployments and builds | Implemented | 19 | 0 | -- | Install, Update, Switch |
api/specs/002-schema-and-sources/spec.md |
Schema, migrations and module sources | Implemented | 11 | 0 | -- | Configure, Update |
api/specs/003-application-catalog/spec.md |
Application catalog | Implemented | 4 | 0 | -- | Install |
api/specs/004-authorization/spec.md |
Authorization of callers | Implemented | 14 | 0 | -- | User management, Switch |
api/specs/005-job-execution/spec.md |
Execution of deployment jobs | Implemented | 5 | 0 | -- | Install |
api/specs/006-service-deployment/spec.md |
Deployment of the api service | Implemented | 5 | 0 | issue 1940 | -- |
panel/specs/001-accounts-and-ownership/spec.md |
Accounts and ownership | Implemented | 8 | 0 | -- | User management |
panel/specs/002-deployment-lifecycle/spec.md |
Deployment lifecycle | Implemented | 26 | 0 | -- | Install, Update, Switch |
panel/specs/003-configuration-form/spec.md |
Configuration form | Implemented | 9 | 0 | -- | Configure |
panel/specs/004-instances-overview/spec.md |
Instances overview | Implemented | 3 | 0 | -- | Install |
panel/specs/005-api-authorization/spec.md |
Authorization of calls to api | Implemented | 7 | 0 | -- | User management |
panel/specs/006-service-deployment/spec.md |
Service deployment | Implemented | 7 | 0 | -- | -- |