A Blossom media server for Nostr running on Fastly Compute, optimized for video content.
Client → Fastly Compute (Rust WASM) → GCS (blobs) + Fastly KV (metadata)
├── Cloud Run Upload (Rust) → GCS + Transcoder trigger
├── Cloud Run Transcoder (Rust, NVIDIA GPU) → HLS segments to GCS
└── Cloud Logging (audit trail)
- Fastly Compute Edge (
src/) - Rust WASM service on Fastly. Handles uploads, metadata KV, HLS proxying, admin, provenance - Cloud Run Upload (
cloud-run-upload/) - Rust service on GCP. Receives video bytes, sanitizes (ffmpeg -c copy), hashes, uploads to GCS, triggers transcoder, receives audit logs - Cloud Run Transcoder (
cloud-run-transcoder/) - Rust service on GCP with NVIDIA GPU. Downloads from GCS, transcodes to HLS via FFmpeg NVENC, uploads segments back - GCS bucket:
divine-blossom-media - CDN:
media.divine.video(Fastly)
- BUD-01: Blob retrieval (GET/HEAD)
- BUD-02: Upload/delete/list management
- BUD-03: User server list support
- Nostr auth: Kind 24242 signature validation (Schnorr signatures)
- Shadow restriction: Moderated content only visible to owner
- Range requests: Native video seeking support
- HLS transcoding: Multi-quality adaptive streaming (1080p, 720p, 480p, 360p)
- WebVTT transcripts: Stable transcript URL at
/<sha256>.vttwith async generation - Provenance & audit: Cryptographic proof of upload/delete authorship with Cloud Logging audit trail
- Tombstones: Legal hold prevents re-upload of removed content
- Admin soft-delete: DMCA/legal removal with full audit trail while preserving recoverable storage
- Admin restore: Re-index and restore previously soft-deleted blobs
- Fastly CLI
- Rust with wasm32-wasi target
- GCP project with GCS bucket and Cloud Run
- Fastly account with Compute enabled
rustup target add wasm32-wasi- Create a GCS bucket with HMAC credentials
- Set up Fastly stores:
# Create KV store
fastly kv-store create --name blossom_metadata
# Create config store
fastly config-store create --name blossom_config
# Create secret store with GCS HMAC credentials
fastly secret-store create --name blossom_secrets# Copy the example config and fill in your credentials
cp fastly.toml.example fastly.toml
# Edit fastly.toml with your GCS credentials (this file is gitignored)
# Then run:
fastly compute serveNote: fastly.toml is gitignored to prevent accidentally committing secrets. The [local_server.secret_stores] section is only used for local testing.
fastly compute publish| Method | Path | Description |
|---|---|---|
GET |
/<sha256>[.ext] |
Retrieve blob |
HEAD |
/<sha256>[.ext] |
Check blob exists |
GET |
/<sha256>.vtt |
Retrieve WebVTT transcript (on-demand generation) |
HEAD |
/<sha256>.vtt |
Check transcript status/existence |
GET |
/<sha256>/VTT |
Alias for transcript retrieval |
| Method | Path | Description |
|---|---|---|
POST |
/v1/subtitles/jobs |
Create subtitle job (video_sha256, optional lang, optional force) |
GET |
/v1/subtitles/jobs/<job_id> |
Get subtitle job status (queued, processing, ready, failed) |
GET |
/v1/subtitles/by-hash/<sha256> |
Idempotent hash lookup for existing subtitle job |
| Method | Path | Auth | Description |
|---|---|---|---|
PUT |
/upload |
Required | Upload blob |
HEAD |
/upload |
None | Get upload requirements |
DELETE |
/<sha256> |
Required | Permanently delete your own blob |
GET |
/list/<pubkey> |
Optional | List user's blobs |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/<sha256>/provenance |
None | Get provenance info (owner, uploaders, auth events) |
POST |
/admin/api/delete |
Admin | Soft-delete blob, remove it from public serving/indexes, and optionally set legal hold |
POST |
/admin/api/restore |
Admin | Restore a soft-deleted blob to active, pending, or restricted |
Every upload and delete stores the signed Nostr auth event (kind 24242) in KV as cryptographic proof of who authorized the action. The /provenance endpoint returns:
{
"sha256": "abc123...",
"owner": "<nostr_pubkey>",
"uploaders": ["<pubkey1>", "<pubkey2>"],
"upload_auth_event": { ... },
"delete_auth_event": null,
"tombstone": null
}All uploads and deletes are logged to Google Cloud Logging via the Cloud Run upload service. Each audit entry includes: action, SHA-256, actor pubkey, timestamp, the signed auth event, and a metadata snapshot. Logs are queryable via Cloud Logging with labels service=divine-blossom, component=audit.
DELETE /<sha256>is a direct user delete and permanently removes the canonical blob.POST /admin/api/deleteis an admin soft-delete. It marks the blob asdeleted, stops all public serving, removes it from user/recent indexes, and preserves the stored blob so it can be recovered later.POST /admin/api/restorerestores a soft-deleted blob and re-indexes it.legal_hold: truesets a tombstone that prevents re-upload of the same hash even if the stored blob is preserved.
curl -X POST https://media.divine.video/admin/api/delete \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{"sha256": "abc123...", "reason": "DMCA #1234", "legal_hold": true}'curl -X POST https://media.divine.video/admin/api/restore \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{"sha256": "abc123...", "status": "active"}'When legal_hold: true, a tombstone is set preventing re-upload of the removed content (returns 403).
Uses Nostr kind 24242 events:
{
"kind": 24242,
"content": "Upload blob",
"tags": [
["t", "upload"],
["x", "<sha256>"],
["expiration", "<unix_timestamp>"]
]
}Send as: Authorization: Nostr <base64_encoded_signed_event>
MIT