sidecar
sidecar
¶
Sidecar config + per-container path resolution for the supervisor.
The parent supervisor and every service child read the same per-container
sidecar JSON — written once at container-creation time by
write_sidecar and pinned by the
terok.sandbox.sidecar annotation — so the config schema
(SidecarConfig) and
the socket/log layout it resolves to
(SupervisorPaths)
live here, imported by both the parent
(main) and the child runners
(children). Keeping them in their
own module (rather than on main) is what lets a child import the
config vocabulary without importing the parent's process-supervision
logic.
SERVICE_NAMES = ('verdict', 'clearance', 'gate', 'vault', 'signer')
module-attribute
¶
SidecarConfig(container_name, ipc_mode, db_path, runtime_dir, routes_path=None, vault_systemd_creds_file=None, credentials_use_keyring=False, credentials_passphrase_command=None, 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, allow_debugger=False, _resolved_passphrase=None)
dataclass
¶
Per-container config the supervisor reads from the sidecar JSON.
Written once at container-creation time by
write_sidecar (terok-executor
routes through the same writer) and read back by the OCI hook on
every podman start. Keyed by container name
(<state>/sidecar/<name>.json); the terok.sandbox.sidecar
annotation pins the absolute path. The file persists across
stop/start cycles — its ports and tokens must keep matching the
container's immutable env — and is removed at real teardown by
remove_container_state.
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).
routes_path = None
class-attribute
instance-attribute
¶
Vault routing table captured by the launch process.
None derives routes.json beside db_path for sidecars made
outside the canonical writer.
vault_systemd_creds_file = None
class-attribute
instance-attribute
¶
Machine-bound passphrase credential captured by the launch process.
None derives vault.passphrase.cred beside db_path.
credentials_use_keyring = False
class-attribute
instance-attribute
¶
Whether the pre-confinement passphrase walk may consult the OS keyring.
credentials_passphrase_command = None
class-attribute
instance-attribute
¶
Optional helper the child executes before installing Landlock.
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
¶
allow_debugger = False
class-attribute
instance-attribute
¶
Debug mode — the children leave themselves ptrace-able instead of
clearing the dumpable flag, so a debugger can attach. False (fully
hardened) unless the launch path opted the task in.
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 isolated service subdirectories.
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.
vault_socket
instance-attribute
¶
ssh_signer_socket
instance-attribute
¶
gate_socket
instance-attribute
¶
Per-container git-gate Unix socket in its service-only directory.
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>/hub.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/<container_name>/ directory
the launch path bind-mounts at /run/terok/ inside the
container. Each service gets a child directory so its Landlock
write grant cannot replace a sibling's socket.
Source code in src/terok_sandbox/supervisor/sidecar.py
wired_services(cfg)
¶
The services cfg wires, in launch order — all but a gate it did not.
The one conditional service is the gate: a sidecar without a mirror path and a token has no repo to serve, and the parent launches no child for it. Everything else runs for every container.
Source code in src/terok_sandbox/supervisor/sidecar.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. The per-field validation lives in helpers that raise
_BadSidecar on the first bad value, so this reader stays a flat
read → validate → build.