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, stereosd writes them to admin tmpfs (/run/stereos/secrets/)
  • SSH key injection - ephemeral per-sandbox authorized_keys installation
  • 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

  1. Kicked by systemd
  2. Create runtime directories (/run/stereos, /run/stereos/secrets, /etc/stereos)
  3. Transition to booting
  4. Create control plane listener (vsock or TCP based on --listen-mode)
  5. Start the NDJSON message server
  6. Start the IPC HTTP server on /run/stereos/stereosd.sock
  7. Start the agentd status poller
  8. Transition to ready

Listener selection (--listen-mode)

ModeBehavior
autoCheck vsock.TransportAvailable(), use vsock if available, else TCP
vsockAF_VSOCK only (Linux/KVM with vhost-vsock-pci)
tcpTCP 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

TypeDirectionPayloadResponse
pinghost -> guestnonepong
get_healthhost -> guestnonehealth
set_confighost -> guestConfigPayloadack
inject_secrethost -> guestSecretPayloadack
inject_ssh_keyhost -> guestSSHKeyPayloadack
mounthost -> guestMountPayloadack
shutdownhost -> guestShutdownPayloadack (immediate, then poweroff)
lifecycleguest -> hostLifecyclePayload(push, no response)

Subsystems

SecretManager

Writes secrets to /run/stereos/secrets/<name> (tmpfs, admin owned, never on persistent disk).

  • Atomic writes: write to .tmp, then rename()
  • Default file mode 0600, configurable via SecretPayload.Mode
  • secret.Value is 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/ (mode 0700), writes authorized_keys (mode 0600)
  • 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>) and 9p (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 lifecycle envelope to the host via a configurable vsockSend callback
  • Tracks agent statuses (replaced atomically by the agentd poller)
  • Promotes ready -> healthy when at least one agent is running
  • Health() returns the full HealthPayload for get_health responses

ShutdownCoordinator

Graceful shutdown sequence:

  1. Transition lifecycle to shutdown
  2. UnmountAll() shared directories (reverse order)
  3. sync filesystems
  4. systemctl 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/health and GET /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).

MethodPathDescription
GET/v1/ping{"status": "ok"}
GET/v1/healthFull HealthPayload (state, uptime, agents)
POST/v1/secretsInject a secret (SecretPayload body)
GET/v1/secretsList secret names
DELETE/v1/secrets/{name}Remove a secret
POST/v1/mountsMount a shared directory (MountPayload body)
GET/v1/mountsList active mounts
POST/v1/shutdownInitiate graceful shutdown (returns 202 Accepted)
GET/v1/agentsList agents (cached from agentd poller)

Runtime directories

PathModeOwnerPurpose
/run/stereos0755root:adminBase runtime directory
/run/stereos/secrets0700root:roottmpfs-backed secret store
/etc/stereos0755root:rootConfiguration (jcard.toml)

NixOS module

The flake exports nixosModules.default with the following options:

OptionTypeDefaultDescription
services.stereosd.enableboolfalseEnable the stereosd daemon
services.stereosd.packagepackageflake defaultThe stereosd package
services.stereosd.listenModeenum ["auto" "vsock" "tcp"]"auto"Control plane listener mode
services.stereosd.extraArgslist 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).