stereosd ๐ฆ
February 26, 2026 ยท View on GitHub
Control plane daemon for stereOS. Manages the system and bridges with the host orchestrator.
stereosd handles:
- Lifecycle signaling - boot status, readiness, and health reported to the host over vsock
- Secret injection - host pushes secrets over vsock,
stereosdwrites them to admin tmpfs (/run/stereos/secrets/) - SSH key injection - ephemeral per-sandbox
authorized_keysinstallation - Shared directory mounting - virtio-fs and 9p mounts from host-provided tags
- Graceful shutdown - unmounts, filesystem sync,
systemctl poweroff - agentd polling - periodically queries agentd for agent status, reports to the host as part of health
Start sequence
- Kicked by
systemd - Create runtime directories (
/run/stereos,/run/stereos/secrets,/etc/stereos) - Transition to
booting - Create control plane listener (vsock or TCP based on
--listen-mode) - Start the NDJSON message server
- Start the IPC HTTP server on
/run/stereos/stereosd.sock - Start the agentd status poller
- Transition to
ready
Listener selection (--listen-mode)
| Mode | Behavior |
|---|---|
auto | Check vsock.TransportAvailable(), use vsock if available, else TCP |
vsock | AF_VSOCK only (Linux/KVM with vhost-vsock-pci) |
tcp | TCP 0.0.0.0:1024 only (macOS/HVF with QEMU user-mode networking) |
Wire protocol
Newline-delimited JSON (NDJSON) over AF_VSOCK or TCP. One JSON object per line, max 1MB per message.
Envelope
{"type": "<message_type>", "payload": { ... }}
Messages
| Type | Direction | Payload | Response |
|---|---|---|---|
ping | host -> guest | none | pong |
get_health | host -> guest | none | health |
set_config | host -> guest | ConfigPayload | ack |
inject_secret | host -> guest | SecretPayload | ack |
inject_ssh_key | host -> guest | SSHKeyPayload | ack |
mount | host -> guest | MountPayload | ack |
shutdown | host -> guest | ShutdownPayload | ack (immediate, then poweroff) |
lifecycle | guest -> host | LifecyclePayload | (push, no response) |
Subsystems
SecretManager
Writes secrets to /run/stereos/secrets/<name> (tmpfs, admin owned, never on persistent disk).
- Atomic writes: write to
.tmp, thenrename() - Default file mode
0600, configurable viaSecretPayload.Mode secret.Valueis zeroed from the payload struct after writing (memory safety)- Name validation: must be a simple filename (no
/, no..) - Operations:
Inject,List,Remove
SSHKeyManager
Installs SSH public keys into ~/.ssh/authorized_keys for a given user.
- Resolves home directory via
os/user.Lookup - Creates
~/.ssh/(mode0700), writesauthorized_keys(mode0600) - Atomic write via
.tmp+rename() - Validates key format against known prefixes:
ssh-ed25519,ssh-rsa,ecdsa-sha2-*,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com - Files are owned by the target user (uid/gid from user lookup)
MountManager
Mounts host-shared directories into the guest.
- Supported filesystems:
virtiofs(mount -t virtiofs <tag> <path>) and9p(mount -t 9p -o trans=virtio,version=9p2000.L <tag> <path>) - Path validation: must be absolute, cannot mount over system directories
(
/,/nix,/etc,/bin,/boot,/dev,/proc,/sys,/run) - Auto-creates mount point directory (mode
0755) - Sets ownership to
agent:agent(best effort) - Tracks mounts in order;
UnmountAll()unmounts in reverse (LIFO)
LifecycleManager
State machine: booting -> ready -> healthy / degraded -> shutdown
- Thread-safe (
sync.RWMutex) - On transition, pushes
lifecycleenvelope to the host via a configurablevsockSendcallback - Tracks agent statuses (replaced atomically by the agentd poller)
- Promotes
ready->healthywhen at least one agent is running Health()returns the fullHealthPayloadforget_healthresponses
ShutdownCoordinator
Graceful shutdown sequence:
- Transition lifecycle to
shutdown UnmountAll()shared directories (reverse order)syncfilesystemssystemctl poweroff
systemd handles SIGTERM delivery to agentd and other services within their
configured TimeoutStopSec.
AgentdClient
Polls agentd over its Unix domain
socket (/run/stereos/agentd.sock).
- HTTP client with Unix socket transport
- Consumes
GET /v1/healthandGET /v1/agents - Poll interval: 5 seconds
- Atomically replaces agent statuses in the
LifecycleManager - agentd being unreachable is expected during boot (logged, not fatal)
IPC HTTP API
Served on /run/stereos/stereosd.sock (mode 0660, group admin).
| Method | Path | Description |
|---|---|---|
GET | /v1/ping | {"status": "ok"} |
GET | /v1/health | Full HealthPayload (state, uptime, agents) |
POST | /v1/secrets | Inject a secret (SecretPayload body) |
GET | /v1/secrets | List secret names |
DELETE | /v1/secrets/{name} | Remove a secret |
POST | /v1/mounts | Mount a shared directory (MountPayload body) |
GET | /v1/mounts | List active mounts |
POST | /v1/shutdown | Initiate graceful shutdown (returns 202 Accepted) |
GET | /v1/agents | List agents (cached from agentd poller) |
Runtime directories
| Path | Mode | Owner | Purpose |
|---|---|---|---|
/run/stereos | 0755 | root:admin | Base runtime directory |
/run/stereos/secrets | 0700 | root:root | tmpfs-backed secret store |
/etc/stereos | 0755 | root:root | Configuration (jcard.toml) |
NixOS module
The flake exports nixosModules.default with the following options:
| Option | Type | Default | Description |
|---|---|---|---|
services.stereosd.enable | bool | false | Enable the stereosd daemon |
services.stereosd.package | package | flake default | The stereosd package |
services.stereosd.listenMode | enum ["auto" "vsock" "tcp"] | "auto" | Control plane listener mode |
services.stereosd.extraArgs | list of str | [] | Additional CLI arguments |
The systemd unit runs after network.target and
systemd-tmpfiles-setup.service, with Restart=always and DynamicUser=true
(overridden to false by the stereOS NixOS module since stereosd needs root
for vsock binding, mount operations, and secret file ownership).