main
main
¶
The supervisor coroutine — run_supervisor(container_id).
One asyncio loop composes:
VaultProxy— vault HTTP/WS proxy, transport picked from the sidecar (socket→UnixBind,tcp→TcpBind).- SSH signer (
start_ssh_signer) — token-gated SSH-agent the container reaches over the same transport. VerdictServer— varlink helper that execsterok-shield allow|deny. Lives in its own per- container socket.ClearanceHub— varlink hub the shield reader (and operator UIs) subscribe to. Wired to the local verdict server above.- Desktop notifier — an
EventSubscriberthat turnsconnection_blockedevents into D-Bus popups viacreate_notifier.
The supervisor awaits podman wait <container_id>; when the
container exits it tears the bundle down in reverse order and
returns 0.
Hidden from main user help; invoked by the OCI hook chain only.
SidecarConfig(container_name, ipc_mode, db_path, runtime_dir, scope_id=None, project_id='', task_id='', tcp_port=None, ssh_signer_port=None, gate_port=None, gate_base_path=None, gate_token=None, dossier_path=None)
dataclass
¶
Per-container config the supervisor reads from the sidecar JSON.
Written by terok-sandbox prepare (and equivalents in
terok-executor / terok) at container-creation time. Keyed by
container name initially; promoted to a container-ID-keyed
filename on first hook fire (see
terok_sandbox.resources.hooks._supervisor_state.load_sidecar).
container_name
instance-attribute
¶
ipc_mode
instance-attribute
¶
db_path
instance-attribute
¶
runtime_dir
instance-attribute
¶
/run/user/<host_uid>/terok/sandbox — pinned by the launch path
because the supervisor cannot re-derive it from inside crun's
rootless user namespace (its os.getuid() is 0 there, which
misroutes generic resolvers to the root-only /run/terok).
scope_id = None
class-attribute
instance-attribute
¶
project_id = ''
class-attribute
instance-attribute
¶
task_id = ''
class-attribute
instance-attribute
¶
tcp_port = None
class-attribute
instance-attribute
¶
Per-container TCP port for the vault proxy in TCP mode.
None in socket mode (the path is derived from the
container ID, not carried here).
ssh_signer_port = None
class-attribute
instance-attribute
¶
Per-container TCP port for the SSH signer in TCP mode.
None in socket mode.
gate_port = None
class-attribute
instance-attribute
¶
Per-container TCP port for the git gate in TCP mode.
None in socket mode.
gate_base_path = None
class-attribute
instance-attribute
¶
Directory holding the shared per-project bare mirrors
(<gate_base_path>/<project_id>.git). None when the gate is
not wired for this container.
gate_token = None
class-attribute
instance-attribute
¶
The single token the gate validates. Travels only via the
sidecar; None when the gate is not wired.
dossier_path = None
class-attribute
instance-attribute
¶
SupervisorPaths(container_id, container_runtime_dir, vault_socket, ssh_signer_socket, gate_socket, clearance_socket, events_socket, verdict_socket, control_socket, log_path)
dataclass
¶
Resolved per-container socket / log / pid locations.
Computed once at supervisor startup from the container ID and the runtime/state dirs the sidecar config doesn't carry directly.
container_id
instance-attribute
¶
container_runtime_dir
instance-attribute
¶
Per-container directory holding vault.sock and
ssh-agent.sock. Keyed on container_name (which the
launch path knows before podman run so it can pre-create
the dir and bind-mount it as /run/terok/ inside the
container). Different containers get different host dirs;
the in-container view of these sockets is always /run/terok/.
vault_socket
instance-attribute
¶
ssh_signer_socket
instance-attribute
¶
gate_socket
instance-attribute
¶
Per-container git-gate Unix socket inside container_runtime_dir
(= the in-container /run/terok). Used only in socket mode; in
TCP mode the gate binds a loopback port instead.
clearance_socket
instance-attribute
¶
events_socket
instance-attribute
¶
Per-container ingester socket the shield reader and shield
up/down push raw line-JSON to. Distinct from
clearance_socket (the varlink subscriber socket operator UIs
glob): the reader speaks line-JSON, not varlink, so the produce and
subscribe roles need separate sockets.
verdict_socket
instance-attribute
¶
control_socket
instance-attribute
¶
log_path
instance-attribute
¶
for_container(container_id, container_name, sidecar_path, runtime_dir)
classmethod
¶
Build the per-container path bundle.
Both anchors come from the launch path — neither is re-resolved inside the supervisor:
- runtime_dir (
/run/user/<host_uid>/terok/sandbox) for per-container sockets; carried in the sidecar because the rootless user namespace makes genericis_root-based resolvers (terok_util.namespace_runtime_dir) misroute to/run/terok. - sidecar_path's grandparent for the persistent log file;
honours whatever
paths.rootresolved to when the launch path wrote the sidecar.
Sockets carry the 12-char short container ID (podman's
display convention) rather than the full UUID — AF_UNIX's
sun_path is 108 bytes including the null terminator, and
<terok-runtime>/clearance/<64-char-uuid>.sock lands at or
past that limit. Twelve characters of hex give 48 bits of
entropy, well past the no-collisions-within-one-host bar.
Logs keep the full UUID because they live on the filesystem
with no AF_UNIX limit and the full UUID is easier to grep.
Clearance / verdict / control sockets live at the
cross-package <terok>/ runtime root (parent of the
sandbox-namespaced runtime_dir) because they're owned by
terok-clearance semantically and consumed by every package
that subscribes (terok-shield's NFLOG reader, terok-clearance
TUI, …). Sandbox-specific sockets (vault, ssh-agent) live in
a per-container runtime_dir/run/<short_id>/ directory the
launch path bind-mounts at /run/terok/ inside the
container — keeping every container's sockets distinct on
the host so concurrent containers don't collide.
Source code in src/terok_sandbox/supervisor/main.py
load_sidecar(sidecar_path)
¶
Read and parse the sidecar JSON at sidecar_path.
The OCI hook pinned this exact path via the
terok.sandbox.sidecar annotation, so the supervisor never
guesses — it opens the named file directly. Returns None on
any I/O / schema failure; run_supervisor surfaces that as
exit-code 2.
Source code in src/terok_sandbox/supervisor/main.py
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
run_supervisor(container_id, sidecar_path)
async
¶
Compose + run the per-container service bundle.
Lifecycle:
- Load the sidecar JSON from sidecar_path; bail with exit code 2 on parse / missing.
- Bring the
_Servicesbundle up in dependency order; a startup failure unwinds anything already started and returns exit code 3. - Install SIGTERM / SIGINT handlers that race with
podman waitso a host-sideterok-sandbox supervisorinvocation can be stopped cleanly with Ctrl-C. - Await
podman wait <container_id>. When it returns, tear the bundle down in reverse and return 0.
The function is the sole supervisor entry point; the CLI verb
terok-sandbox supervisor invokes it via asyncio.run.
Source code in src/terok_sandbox/supervisor/main.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | |