Blind sync server for the BlindSync app: it relays your devices' ciphertext and never holds a key.
3.6K
The sync server for BlindSync: a small, end-to-end-encrypted, account-free app for a household. It relays ciphertext between your devices. It has never held a key, so it can't read anything it stores.
The app is The Mycelium. It runs in a browser and installs as a PWA, and it holds several things in one place:
The app keeps a copy of its data on the device and works offline. It syncs through the BlindSync server you give it at setup, and this image is that server.
services:
blind-sync:
image: darktrees/blind-sync:latest
env_file: .env
volumes:
- blind-sync-data:/data
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
mem_limit: 512m
read_only: true
cap_drop:
- ALL
security_opt:
- "no-new-privileges:true"
volumes:
blind-sync-data:
The database and the encrypted files both live in /data, so back up that
volume.
The app is served over HTTPS, so a browser will only talk to a server that
is HTTPS too. Put it behind a reverse proxy with a trusted certificate, then
enter that address in the app when it asks for a server, for example
https://sync.example.com.
There's no login, by design. A group id is a capability: anything that knows one can fetch that group's ciphertext, and can't read it. To limit what a stranger can cost you, every storage, connection and rate cap has a finite default.
Keep it on a private network such as Tailscale or WireGuard if that suits your household. If you put it on the internet:
CORS_ALLOWED_ORIGINS to the app address you use. An origin left out
shows up as "Can't reach your server" in the app, while /health still
loads in a browser tab.ALLOW_PRIVATE_NETWORK=false.TRUST_PROXY=true only behind a proxy that sets X-Real-IP. Without
one, a client could pick its own address and step around the rate limits.client_max_body_size of at least 2 MB on the proxy for encrypted
file chunks.Put these in .env beside the compose file. Keep the VAPID private key and
the SMTP password out of the compose file itself.
| Variable | Default | What it's for |
|---|---|---|
CORS_ALLOWED_ORIGINS | * | The browser origins allowed to call the API, comma-separated |
ALLOW_PRIVATE_NETWORK | true | Lets a public app page reach a server on a private address. Turn it off on a public server. |
TRUST_PROXY | true | Take the client address from the proxy's X-Real-IP |
MAX_TOTAL_BYTES | 2 GB | All documents across all groups |
MAX_GROUP_BYTES | 8 MB | Documents in one group |
MAX_TOTAL_BLOB_BYTES | 20 GiB | All encrypted files. Size it to the disk. |
MAX_GROUP_BLOB_BYTES | 512 MiB | Encrypted files in one group. Raise it for long recordings. |
RATE_LIMIT_RPS / RATE_LIMIT_BURST | 10 / 200 | Per-address request rate. Opening the app costs about four requests per group at once, so don't set the burst low. |
MAX_WS_PER_IP | 64 | Live connections per address. A device holds one per group. |
VAPID_PRIVATE_KEY | empty | Turns on notifications and reminders |
VAPID_SUBJECT | empty | A mailto: or https: address for push services to contact you |
MAIL_ENABLED | false | Turns on calendar invites by email |
SMTP_HOST / SMTP_PORT | empty / 587 | Your SMTP relay. Port 465 is implicit TLS; any other port uses STARTTLS. |
SMTP_USER / SMTP_PASS | empty | Relay credentials, if it needs them |
MAIL_FROM | empty | The organiser address invites are sent from. Keep it fixed: calendar apps match updates to it. |
Set any cap to 0 for no limit. Everything else has a working default.
Generate the VAPID key with the image itself:
docker run --rm darktrees/blind-sync -genvapid
Changing the key later cuts every phone off from notifications until the app re-subscribes, which current versions do on their own.
GET /health answers {"status":"ok"}, and the image has a built-in
health check. When notifications don't arrive, start from Settings →
Notifications → Check notifications in the app. It tests every link and
sends a real push to that phone. Then check docker logs for push.
The server ships no data and fetches none. Everything it stores came from your own devices. It contacts two kinds of outside service, and only when you configure them:
| Service | Used for | Terms |
|---|---|---|
| Browser push services (run by the browser's maker) | Delivering notifications | The browser maker's terms. Each push is encrypted to the device, and padded. |
| Your SMTP relay | Calendar invites by email | Your own provider's terms |
The binary is built with these open-source libraries, each under its own licence: chi and go-chi/cors (MIT), Gorilla WebSocket (BSD-2-Clause), golang.org/x/time (BSD-3-Clause), modernc.org/sqlite (BSD-3-Clause, a translation of SQLite, which is public domain) and the Go standard library (BSD-3-Clause).
BlindSync is source-available under the
PolyForm Noncommercial License 1.0.0.
Personal, hobby, research, educational and charity use are all welcome.
Commercial use is not permitted without a separate licence. The full
terms are at /LICENSE in the image.
The code lives on a private forge. It's available on request for non-commercial use; get in touch through the contact link on the darktrees Docker Hub profile. To ask about commercial use, use the same link.
Content type
Image
Digest
sha256:4eeb90998…
Size
5.2 MB
Last updated
2 days ago
docker pull darktrees/blind-sync