API
The Fediversity API, under api/, is the HTTP and pub/sub backend that
front-ends (such as the
panel)
use to manage deployments. It wraps the stateless deployment programs of the
data model with queued execution, progress reporting, and authorization.
Where it sits
The API sits between front-ends and the deployment machinery:
- Front-ends submit an operator configuration over HTTP. The API validates it, enqueues a build, and runs the deployment through the effects backends (subprocess by default, or Windmill for job tracking).
- It streams build progress and deployment-state changes back over Centrifugo
WebSocket channels (
fediversity.builds.<id>andfediversity.deployments.<id>), rather than making clients poll. - It maintains a small sqlite state store of deployments and build history.
Deployment identifiers are opaque to the API, formatted {username}/{pk}; the
panel
maps them to its Django User and Deployment models.
Wire surface
The API's contract is split across two standards-based, generated artifacts, so the documentation never drifts from the code:
- HTTP RPC (submit, query, cancel, delete, schema, build list, logs) is
described by an OpenAPI 3.1 spec generated from the FastAPI app. Browse it as
Swagger UI, or build the spec
with
nix build ./api#packages.x86_64-linux.openapi, or the interactive Swagger UI bundle withnix build ./api#packages.x86_64-linux.swagger-uiand openresult/index.html. - Centrifugo pub/sub events (build progress, deployment-state changes) are
described by an AsyncAPI 3.0 spec generated from the pydantic event models.
Browse the rendered AsyncAPI docs,
or build the spec with
nix build ./api#packages.x86_64-linux.asyncapi-spec, or the rendered docs withnix build ./api#packages.x86_64-linux.asyncapi-docs.
CI validates both specs (api-openapi-validate, api-asyncapi-validate).
Both specs name a real host
The OpenAPI spec carries a servers block, so Swagger UI offers a host
dropdown rather than resolving calls relative to whichever static host is
serving the page. It lists the production deployment and a local
api-http-stack on http://127.0.0.1:8088. Try it out therefore works
against the production API for the endpoints that need no credentials --
GET /schema is the useful one -- while anything else answers 403 without a
bearer token, which the dropdown cannot supply for you. The production api/
allows the Swagger host as a CORS origin for exactly this, and for nothing else:
it is deliberately not on the allowlist of origins api/ may call back to check
authorization. A running instance overrides servers with its own
domain, so /openapi.json
fetched from a deployment always advertises that deployment.
The AsyncAPI spec's host server variable likewise defaults to the
deployment's Centrifugo name instead of a bare {host} placeholder. That name
resolves inside the deployment only -- it is not published in the public
zone -- and the rendered AsyncAPI page is static output with no try-it-out
button, so this half is informational: it tells you what to connect to from a
browser that is already talking to the panel.
Design and operation
The conceptual design -- why the wire surface is split, the build-queue
semantics, the backend-selection criteria and the comparison that led to
choosing Windmill, the authorization callback model, and the error-code mapping
-- is written up in api/specification.md.
Running the API locally (the Terraform state backend, the HTTP stack, optional
Windmill, the required incus setup, and the environment variables that
configure it) is an operational concern maintained alongside the code: see
api/README.md.
API tests run with nix build ./api#checks.x86_64-linux.api-python-tests;
end-to-end deployment tests with nix build ./api#checks.x86_64-linux.apps-api.
Options
The services.fediversity-api NixOS options below configure an API deployment.
services.fediversity-api.enable
Whether to enable Fediversity API HTTP service.
Type: boolean
Default:
false
Example:
true
Declared by:
services.fediversity-api.authz.callbackUrl
HTTP endpoint called per request for dynamic authorization. The
api/ POSTs {procedure, scope, headers} and expects
{allow: bool, sub?: string}. Empty disables the callback and
allows every request – dev mode only.
Type: string
Default:
""
Declared by:
services.fediversity-api.centrifugo.apiKeyFile
Path to a file containing Centrifugo’s HTTP API admin key. The file
must be readable by the fediversity-api service user.
Type: string
Default:
""
Declared by:
services.fediversity-api.centrifugo.apiUrl
Base HTTP URL of Centrifugo’s /api/* endpoint (e.g.
http://panel:8090/api). Empty disables Centrifugo publishes
(events are dropped).
Type: string
Default:
""
Declared by:
services.fediversity-api.deployment.attribute
The attribute pointing to the deployment’s dev shell
(devShells.<system>.<ATTRIBUTE>).
Type: string
Default:
"operator-tf-incus-operator-all"
Declared by:
services.fediversity-api.deployment.destroyExecutable
The executable for destroy operations, as exposed inside the shell
named attribute.
Type: string
Default:
"tf-destroy.sh"
Declared by:
services.fediversity-api.deployment.env
The deploy-env value-map forwarded verbatim into the render-eval
subprocess. Its keys are the core-declared deploy-env key-set
(core.lib.deployEnvKeys), which is exactly what core’s render eval
consumes (SSH_PRIVATE_KEY_FILE, PUBLISHED_HOSTS/INTERNAL_HOSTS,
ENVIRONMENTS, TF_HTTP_ADDRESS/BOOTSTRAP, NETBOX_*, …). api/
forwards these without interpreting them, so it names no effect var:
a new effect adds its keys to its envKeys and this map grows with
no api/ edit. The deployer sets whichever values apply.
Type: attribute set of string
Default:
{ }
Declared by:
services.fediversity-api.deployment.executable
The executable to trigger as exposed inside the shell named
attribute.
Type: string
Default:
"tf-run.sh"
Declared by:
services.fediversity-api.deployment.moduleType
The type of the option module to expose to the user.
Type: optionType
Declared by:
services.fediversity-api.deployment.nixpkgs
The nixpkgs instance for NIX_PATH.
Type: string
Default:
"/nix/store/9pzxgnnqcis3xifbf4ynawzyk2aihab9-source"
Declared by:
services.fediversity-api.deployment.root_path
The path to the flake containing the deployment.
Type: absolute path
Default:
/nix/store/48005vlhqahcd35gsap5l2bwcj7xqf5l-source
Declared by:
services.fediversity-api.deployment.selfModuleSource
A fetchTree-compatible pin of api/'s own source revision,
surfaced to the service as SELF_MODULE_SOURCE. When a deployment is
submitted with no module_source, api/ freezes it to this pin rather
than storing a bare null, so the “old” schema for the change view
stays the one actually deployed under instead of floating to whatever
api/ is later rebuilt against.
The revision is a property of the deploy, not the source tree –
recording it in-tree would be self-referential, since writing the
revision changes the tree and thus the revision – so it is threaded
in here rather than baked into the package. The deployer must set it
to the built revision, including a narHash so the fetch is
hash-pinned; see api/specification.md. null keeps the floating
behaviour, acceptable for a dirty local build whose working tree is
not reproducible via fetchTree anyway.
Type: null or (attribute set of anything)
Default:
null
Example:
{
narHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
rev = "0000000000000000000000000000000000000000";
type = "git";
url = "https://git.fediversity.eu/fediversity/fediversity";
}
Declared by:
services.fediversity-api.docsOrigins
Extra browser origins allowed to call api/ via CORS, without being
trusted in the other direction. Deliberately not folded into
frontendOrigins: that list doubles as the destroy-reconcile callback
allowlist, and a static documentation host must never become a legal
authorization oracle.
The use is a published Swagger UI bundle whose “Try it out” button
issues browser calls to this api/. That is safe because CORS restricts
browsers only – anyone holding a token could always call from outside
one – and because allow_credentials is off, so a browser attaches no
ambient cookies; api/ does no bearer validation of its own and answers
a tokenless call with 403. The one endpoint a docs reader can exercise
without a token is GET /schema, which is unauthenticated anyway.
Type: list of string
Default:
[ ]
Declared by:
services.fediversity-api.domain
api/'s browser-facing FQDN. When set, nginx terminates TLS on this name
and proxies to the loopback-bound FastAPI server, giving api/ a
browser-reachable origin: the panel’s deployment form fetches its schema
and rename table straight from api/ (GET /schema,
GET /schema/migrations), and the Deploy/Delete buttons call it
directly too. Empty leaves api/ loopback-only – no vhost, no ingress.
The certificate is not configured here: forceSSL is set and the ssl
resource writes sslCertificate/sslCertificateKey (and enableACME
for a publicly-issued leaf) onto this vhost from the application’s
tls.ssl.domains request, as it does for the panel. A standalone user
of this module setting domain must supply that TLS material itself.
Type: string
Default:
""
Declared by:
services.fediversity-api.frontendOrigins
Browser origins (scheme + host + optional port) allowed to call api/ directly via CORS. The front-end lives on a different registrable domain than api/ (white-labelling), so cross-origin requests need an explicit allowlist. Empty disables CORS (no browser-direct access).
This list doubles as the allowlist for the destroy reconcile callback:
the browser supplies a callback URL on the DELETE request and api/ only
POSTs it back, on destroy-success, when its origin is in this list. The
set of front-ends api/ may call back is exactly the set trusted to call
in, so it is one list. See api/specification.md (Two-store model and deletion).
Type: list of string
Default:
[ ]
Declared by:
services.fediversity-api.hostingConfig
The hosting provider’s curation of the deployment form, as data: the
HOSTING_CONFIG model documented in api/specification.md, with its
environments and effect-defaults sections. null leaves the form
uncurated, offering every operator-facing field.
Taken as an attrset rather than a path so the values stay
module-composable and no relative import has to survive a copy into
the store; it is rendered to one JSON file here.
That one file reaches all three consumers that must agree – the
build-time schema prune (nix/package.nix’s hostingLeaves), the
per-module_source schema re-eval (HOSTING_CONFIG in the unit
environment), and the deploy eval (HOSTING_CONFIG in deploy_env) –
so a leaf pruned from the form is exactly a leaf baked as a value, and
the operator can neither see nor override it.
Type: null or (attribute set of anything)
Default:
null
Example:
{
effect-defaults = {
tf-incus-operator-hosts = {
host = "10.0.100.1";
port = 8443;
};
};
}
Declared by:
services.fediversity-api.http.listenAddr
Bind address for the FastAPI HTTP server. Loopback by default even
when domain publishes api/ to browsers: nginx fronts it on the same
node, so the FastAPI server itself stays off the network.
Type: string
Default:
"127.0.0.1"
Declared by:
services.fediversity-api.http.listenPort
Bind port for the FastAPI HTTP server. 8081 collides with
terraform-backend’s metrics endpoint when both run on the same
host; pick something further out by default.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
8088
Declared by:
services.fediversity-api.pinEffect
Collapse the served deployment-method union to this single member: the
served schema loses deployment-method entirely and carries that
effect’s settings at its root, so the form has no method picker and
renders those settings as ordinary fields. Names any union member – a
deployment method or a standalone effect. The module-option counterpart
of the PIN_EFFECT build variable documented in api/README.md; an
undeclared name fails the build, as does a setting whose name a
top-level option already takes.
null bakes the whole union, the form where the operator picks the
method.
Type: null or string
Default:
null
Example:
"tf-incus-operator-hosts"
Declared by:
services.fediversity-api.restart
systemd restart behavior
Type: one of “no”, “on-success”, “on-failure”, “on-abnormal”, “on-abort”, “always”
Default:
"always"
Declared by:
services.fediversity-api.trustedSourceUrls
Exact module_source repo URLs api/ will fetch and deploy. A submitted
source whose url equals one of these, after tolerating a trailing
.git// and any ?ref=/#, passes; anything else – including a
sibling repo or a /../ traversal that merely shares a prefix – is
rejected with HTTP 400 before fetchTree. This is the hosting
provider’s trust lever, not the Fediversity project gatekeeping forks.
The default is our repo only. An explicit empty list [ ] allows any
source. A null module_source (the built-in default module) is
always allowed; it is not a caller-named fork.
Type: list of string
Default:
[
"https://git.fediversity.eu/fediversity/fediversity"
]
Declared by:
services.fediversity-api.windmill.baseUrl
Base HTTP URL of the Windmill instance.
Type: string
Default:
""
Declared by:
services.fediversity-api.windmill.workspace
Windmill workspace identifier.
Type: string
Default:
"fediversity"
Declared by: