Skip to content

angee.operator.daemon

The local operator daemon as seen from Django: endpoint + token minting.

OperatorDaemonError

python
class OperatorDaemonError(RuntimeError)

An operator daemon REST call failed.

OperatorDaemonNotFound

python
class OperatorDaemonNotFound(OperatorDaemonError)

The daemon reported that the requested resource is already absent.

RemoteFile

python
@dataclass(frozen=True, slots=True)
class RemoteFile()

One workspace file read through the operator file tools.

etag is the daemon's content hash for optimistic concurrency — read it, edit, then write it back so a concurrent edit fails the write rather than silently clobbering it.

OperatorDaemon

python
@dataclass(frozen=True, slots=True)
class OperatorDaemon()

The operator daemon bridge resolved from settings.

endpoint is the browser-visible GraphQL URL handed to an authorized actor. server_base and admin_bearer are server-side only — the admin bearer never reaches the browser; it is the credential used to mint a short-lived, scoped per-actor token via :meth:mint_token, and to drive the daemon's lifecycle server-side over its REST API (:meth:set_secret, :meth:create_workspace, :meth:create_service, :meth:destroy_workspace) when Django provisions on a user's behalf.

from_settings

python
@classmethod
def from_settings(cls) -> OperatorDaemon

Resolve the daemon bridge from Django settings and the environment.

mint_token

python
def mint_token(actor: str) -> str | None

Mint a short-lived, scoped connection token for actor, or None.

Calls the daemon's POST /tokens/mint with the admin bearer (server-side only) and returns the minted aud=operator token the browser presents — so a leaked browser token expires and never carries root access. Returns None (hiding the connection) when the daemon URL or bearer is unset, or the call fails. An empty scope is full access until the daemon enforces a capability map.

introspect_sdl

python
def introspect_sdl() -> str | None

Return the daemon's GraphQL SDL by introspecting it, or None.

The daemon owns its schema; the console derives its types from it instead of hand-maintaining them. This reuses the addon's authenticated connection (the admin bearer over the absolute GraphQL URL) to fetch a fresh contract — manage.py operator_schema writes it where frontend codegen reads it. Returns None when the daemon is unset or unreachable.

set_secret

python
def set_secret(name: str, value: str) -> None

Set a secret value in the operator store (POST /secrets/{name}).

resolve_template_ref

python
def resolve_template_ref(*, name: str, kind: str) -> str | None

Return the daemon's template ref for a template name + kind.

The daemon owns the ref format and emits it in its own GET /templates listing (its path there is an absolute filesystem path, not the template ref), so match the manifest name (and kind) — both sides parse it from the template's _angee block — and return the daemon's ref.

create_workspace

python
def create_workspace(*, template: str, inputs: dict[str, str]) -> str

Render a workspace from a daemon template ref; return the instance name.

create_service

python
def create_service(*,
                   template: str,
                   workspace: str,
                   inputs: dict[str, str],
                   start: bool = True) -> str

Render a service into the stack mounting workspace; return the instance name.

destroy_workspace

python
def destroy_workspace(name: str) -> None

Destroy a workspace and its files (POST /workspaces/{name}/destroy).

destroy_service

python
def destroy_service(name: str) -> None

Destroy (stop + remove) a stack service (POST /services/{name}/destroy).

A service is a stack entry distinct from the workspace it mounts, so it has its own lifecycle: tearing down the workspace leaves the service behind (a later create_service then 409s), and a secret change needs the service recreateddestroy_service then create_service over the same workspace — to re-resolve its ${secret.<name>} env, not just restarted.

service_endpoint

python
def service_endpoint(name: str) -> dict[str, Any]

Return a routed service's reachable endpoint (GET /services/{name}/endpoint).

The daemon owns routing: for a service fronted by the central Caddy it returns {"routed": true, "url": "wss://<service>.<domain>/", …} — a browser-reachable WebSocket URL carrying no token. The browser appends an operator-minted route token (:meth:mint_route_token) as a query parameter, which the central Caddy forward-auths against the daemon.

mint_route_token

python
def mint_route_token(actor: str,
                     service: str,
                     ttl: str = _DEFAULT_TTL) -> dict[str, Any]

Mint a route token scoping actor to a routed service (POST /tokens/route).

Distinct from :meth:mint_token (the aud=operator GraphQL token): this is the aud=svc:<service> token the central Caddy forward-auths on a routed service's upgrade, so a browser that holds it can open the service WebSocket and nothing else. Short-lived and per-actor; the daemon caps the TTL at 24h.

read_file

python
def read_file(source: str, path: str) -> RemoteFile

Read path under a stack source (GET /files?source=&path=).

write_file

python
def write_file(source: str, path: str, content: str, etag: str = "") -> str

Write path under a stack source (PUT /files?source=&path=); return the new etag.

stack_build

python
def stack_build() -> str

Trigger a stack rebuild + restart (POST /stack/build); return a status marker.

Released under the AGPL-3.0 License.