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
- 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
| 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) |
Newline-delimited JSON (NDJSON) over AF_VSOCK or TCP. One JSON object per line, max 1MB per message.
{"type": "<message_type>", "payload": { ... }}| 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) |
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
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)
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)
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
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.
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)
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) |
| 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) |
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).