Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
V3.1 → V3.12 consolidated and tagged for the first GA release. Wire format unchanged from 0.4.x — 4.0 peers interoperate with 0.4.x peers byte-for-byte. The version bump is semantic: audit-cycle complete, opt-in surface fully exposed, threat model refreshed for every new surface. Highlights: - All 24 @shade/* packages bumped to 4.0.0 in lockstep. - CHANGELOG 4.0.0 section is the canonical manifest of what landed. - THREAT-MODEL extended (§10 fingerprint gates, §11 WebRTC P2P, §12 Web-Worker boundary) + residual-risks table refreshed. - OpenAPI now covers all 27 routes: prekey, transfer, KT, inbox, bridge, observer, /metrics, /healthz, /ready. - MIGRATION 0.3.x → 4.0 documented + smoke-tested against shade migrate-storage on a real SQLite DB. - docs/audit/REVIEW-BUNDLE.md + SCOPE.md ready for external reviewer. - scripts/soak.ts harness for the GA-stable 2-week soak window. - All V*.md plans archived under docs/archive/ with Status: Done. - Voice/Video carved out into V5.0; 4.0 audit focuses on the frozen non-realtime stack. Tests: TS 1000/1000 + Kotlin 11/11 cross-platform vectors green. Docker: gt.zyon.no/stian/shade-prekey:4.0.0 builds and reports version 4.0.0 on /health. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
5.3 KiB
Markdown
130 lines
5.3 KiB
Markdown
# @shade/server — Shade Prekey Server (standalone container)
|
|
|
|
A self-contained Docker image that provides the prekey server, OpenAPI contract, observer dashboard, and stale cleanup — **everything a project needs to adopt Shade**, with zero coupling to the consumer's stack.
|
|
|
|
## Deploy in 2 minutes
|
|
|
|
```bash
|
|
docker run -d \
|
|
--name my-project-shade \
|
|
-v my-project-shade:/data \
|
|
-p 3900:3900 \
|
|
-e SHADE_OBSERVER_TOKEN=change-me-to-at-least-16-chars \
|
|
gt.zyon.no/stian/shade-prekey:latest
|
|
```
|
|
|
|
Done. Your prekey server is live:
|
|
- `http://localhost:3900/health` — health check
|
|
- `http://localhost:3900/openapi.yaml` — API contract for any language
|
|
- `http://localhost:3900/docs` — interactive API reference (Redoc)
|
|
- `http://localhost:3900/shade-observer/dashboard/` — live debugger (token required)
|
|
- `http://localhost:3900/v1/keys/*` — prekey REST API
|
|
|
|
Your consumer projects (Nova, Orchestrator, Python apps, anything) then point at `http://localhost:3900` as their `prekeyServer` URL.
|
|
|
|
## One container per project
|
|
|
|
The recommended architecture is **one Shade container per project**:
|
|
|
|
```
|
|
nova-shade (Docker container, SQLite volume) ← Nova backend + Android app
|
|
orchestrator-shade (Docker container, SQLite volume) ← Orchestrator hub + workstations
|
|
future-project (Docker container, SQLite volume) ← Any future app
|
|
```
|
|
|
|
Each project owns its own container, its own volume, its own observer token. Zero cross-project coupling. If one project's Shade is down, the others keep running.
|
|
|
|
## Keys vs. payloads — what this server is, and isn't
|
|
|
|
The prekey server is a **public-key directory**. It exists so a brand-
|
|
new client can find the right Ed25519 + X25519 bundle to start an
|
|
X3DH handshake with a peer it has never talked to. After that, the
|
|
peers ratchet directly.
|
|
|
|
What lives on this server:
|
|
|
|
- Identity public keys
|
|
- Signed prekey + one-time prekey bundles
|
|
- Activity timestamps (used by stale cleanup)
|
|
- Operator metadata: `/health`, `/metrics`, `/openapi.yaml`,
|
|
`/shade-observer/*`
|
|
|
|
What never lives on this server:
|
|
|
|
- **Message plaintext.** Ratchet envelopes flow peer-to-peer.
|
|
- **Transfer chunks.** `@shade/transfer` POSTs ciphertext directly to
|
|
the receiver's `/v1/transfer/:streamId/chunk` route — not here.
|
|
- **Identity private keys** or **session state**. Both are device-
|
|
local.
|
|
- **Resume secrets** for in-flight transfers. Encrypted under a
|
|
device-key derived from the identity signing key, never uploaded.
|
|
|
|
This is the bright line that lets you deploy one shared prekey
|
|
container per project even when consumer apps don't trust each other:
|
|
the worst a compromised prekey server can do is hand out a fake
|
|
bundle (MITM at first contact). Out-of-band fingerprint comparison
|
|
detects this — see `THREAT-MODEL.md § 2` and the `getIdentityFingerprint()`
|
|
API.
|
|
|
|
For deployment-time gates (TLS, backup, observer-token rotation, log
|
|
level, secret rotation) see
|
|
[`docs/PRODUCTION-CHECKLIST.md`](../../docs/PRODUCTION-CHECKLIST.md).
|
|
For the wire contract — including the peer-served
|
|
`/v1/transfer/*` and `ShadeTransferAuthenticator` security scheme —
|
|
see [`openapi.yaml`](./openapi.yaml).
|
|
|
|
## Environment variables
|
|
|
|
| Var | Default | Description |
|
|
|-----|---------|-------------|
|
|
| `PORT` | `3900` | HTTP port |
|
|
| `SHADE_PREKEY_DB_PATH` | `/data/shade-prekeys.db` | SQLite file path |
|
|
| `SHADE_PREKEY_PG_URL` | unset | Postgres connection string. If set, overrides SQLite. |
|
|
| `SHADE_OBSERVER_TOKEN` | unset | Bearer token for the dashboard. Min 16 chars. Unset = observer disabled. |
|
|
| `SHADE_STALE_DAYS` | `30` | Purge identities with no activity in N days |
|
|
| `SHADE_CLEANUP_INTERVAL_HOURS` | `24` | How often the cleanup task runs |
|
|
| `SHADE_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
|
|
|
|
## Persistence
|
|
|
|
The `/data` volume holds the SQLite database. Back it up by copying the `.db` file (use SQLite's online backup API or just stop the container briefly).
|
|
|
|
To switch to Postgres, set `SHADE_PREKEY_PG_URL=postgres://user:pass@host/db`. Tables will be created automatically with the `shade_server_*` prefix.
|
|
|
|
## Stale cleanup
|
|
|
|
Identities that have no activity (no bundle fetches, no replenishments, no registration updates) for more than `SHADE_STALE_DAYS` days are automatically purged. This keeps the database bounded even if users never unregister cleanly.
|
|
|
|
## Using from your project
|
|
|
|
Any language can speak to a Shade container — it's just HTTP. See [openapi.yaml](./openapi.yaml) for the full contract.
|
|
|
|
**TypeScript / Bun:**
|
|
```ts
|
|
import { createShade } from '@shade/sdk';
|
|
const shade = await createShade({ prekeyServer: 'http://my-project-shade:3900' });
|
|
```
|
|
|
|
**Python / Go / Rust:** generate a client from the OpenAPI spec with `openapi-generator`, or implement the wire protocol directly (8 endpoints, Ed25519 signatures documented in the spec).
|
|
|
|
**Android:** use the `shade-android` Kotlin module. Same wire protocol, verified by cross-platform test vectors.
|
|
|
|
## Building locally
|
|
|
|
```bash
|
|
bun run build:docker # build shade-prekey:dev
|
|
bun run build:docker -- --tag v1.0.0 # custom tag
|
|
GITEA_TOKEN=... bun run publish:docker # build + push to registry
|
|
```
|
|
|
|
## CI publishing
|
|
|
|
Tag a release and CI publishes automatically:
|
|
|
|
```bash
|
|
git tag v1.0.0
|
|
git push --tags
|
|
```
|
|
|
|
`.gitea/workflows/docker.yml` runs tests, builds the image, and pushes both `v1.0.0` and `latest` tags to `gt.zyon.no/stian/shade-prekey`.
|