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

toml
[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étodoRutaAuthDescripción
GET/—Información del servidor Blossom
GET / HEAD/<sha256>[.ext]—Obtener / sondear un blob (rangos de bytes, 206)
PUT/uploadkind 24242 (t=upload, x=sha256, expiration)Subir un blob — 201 nuevo, 200 ya existe
HEAD/uploadkind 24242 (t=upload, x=sha256, expiration)Pre-vuelo BUD-06 — ¿se aceptaría la subida?
PUT/mediakind 24242 (t=media, x=sha256, expiration)Subida multimedia BUD-05 (almacenada tal cual)
HEAD/mediakind 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: attachment y 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-256 se 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 banpubkey es rechazada en cada endpoint.

Ejemplo

sh
# 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]:

toml
[blossom]
host = "media.example.com"
restrict_uploads = true

La lista de permitidos vive en la base de datos del relé (LMDB), gestionada con comandos dedicados — sin reinicio necesario, el demonio recarga automáticamente:

sh
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_uploads

Las 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.