{"id":48671484,"url":"https://github.com/papercomputeco/stereosd","last_synced_at":"2026-04-10T12:30:46.407Z","repository":{"id":340870925,"uuid":"1155490957","full_name":"papercomputeco/stereosd","owner":"papercomputeco","description":"The StereOS daemon control plane.","archived":false,"fork":false,"pushed_at":"2026-02-26T22:23:36.000Z","size":3278,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-27T04:41:44.275Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/papercomputeco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-11T15:18:12.000Z","updated_at":"2026-02-27T03:11:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/papercomputeco/stereosd","commit_stats":null,"previous_names":["papercomputeco/stereosd"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/papercomputeco/stereosd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fstereosd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fstereosd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fstereosd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fstereosd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/papercomputeco","download_url":"https://codeload.github.com/papercomputeco/stereosd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fstereosd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31642622,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T07:40:12.752Z","status":"ssl_error","status_checked_at":"2026-04-10T07:40:11.664Z","response_time":98,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-10T12:30:45.673Z","updated_at":"2026-04-10T12:30:46.391Z","avatar_url":"https://github.com/papercomputeco.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `stereosd` 📦\n\nControl plane daemon for [stereOS](https://github.com/papercomputeco/stereos).\nManages the system and bridges with the host orchestrator.\n\n`stereosd` handles:\n\n- **Lifecycle signaling** - boot status, readiness, and health reported to\n  the host over vsock\n- **Secret injection** - host pushes secrets over vsock, `stereosd` writes them\n  to admin tmpfs (`/run/stereos/secrets/`)\n- **SSH key injection** - ephemeral per-sandbox `authorized_keys` installation\n- **Shared directory mounting** - virtio-fs and 9p mounts from host-provided tags\n- **Graceful shutdown** - unmounts, filesystem sync, `systemctl poweroff`\n- **agentd polling** - periodically queries agentd for agent status, reports\n  to the host as part of health\n\n### Start sequence\n\n1. Kicked by `systemd`\n1. Create runtime directories (`/run/stereos`, `/run/stereos/secrets`, `/etc/stereos`)\n1. Transition to `booting`\n1. Create control plane listener (vsock or TCP based on `--listen-mode`)\n1. Start the NDJSON message server\n1. Start the IPC HTTP server on `/run/stereos/stereosd.sock`\n1. Start the agentd status poller\n1. Transition to `ready`\n\n### Listener selection (`--listen-mode`)\n\n| Mode | Behavior |\n|------|----------|\n| `auto` | Check `vsock.TransportAvailable()`, use vsock if available, else TCP |\n| `vsock` | AF_VSOCK only (Linux/KVM with `vhost-vsock-pci`) |\n| `tcp` | TCP `0.0.0.0:1024` only (macOS/HVF with QEMU user-mode networking) |\n\n## Wire protocol\n\nNewline-delimited JSON (NDJSON) over AF_VSOCK or TCP. One JSON object per\nline, max 1MB per message.\n\n### Envelope\n\n```json\n{\"type\": \"\u003cmessage_type\u003e\", \"payload\": { ... }}\n```\n\n### Messages\n\n| Type | Direction | Payload | Response |\n|------|-----------|---------|----------|\n| `ping` | host -\u003e guest | none | `pong` |\n| `get_health` | host -\u003e guest | none | `health` |\n| `set_config` | host -\u003e guest | `ConfigPayload` | `ack` |\n| `inject_secret` | host -\u003e guest | `SecretPayload` | `ack` |\n| `inject_ssh_key` | host -\u003e guest | `SSHKeyPayload` | `ack` |\n| `mount` | host -\u003e guest | `MountPayload` | `ack` |\n| `shutdown` | host -\u003e guest | `ShutdownPayload` | `ack` (immediate, then poweroff) |\n| `lifecycle` | guest -\u003e host | `LifecyclePayload` | (push, no response) |\n\n## Subsystems\n\n### `SecretManager`\n\nWrites secrets to `/run/stereos/secrets/\u003cname\u003e` (tmpfs, `admin` owned, never on persistent disk).\n\n- Atomic writes: write to `.tmp`, then `rename()`\n- Default file mode `0600`, configurable via `SecretPayload.Mode`\n- `secret.Value` is zeroed from the payload struct after writing (memory safety)\n- Name validation: must be a simple filename (no `/`, no `..`)\n- Operations: `Inject`, `List`, `Remove`\n\n### `SSHKeyManager`\n\nInstalls SSH public keys into `~/.ssh/authorized_keys` for a given user.\n\n- Resolves home directory via `os/user.Lookup`\n- Creates `~/.ssh/` (mode `0700`), writes `authorized_keys` (mode `0600`)\n- Atomic write via `.tmp` + `rename()`\n- Validates key format against known prefixes: `ssh-ed25519`, `ssh-rsa`,\n  `ecdsa-sha2-*`, `sk-ssh-ed25519@openssh.com`,\n  `sk-ecdsa-sha2-nistp256@openssh.com`\n- Files are owned by the target user (uid/gid from user lookup)\n\n### `MountManager`\n\nMounts host-shared directories into the guest.\n\n- Supported filesystems: `virtiofs` (`mount -t virtiofs \u003ctag\u003e \u003cpath\u003e`) and\n  `9p` (`mount -t 9p -o trans=virtio,version=9p2000.L \u003ctag\u003e \u003cpath\u003e`)\n- Path validation: must be absolute, cannot mount over system directories\n  (`/`, `/nix`, `/etc`, `/bin`, `/boot`, `/dev`, `/proc`, `/sys`, `/run`)\n- Auto-creates mount point directory (mode `0755`)\n- Sets ownership to `agent:agent` (best effort)\n- Tracks mounts in order; `UnmountAll()` unmounts in reverse (LIFO)\n\n### `LifecycleManager`\n\nState machine: `booting` -\u003e `ready` -\u003e `healthy` / `degraded` -\u003e `shutdown`\n\n- Thread-safe (`sync.RWMutex`)\n- On transition, pushes `lifecycle` envelope to the host via a configurable\n  `vsockSend` callback\n- Tracks agent statuses (replaced atomically by the agentd poller)\n- Promotes `ready` -\u003e `healthy` when at least one agent is running\n- `Health()` returns the full `HealthPayload` for `get_health` responses\n\n### `ShutdownCoordinator`\n\nGraceful shutdown sequence:\n\n1. Transition lifecycle to `shutdown`\n2. `UnmountAll()` shared directories (reverse order)\n3. `sync` filesystems\n4. `systemctl poweroff`\n\nsystemd handles SIGTERM delivery to agentd and other services within their\nconfigured `TimeoutStopSec`.\n\n### `AgentdClient`\n\nPolls [agentd](https://github.com/papercomputeco/agentd) over its Unix domain\nsocket (`/run/stereos/agentd.sock`).\n\n- HTTP client with Unix socket transport\n- Consumes `GET /v1/health` and `GET /v1/agents`\n- Poll interval: 5 seconds\n- Atomically replaces agent statuses in the `LifecycleManager`\n- agentd being unreachable is expected during boot (logged, not fatal)\n\n## IPC HTTP API\n\nServed on `/run/stereos/stereosd.sock` (mode `0660`, group `admin`).\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/v1/ping` | `{\"status\": \"ok\"}` |\n| `GET` | `/v1/health` | Full `HealthPayload` (state, uptime, agents) |\n| `POST` | `/v1/secrets` | Inject a secret (`SecretPayload` body) |\n| `GET` | `/v1/secrets` | List secret names |\n| `DELETE` | `/v1/secrets/{name}` | Remove a secret |\n| `POST` | `/v1/mounts` | Mount a shared directory (`MountPayload` body) |\n| `GET` | `/v1/mounts` | List active mounts |\n| `POST` | `/v1/shutdown` | Initiate graceful shutdown (returns `202 Accepted`) |\n| `GET` | `/v1/agents` | List agents (cached from agentd poller) |\n\n\n### Runtime directories\n\n| Path | Mode | Owner | Purpose |\n|------|------|-------|---------|\n| `/run/stereos` | `0755` | root:admin | Base runtime directory |\n| `/run/stereos/secrets` | `0700` | root:root | tmpfs-backed secret store |\n| `/etc/stereos` | `0755` | root:root | Configuration (jcard.toml) |\n\n## NixOS module\n\nThe flake exports `nixosModules.default` with the following options:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `services.stereosd.enable` | bool | `false` | Enable the stereosd daemon |\n| `services.stereosd.package` | package | flake default | The stereosd package |\n| `services.stereosd.listenMode` | enum `[\"auto\" \"vsock\" \"tcp\"]` | `\"auto\"` | Control plane listener mode |\n| `services.stereosd.extraArgs` | list of str | `[]` | Additional CLI arguments |\n\nThe systemd unit runs after `network.target` and\n`systemd-tmpfiles-setup.service`, with `Restart=always` and `DynamicUser=true`\n(overridden to `false` by the stereOS NixOS module since stereosd needs root\nfor vsock binding, mount operations, and secret file ownership).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapercomputeco%2Fstereosd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpapercomputeco%2Fstereosd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapercomputeco%2Fstereosd/lists"}