Finished updating the readme
This commit is contained in:
236
README.md
236
README.md
@@ -1,24 +1,224 @@
|
||||
# GreenCoast — Privacy-First, Shardable Social (Dockerized)
|
||||
# GreenCoast
|
||||
|
||||
**Goal:** A BlueSky-like experience with **shards**, **zero-trust**, **no data collection**, **E2EE**, and easy self-hosting — from x86_64 down to **Raspberry Pi Zero**.
|
||||
License: **The Unlicense** (public-domain equivalent).
|
||||
|
||||
This repo contains a minimal, working **shard**: an append-only object API with zero-data-collection defaults. It’s structured to evolve into full federation, E2EE, and client apps, while keeping Pi Zero as a supported host.
|
||||
A privacy-first, shardable social backend + minimalist client. **Zero PII**, **zero passwords**, optional **E2EE per post**, and **public-key accounts**. Includes **DPoP-style proof-of-possession**, **Discord SSO with PKCE**, and a tiny static client.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start (Laptop / Dev)
|
||||
## Features
|
||||
|
||||
**Requirements:** Docker + Compose v2
|
||||
- **Zero-trust by design**: server stores no emails or passwords.
|
||||
- **Accounts = public keys** (Ed25519 or P-256). No usernames required.
|
||||
- **Proof-of-possession (PoP)** on every authenticated API call.
|
||||
- **Short-lived tokens** (HMAC “gc2”) bound to device keys.
|
||||
- **Shardable storage** (mTLS or signed shard requests).
|
||||
- **No fingerprinting**: no IP/UA logs; coarse timestamps optional.
|
||||
- **Static client** with strong CSP; optional E2EE per post.
|
||||
- **Discord SSO (PKCE)** as an *optional* convenience.
|
||||
- **Filesystem storage** supports both **flat** and **nested** object layouts.
|
||||
|
||||
---
|
||||
|
||||
## Architecture (brief)
|
||||
|
||||
- **Shard**: stateless API + local FS object store + in-memory index.
|
||||
- **Client**: static files (HTML/JS/CSS) served by the shard or any static host.
|
||||
- **Identity**: device key (P-256/Ed25519) or passkey; server mints short-lived **gc2** tokens bound to the device key (`cnf` claim).
|
||||
- **Privacy**: objects can be plaintext (public) or client-encrypted (private).
|
||||
|
||||
---
|
||||
|
||||
## Security posture
|
||||
|
||||
- **Zero-trust**: no passwords/emails; optional SSO is *linking*, not source-of-truth.
|
||||
- **DPoP-style PoP** on requests:
|
||||
- Client sends:
|
||||
- `Authorization: Bearer gc2.…`
|
||||
- `X-GC-Key: p256:<base64-raw>` (or `ed25519:…`)
|
||||
- `X-GC-TS: <unix seconds>`
|
||||
- `X-GC-Proof: sig( METHOD "\n" URL "\n" TS "\n" SHA256(body) )`
|
||||
- Server verifies `gc2` signature, key binding (`cnf`), timestamp window, and replay cache.
|
||||
- **Replay protection**: 10-minute proof cache.
|
||||
- **No fingerprinting/logging**: no IPs, no UAs.
|
||||
- **Strict CSP** for client: blocks XSS/token theft.
|
||||
- **Limits**: request body limits (default 10 MiB), simple per-account rate limiting.
|
||||
- **Shard↔shard**: mTLS or per-shard signatures with timestamp + replay cache.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.21+
|
||||
- Docker (optional)
|
||||
- A signing key for tokens: `GC_SIGNING_SECRET_HEX` (32+ bytes hex)
|
||||
- (Optional) Discord OAuth app (Client ID/Secret + redirect URI)
|
||||
- (Optional) Cloudflare Tunnel or other TLS reverse proxy
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
GC_HTTP_ADDR=:9080
|
||||
GC_HTTPS_ADDR= # optional
|
||||
GC_TLS_CERT= # optional
|
||||
GC_TLS_KEY= # optional
|
||||
|
||||
GC_STATIC_ADDR=:9082
|
||||
GC_STATIC_DIR=/opt/greencoast/client
|
||||
|
||||
GC_DATA_DIR=/var/lib/greencoast
|
||||
GC_ZERO_TRUST=true
|
||||
GC_COARSE_TS=false
|
||||
|
||||
GC_SIGNING_SECRET_HEX=<64+ hex chars> # required for gc2 tokens
|
||||
GC_REQUIRE_POP=true # default true; set false for first-run
|
||||
|
||||
# Dev convenience (testing only; disable for production)
|
||||
GC_DEV_ALLOW_UNAUTH=false
|
||||
GC_DEV_BEARER=
|
||||
|
||||
# Discord SSO (optional)
|
||||
GC_DISCORD_CLIENT_ID=
|
||||
GC_DISCORD_CLIENT_SECRET=
|
||||
GC_DISCORD_REDIRECT_URI=https://greencoast.example.com/auth-callback.html
|
||||
|
||||
---
|
||||
|
||||
## Quickstart (Docker)
|
||||
|
||||
Minimal compose for local testing (PoP disabled + dev unauth allowed for first run):
|
||||
|
||||
services:
|
||||
shard-test:
|
||||
build: .
|
||||
environment:
|
||||
- GC_HTTP_ADDR=:9080
|
||||
- GC_STATIC_ADDR=:9082
|
||||
- GC_STATIC_DIR=/opt/greencoast/client
|
||||
- GC_DATA_DIR=/var/lib/greencoast
|
||||
- GC_ZERO_TRUST=true
|
||||
- GC_SIGNING_SECRET_HEX=7f6e1a0f2b4d7e3a... # replace with your secret
|
||||
- GC_REQUIRE_POP=false # easier first-run
|
||||
- GC_DEV_ALLOW_UNAUTH=true
|
||||
volumes:
|
||||
- ./testdata:/var/lib/greencoast
|
||||
- ./client:/opt/greencoast/client:ro
|
||||
ports:
|
||||
- "9080:9080"
|
||||
- "9082:9082"
|
||||
|
||||
Open `http://localhost:9082` → set the Shard URL (`http://localhost:9080`) → publish a test post.
|
||||
|
||||
When ready, **turn PoP on** by removing `GC_REQUIRE_POP=false` and disabling `GC_DEV_ALLOW_UNAUTH`.
|
||||
|
||||
---
|
||||
|
||||
## Cloudflare Tunnel example
|
||||
|
||||
ingress:
|
||||
- hostname: greencoast.example.com
|
||||
service: http://shard-test:9082
|
||||
- hostname: api-gc.greencoast.example.com
|
||||
service: http://shard-test:9080
|
||||
- service: http_status:404
|
||||
|
||||
Use “Full (strict)” TLS and ensure your cert covers both hosts.
|
||||
|
||||
---
|
||||
|
||||
## Client usage
|
||||
|
||||
- **Shard URL**: set it in the top “Connect” section (or use `?api=` query or `<meta name="gc-api-base">`).
|
||||
- **Device key sign-in (no OAuth)**:
|
||||
1) Client generates/stores a P-256 device key in the browser.
|
||||
2) Client calls `/v1/auth/key/challenge` then `/v1/auth/key/verify` to obtain a **gc2** token bound to that key.
|
||||
- **Discord SSO (optional)**:
|
||||
- Requires `GC_DISCORD_CLIENT_*` env vars and a valid `GC_DISCORD_REDIRECT_URI`.
|
||||
- Uses PKCE (`S256`) and binds the minted **gc2** token to the device key presented at `/start`.
|
||||
|
||||
---
|
||||
|
||||
## API (overview)
|
||||
|
||||
- `GET /healthz` – liveness
|
||||
- `PUT /v1/object` – upload blob (headers: optional `X-GC-Private: 1`, `X-GC-TZ`)
|
||||
- `GET /v1/object/{hash}` – download blob
|
||||
- `DELETE /v1/object/{hash}` – delete blob
|
||||
- `GET /v1/index` – list indexed entries (latest first)
|
||||
- `GET /v1/index/stream` – SSE updates
|
||||
- `POST /v1/admin/reindex` – rebuild index from disk
|
||||
- **Auth**
|
||||
- `POST /v1/auth/key/challenge` → `{nonce, exp}`
|
||||
- `POST /v1/auth/key/verify` `{nonce, alg, pub, sig}` → `{bearer, sub, exp}`
|
||||
- `POST /v1/auth/discord/start` (requires `X-GC-3P-Assent: 1` and `X-GC-Key`)
|
||||
- `GET /v1/auth/discord/callback` → redirects with `#bearer=…`
|
||||
- **GDPR**
|
||||
- `GET /v1/gdpr/policy` – current data-handling posture
|
||||
|
||||
> When `GC_REQUIRE_POP=true`, all authenticated endpoints require PoP headers.
|
||||
|
||||
### PoP header format (pseudocode)
|
||||
|
||||
Authorization: Bearer gc2.<claims>.<sig>
|
||||
X-GC-Key: p256:<base64-raw> # or ed25519:<base64-raw>
|
||||
X-GC-TS: <unix seconds>
|
||||
X-GC-Proof: base64(
|
||||
Sign_device_key(
|
||||
UPPER(METHOD) + "\n" + URL + "\n" + X-GC-TS + "\n" + SHA256(body)
|
||||
)
|
||||
)
|
||||
|
||||
---
|
||||
|
||||
## Storage layout & migration
|
||||
|
||||
- **Writes** are flat: `objects/<hash>`
|
||||
- **Reads** (and reindex) also support:
|
||||
- `objects/<hash>/blob|data|content`
|
||||
- `objects/<hash>/<single file>`
|
||||
- `objects/<prefix>/<hash>` (two-level prefix)
|
||||
- To **restore** data into a fresh container:
|
||||
1) Mount your objects at `/var/lib/greencoast/objects`
|
||||
2) Call `POST /v1/admin/reindex` (with auth+PoP or enable dev unauth briefly)
|
||||
|
||||
---
|
||||
|
||||
## Reindex examples
|
||||
|
||||
Unauth (dev only):
|
||||
|
||||
curl -X POST https://api-gc.yourdomain/v1/admin/reindex
|
||||
|
||||
With bearer + PoP (placeholders):
|
||||
|
||||
curl -X POST https://api-gc.yourdomain/v1/admin/reindex ^
|
||||
-H "Authorization: Bearer <gc2_token>" ^
|
||||
-H "X-GC-Key: p256:<base64raw>" ^
|
||||
-H "X-GC-TS: <unix>" ^
|
||||
-H "X-GC-Proof: <base64sig>"
|
||||
|
||||
---
|
||||
|
||||
## Hardening checklist (prod)
|
||||
|
||||
- Set `GC_REQUIRE_POP=true`, remove dev bypass.
|
||||
- Keep access token TTL ≤ 8h; rotate signing key periodically.
|
||||
- Static client served with strong CSP (already enabled).
|
||||
- Containers run non-root, read-only FS, `no-new-privileges`, `cap_drop: ["ALL"]`.
|
||||
- Edge WAF/rate limits; 10 MiB default request cap (tunable).
|
||||
- Commit `go.sum`; run `go mod verify` in CI.
|
||||
|
||||
---
|
||||
|
||||
## GDPR
|
||||
|
||||
- Server stores **no PII** (no emails, no IP/UA logs).
|
||||
- Timestamps are UTC (or coarse UTC if enabled).
|
||||
- `/v1/gdpr/policy` exposes current posture.
|
||||
- Roadmap: `/v1/gdpr/export` and `/v1/gdpr/delete` to enumerate/remove blobs signed by a given key.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under **The Unlicense**. See `LICENSE` for details.
|
||||
|
||||
```bash
|
||||
git clone <your repo> greencoast
|
||||
cd greencoast
|
||||
cp .env.example .env
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
# Health:
|
||||
curl -s http://localhost:8080/healthz
|
||||
# Put an object (dev mode allows unauthenticated PUT/GET):
|
||||
curl -s -X PUT --data-binary @README.md http://localhost:8080/v1/object
|
||||
# -> {"ok":true,"hash":"<sha256>",...}
|
||||
curl -s http://localhost:8080/v1/object/<sha256> | head
|
||||
|
Reference in New Issue
Block a user