Servidor de arquivos Blossom

Hospedagem de mídia no seu próprio hostname: uploads endereçados por conteúdo, armazenamento local ou compatível com S3 e autenticação kind-24242 para o seu relay Nostr.

Visão geral

O nostrfy pode atuar como servidor de blobs Blossom: os clientes enviam arquivos endereçados pelo hash SHA-256, e o relay os serve de volta. Como a API REST, ele fica em um hostname dedicado na mesma porta.

Configuração

toml
[blossom]
host = "media.example.com"          # obrigatório — ativa o recurso
storage = "local"                   # "local" ou "s3"
local_path = "./data/images"        # raiz do armazenamento local
max_upload_bytes = 20971520         # 20 MiB
min_free_bytes = 33554432           # recusar uploads quando o disco tiver menos espaço livre
restrict_uploads = false            # só pubkeys na allowlist podem enviar

# Para S3 / Cloudflare R2:
s3_endpoint = "https://<account>.r2.cloudflarestorage.com"
s3_region = "auto"
s3_bucket = "nostr-media"
s3_access_key = "..."
s3_secret_key = "..."

Aponte media.example.com para a mesma porta no seu proxy reverso e reinicie. Um GET / nesse host responde com o documento de informações do servidor Blossom. Com storage = "s3" o endpoint precisa ser HTTPS, a menos que o host seja loopback (por ex. um MinIO local para testes).

Esquema de armazenamento

Ambos os backends usam a hierarquia <npub1...>, indexada pelo SHA-256 do arquivo:

  • local — arquivos em <local_path>/<npub1...>/<sha256>
  • s3 / R2 — objetos <npub1...>/<sha256> no bucket configurado

Os bytes dos blobs nunca passam pelo banco de dados do relay — o LMDB guarda apenas o mapeamento sha256 → dono e a allowlist de upload.

Endpoints

MétodoCaminhoAuthDescrição
GET/—Informação do servidor Blossom
GET / HEAD/<sha256>[.ext]—Buscar / sondar um blob (faixas de bytes, 206)
PUT/uploadkind 24242 (t=upload, x=sha256, expiration)Enviar um blob — 201 novo, 200 se já existe
HEAD/uploadkind 24242 (t=upload, x=sha256, expiration)Pré-verificação BUD-06 — o upload seria aceito?
PUT/mediakind 24242 (t=media, x=sha256, expiration)Upload de mídia BUD-05 (armazenado como recebido)
HEAD/mediakind 24242 (t=media, x=sha256, expiration)Pré-verificação BUD-05 — o upload seria aceito?
GET/list/<pubkey>kind 24242 (t=list, expiration)Blobs enviados pela pubkey solicitante (cursor + limit)
DELETE/<sha256>kind 24242 (t=delete, x=sha256, expiration)Excluir um blob (só quem enviou)

Notas de segurança

  • Os bytes enviados pelos usuários são servidos com X-Content-Type-Options: nosniff.
  • HTML/SVG/XML/JavaScript recebem ainda Content-Disposition: attachment e uma CSP de sandbox, então a origem de mídia não pode ser usada para XSS armazenado.
  • Os tokens são aceitos tanto na forma base64url da especificação (sem preenchimento) quanto na forma padrão com preenchimento (BUD-11).
  • O cabeçalho X-SHA-256 é comparado aos bytes reais — uma divergência retorna 409.
  • Os arquivos são servidos com ETag, Cache-Control: immutable e o tipo de conteúdo armazenado.
  • Uma pubkey banida com NIP-86 banpubkey é recusada em todos os endpoints.

Exemplo

sh
# Informações do servidor
curl https://media.example.com/

# Upload (evento de auth do seu cliente Blossom, ex. via nak ou o helper blossom do nostr-tools)
curl -X PUT -H "Authorization: Nostr <auth>" -H "Content-Type: image/png" --data-binary @photo.png https://media.example.com/upload

# Baixar
curl https://media.example.com/<sha256>

# Listar seus próprios uploads (evento de auth com t=list; a pubkey do caminho precisa ser a sua)
curl -H "Authorization: Nostr <auth>" https://media.example.com/list/<pubkey-hex>

# Excluir (evento de auth com t=delete e x=<sha256>)
curl -X DELETE -H "Authorization: Nostr <auth>" https://media.example.com/<sha256>

Restringir uploads

Defina restrict_uploads = true na seção [blossom]:

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

A allowlist fica no banco de dados do relay (LMDB) e é gerenciada com comandos dedicados — sem precisar reiniciar, o daemon recarrega automaticamente:

sh
nostrfy blossom allow npub1...          # permitir uma pubkey (npub1... ou hex)
nostrfy blossom deny npub1...           # remover uma pubkey
nostrfy blossom list                    # mostrar a lista e o restrict_uploads

Uploads de pubkeys fora da lista são recusados com 403.

Backups e migração

Faça backup do armazenamento de blobs configurado e de database.path para preservar o inventário completo e o estado de autorização. O mapeamento sha256 → dono é persistido em LMDB, então as reinicializações são instantâneas e não é preciso índice em memória nem varredura na inicialização — as buscas leem o mapeamento direto do banco de dados. Uma migração automática única reconstrói o mapeamento a partir dos blobs antigos na primeira inicialização após uma atualização; um marcador evita repetições nas reinicializações seguintes.