Servidor de archivos Blossom
Alojamiento multimedia en su propio nombre de host: subidas direccionadas por contenido, almacenamiento local o compatible con S3, y autenticación kind-24242 para tu relé Nostr.
Resumen
nostrfy puede actuar como servidor de blobs Blossom: los clientes suben archivos direccionados por su hash SHA-256, y el relé los sirve de vuelta. Como la API REST, vive en un nombre de host dedicado en el mismo puerto.
Configuración
[blossom]
host = "media.example.com" # obligatorio — activa la función
storage = "local" # "local" o "s3"
local_path = "./data/images" # raíz del almacenamiento local
max_upload_bytes = 20971520 # 20 MiB
min_free_bytes = 33554432 # rechaza subidas cuando al disco le queda menos espacio libre
restrict_uploads = false # solo las pubkeys de la lista de permitidos pueden subir
# Para S3 / Cloudflare R2:
s3_endpoint = "https://<account>.r2.cloudflarestorage.com"
s3_region = "auto"
s3_bucket = "nostr-media"
s3_access_key = "..."
s3_secret_key = "..."Apunta media.example.com al mismo puerto en tu proxy inverso y reinicia. GET /
en ese host responde con el documento de información del servidor Blossom. Con storage = "s3" el
endpoint debe ser HTTPS a menos que el host sea loopback (p. ej. un MinIO local para pruebas).
Diseño del almacenamiento
Ambos backends usan la jerarquía <npub1...>, indexada por el SHA-256 del archivo:
- local — archivos bajo
<local_path>/<npub1...>/<sha256> - s3 / R2 — objetos
<npub1...>/<sha256>en el bucket configurado
Los bytes de los blobs nunca tocan la base de datos del relé — LMDB solo guarda el mapeo sha256 → propietario y la lista de permitidos de subida.
Endpoints
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
GET | / | — | Información del servidor Blossom |
GET / HEAD | /<sha256>[.ext] | — | Obtener / sondear un blob (rangos de bytes, 206) |
PUT | /upload | kind 24242 (t=upload, x=sha256, expiration) | Subir un blob — 201 nuevo, 200 ya existe |
HEAD | /upload | kind 24242 (t=upload, x=sha256, expiration) | Pre-vuelo BUD-06 — ¿se aceptaría la subida? |
PUT | /media | kind 24242 (t=media, x=sha256, expiration) | Subida multimedia BUD-05 (almacenada tal cual) |
HEAD | /media | kind 24242 (t=media, x=sha256, expiration) | Pre-vuelo BUD-05 — ¿se aceptaría la subida? |
GET | /list/<pubkey> | kind 24242 (t=list, expiration) | Blobs subidos por la pubkey solicitante (cursor + limit) |
DELETE | /<sha256> | kind 24242 (t=delete, x=sha256, expiration) | Eliminar un blob (solo el que lo subió) |
Notas de seguridad
- Los bytes subidos por usuarios se sirven con
X-Content-Type-Options: nosniff. - HTML/SVG/XML/JavaScript reciben además
Content-Disposition: attachmenty un CSP sandbox, para que el origen multimedia no pueda usarse para XSS almacenado. - Los tokens se aceptan tanto en la forma base64url de la especificación (sin relleno) como en la forma estándar con relleno (BUD-11).
- La cabecera
X-SHA-256se verifica contra los bytes reales — una discrepancia devuelve 409. - Los archivos se sirven con ETag, Cache-Control: immutable y el tipo de contenido almacenado.
- Una pubkey vetada con NIP-86
banpubkeyes rechazada en cada endpoint.
Ejemplo
# Información del servidor
curl https://media.example.com/
# Subida (evento de auth de tu cliente Blossom, p. ej. con nak o el helper blossom de nostr-tools)
curl -X PUT -H "Authorization: Nostr <auth>" -H "Content-Type: image/png" --data-binary @photo.png https://media.example.com/upload
# Obtener
curl https://media.example.com/<sha256>
# Lista tus propias subidas (evento de auth con t=list; la pubkey de la ruta debe ser la tuya)
curl -H "Authorization: Nostr <auth>" https://media.example.com/list/<pubkey-hex>
# Eliminar (evento de auth con t=delete y x=<sha256>)
curl -X DELETE -H "Authorization: Nostr <auth>" https://media.example.com/<sha256>Restringir subidas
Establece restrict_uploads = true en la sección [blossom]:
[blossom]
host = "media.example.com"
restrict_uploads = trueLa lista de permitidos vive en la base de datos del relé (LMDB), gestionada con comandos dedicados — sin reinicio necesario, el demonio recarga automáticamente:
nostrfy blossom allow npub1... # permite una pubkey (npub1... o hex)
nostrfy blossom deny npub1... # revoca una pubkey
nostrfy blossom list # muestra la lista y restrict_uploadsLas subidas de pubkeys no listadas se rechazan con 403.
Copias de seguridad y migración
Respalda tanto el almacenamiento de blobs configurado como database.path para preservar el inventario completo
y el estado de autorización. El mapeo sha256 → propietario persiste en LMDB, así que los reinicios son
instantáneos y no se necesita índice en memoria ni escaneo de inicio — las búsquedas leen el mapeo directamente de
la base de datos. Una migración automática única reconstruye el mapeo desde los blobs heredados en el primer
arranque tras una actualización; un marcador omite reinicios posteriores.