Blossom 文件服务器

运行在独立主机名上的媒体托管:内容寻址上传、本地或 S3 兼容存储,以及 kind-24242 认证。

概览

nostrfy 可以作为 Blossom blob 服务器:客户端上传以其 SHA-256 哈希寻址的文件, 中继将它们提供回去。与 REST API 一样,它运行在同一端口上的专用主机名。

配置

toml
[blossom]
host = "media.example.com"          # 必需 — 启用该功能
storage = "local"                   # "local" 或 "s3"
local_path = "./data/images"        # 本地存储根目录
max_upload_bytes = 20971520         # 即 20 MiB
min_free_bytes = 33554432           # 磁盘剩余空间不足时拒绝上传
restrict_uploads = false            # 仅白名单公钥可上传

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

在反向代理中将 media.example.com 指向同一端口,然后重启。该主机上的 GET / 会返回 Blossom 服务器信息文档。使用 storage = "s3" 时, 端点必须是 HTTPS,除非主机是回环地址(例如用于测试的本地 MinIO)。

存储布局

两种后端都使用 <npub1...> 层级,以文件的 SHA-256 为键:

  • local — 文件位于 <local_path>/<npub1...>/<sha256>
  • s3 / R2 — 配置的存储桶中的对象 <npub1...>/<sha256>

Blob 字节从不进入中继数据库 — LMDB 只保存 sha256 → 所有者映射和上传 白名单。

端点

方法路径认证说明
GET/—Blossom 服务器信息
GET / HEAD/<sha256>[.ext]—获取 / 探测 blob(字节范围,206)
PUT/uploadkind 24242 (t=upload, x=sha256, expiration)上传 blob — 201 新建,200 已存在
HEAD/uploadkind 24242 (t=upload, x=sha256, expiration)BUD-06 预检 — 上传会被接受吗?
PUT/mediakind 24242 (t=media, x=sha256, expiration)BUD-05 媒体上传(原样存储)
HEAD/mediakind 24242 (t=media, x=sha256, expiration)BUD-05 预检 — 上传会被接受吗?
GET/list/<pubkey>kind 24242 (t=list, expiration)由请求公钥上传的 blob(游标 + limit)
DELETE/<sha256>kind 24242 (t=delete, x=sha256, expiration)删除 blob(仅上传者)

安全说明

  • 用户上传的字节以 X-Content-Type-Options: nosniff 提供。
  • HTML/SVG/XML/JavaScript 还会附加 Content-Disposition: attachment 和 沙箱 CSP,因此媒体源不能用于存储型 XSS。
  • 令牌接受规范中的 base64url(无填充)形式和带填充的标准形式 (BUD-11)。
  • X-SHA-256 头会与实际字节验证 — 不匹配返回 409。
  • 文件以 ETag、Cache-Control: immutable 和存储的内容类型提供。
  • 被 NIP-86 banpubkey 封禁的公钥在每个端点上都会被拒绝。

示例

sh
# 服务器信息
curl https://media.example.com/

# 上传(来自 Blossom 客户端的认证事件,例如通过 nak 或 nostr-tools 的 blossom 辅助工具)
curl -X PUT -H "Authorization: Nostr <auth>" -H "Content-Type: image/png" --data-binary @photo.png https://media.example.com/upload

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

# 列出自己的上传(携带 t=list 的认证事件;路径中的公钥必须是你的)
curl -H "Authorization: Nostr <auth>" https://media.example.com/list/<pubkey-hex>

# 删除(携带 t=delete 和 x=<sha256> 的认证事件)
curl -X DELETE -H "Authorization: Nostr <auth>" https://media.example.com/<sha256>

限制上传

在 [blossom] 段中设置 restrict_uploads = true:

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

白名单存储在中继数据库(LMDB)中,用专用命令管理 — 无需重启, 守护进程会自动重载:

sh
nostrfy blossom allow npub1...          # 允许某个公钥(npub1... 或 hex)
nostrfy blossom deny npub1...           # 撤销某个公钥
nostrfy blossom list                    # 显示列表和 restrict_uploads

未列入白名单的公钥上传会被 403 拒绝。

备份与迁移

备份配置的 blob 存储和 database.path,以保留完整的 清单和授权状态。sha256 → 所有者映射持久化在 LMDB 中,因此重启是 瞬时的,无需内存索引或启动扫描 — 查找直接从数据库读取映射。升级后的首次启动会 自动执行一次性迁移,从旧 blob 重建映射;标记会跳过后续重启。