Skip to content

main

main

The parent supervisor — run_supervisor(container_id, sidecar_path).

Once a single asyncio loop composing every service, the supervisor is now a supervisor of processes: it launch_childs one hardened child per service and owns only their lifecycle. Each child (run_child) hardens itself, rebuilds its one service from the sidecar, and runs it until the parent signals stop — so a bug in the desktop notifier can no longer touch the address space that holds the vault's session key.

The five children, in launch order:

The parent awaits podman wait <container_id>; when the container exits (or every child dies first) it terminates the children in reverse launch order and returns.

Hidden from main user help; invoked by the OCI hook chain only.

run_supervisor(container_id, sidecar_path, container_pid=None) async

Launch, monitor, and tear down the per-container child processes.

container_pid is the container's init host-PID, handed down from the createRuntime OCI hook. When present it is the authoritative container-death signal: the supervisor watches it directly (via pidfd), so it tears down the instant the container exits even where nested podman wait is blind (crun handing the hook the container's env). None (older hook, or a runtime that didn't supply it) falls back to the podman wait watch alone.

Lifecycle:

  1. Load the sidecar JSON from sidecar_path; bail with exit code 2 on parse / missing.
  2. Launch one hardened child per service (skipping the git gate when the sidecar didn't wire it). A child that never spawns is logged and skipped; when no child spawns, exit code 3 hands the wrapper its retry.
  3. Install SIGTERM / SIGINT handlers so a host-side terok-sandbox supervisor invocation stops cleanly with Ctrl-C.
  4. Await podman wait <container_id> racing the children's own liveness and the stop signal. On any of them, terminate the children in reverse launch order and return.

The function is the sole parent entry point; the CLI verb terok-sandbox supervisor invokes it via asyncio.run. The per-service work now lives in run_child, one process each.

Source code in src/terok_sandbox/supervisor/main.py
async def run_supervisor(
    container_id: str, sidecar_path: Path, container_pid: int | None = None
) -> int:
    """Launch, monitor, and tear down the per-container child processes.

    *container_pid* is the container's init host-PID, handed down from the
    ``createRuntime`` OCI hook.  When present it is the **authoritative**
    container-death signal: the supervisor watches it directly (via
    ``pidfd``), so it tears down the instant the container exits even where
    nested ``podman wait`` is blind (crun handing the hook the container's
    env).  ``None`` (older hook, or a runtime that didn't supply it) falls
    back to the ``podman wait`` watch alone.

    Lifecycle:

    1. Load the sidecar JSON from *sidecar_path*; bail with exit code 2
       on parse / missing.
    2. Launch one hardened child per service (skipping the git gate when
       the sidecar didn't wire it).  A child that never spawns is logged
       and skipped; when *no* child spawns, exit code 3 hands the wrapper
       its retry.
    3. Install SIGTERM / SIGINT handlers so a host-side
       ``terok-sandbox supervisor`` invocation stops cleanly with Ctrl-C.
    4. Await ``podman wait <container_id>`` racing the children's own
       liveness and the stop signal.  On any of them, terminate the
       children in reverse launch order and return.

    The function is the sole parent entry point; the CLI verb
    ``terok-sandbox supervisor`` invokes it via ``asyncio.run``.  The
    per-service work now lives in
    [`run_child`][terok_sandbox.supervisor.children.run_child], one
    process each.
    """
    cfg = load_sidecar(sidecar_path)
    if cfg is None:
        _logger.error(
            "no usable sidecar at %s — aborting supervisor for %s",
            sidecar_path,
            container_id,
        )
        return 2

    paths = SupervisorPaths.for_container(
        container_id, cfg.container_name, sidecar_path, cfg.runtime_dir
    )
    services = _select_services(cfg, SERVICE_NAMES)
    stop_event = asyncio.Event()
    _install_signal_handlers(stop_event)

    handles: list[ChildHandle] = []
    try:
        handles = await _launch_children(services, container_id, sidecar_path)
        if not handles:
            _logger.error(
                "supervisor: no child could be launched for %s — exiting so the wrapper retries",
                container_id,
            )
            return 3
        await _supervise(container_id, handles, stop_event, container_pid)
        return 0
    finally:
        await _terminate_children(handles)
        # rmtree the per-container dir on every exit path — launch
        # failure included — so a half-bound socket directory can't
        # outlive the supervisor and confuse the next launch.
        shutil.rmtree(paths.container_runtime_dir, ignore_errors=True)