angee.operator.daemon
The local operator daemon as seen from Django: endpoint + token minting.
OperatorDaemonError
class OperatorDaemonError(RuntimeError)An operator daemon REST call failed.
OperatorDaemonNotFound
class OperatorDaemonNotFound(OperatorDaemonError)The daemon reported that the requested resource is already absent.
RemoteFile
@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
@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
@classmethod
def from_settings(cls) -> OperatorDaemonResolve the daemon bridge from Django settings and the environment.
mint_token
def mint_token(actor: str) -> str | NoneMint 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
def introspect_sdl() -> str | NoneReturn 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
def set_secret(name: str, value: str) -> NoneSet a secret value in the operator store (POST /secrets/{name}).
resolve_template_ref
def resolve_template_ref(*, name: str, kind: str) -> str | NoneReturn 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
def create_workspace(*, template: str, inputs: dict[str, str]) -> strRender a workspace from a daemon template ref; return the instance name.
create_service
def create_service(*,
template: str,
workspace: str,
inputs: dict[str, str],
start: bool = True) -> strRender a service into the stack mounting workspace; return the instance name.
destroy_workspace
def destroy_workspace(name: str) -> NoneDestroy a workspace and its files (POST /workspaces/{name}/destroy).
destroy_service
def destroy_service(name: str) -> NoneDestroy (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 recreated — destroy_service then create_service over the same workspace — to re-resolve its ${secret.<name>} env, not just restarted.
service_endpoint
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
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
def read_file(source: str, path: str) -> RemoteFileRead path under a stack source (GET /files?source=&path=).
write_file
def write_file(source: str, path: str, content: str, etag: str = "") -> strWrite path under a stack source (PUT /files?source=&path=); return the new etag.
stack_build
def stack_build() -> strTrigger a stack rebuild + restart (POST /stack/build); return a status marker.