shoal

self-hosted sync server for local-first apps

one small rust binary stores an append-only log of end-to-end encrypted operations per user. clients merge, the server only stores and orders ciphertext.

properties

how it works

clients append full-state record ops to a local outbox and push them in signed batches. the server assigns each op a per-user sequence number. clients pull ops past their cursor and merge with per-table strategies (last-writer-wins on a hybrid logical clock, or append-only). an sse endpoint pokes connected clients when new ops land.

the full wire format, key derivation, auth scheme, and storage schema are in protocol.md.

run

docker compose up -d --build

or pull the prebuilt image: ghcr.io/emerson-d-lopes/shoal:latest. or bare: cargo run --release.

api

endpointpurpose
POST /v1/opspush a batch of encrypted ops (idempotent by op_id)
GET /v1/ops?since=Npull ops past a cursor, optional collection filter
GET /v1/pokesse stream, emits when new ops arrive for the caller
GET /healthzliveness

all endpoints except /healthz require ed25519 request signatures.

clients

a typescript client is at shoal-client, used by mnemonic and habit-tracker. a kotlin client for android is in progress.