Blossom ファイルサーバー

独自ホスト名でのメディアホスティング:コンテンツアドレス指定のアップロード、ローカルまたはS3互換ストレージ、Nostrリレー向けの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" の場合、ホストがループバック(例:テスト用のローカル MinIO)でない限り、エンドポイントは HTTPS である必要があります。

ストレージ構成

どちらのバックエンドも、ファイルの SHA-256 をキーとする <npub1...> 階層を使用します:

  • 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からマッピングを再構築する自動のワンタイム移行が実行されます。以降の再起動ではマーカーによりスキップされます。