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
[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étodo | Caminho | Auth | Descrição |
|---|---|---|---|
GET | / | — | Informação do servidor Blossom |
GET / HEAD | /<sha256>[.ext] | — | Buscar / sondar um blob (faixas de bytes, 206) |
PUT | /upload | kind 24242 (t=upload, x=sha256, expiration) | Enviar um blob — 201 novo, 200 se já existe |
HEAD | /upload | kind 24242 (t=upload, x=sha256, expiration) | Pré-verificação BUD-06 — o upload seria aceito? |
PUT | /media | kind 24242 (t=media, x=sha256, expiration) | Upload de mídia BUD-05 (armazenado como recebido) |
HEAD | /media | kind 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: attachmente 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
# 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]:
[blossom]
host = "media.example.com"
restrict_uploads = trueA allowlist fica no banco de dados do relay (LMDB) e é gerenciada com comandos dedicados — sem precisar reiniciar, o daemon recarrega automaticamente:
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_uploadsUploads 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.