Файловый сервер Blossom

Хостинг медиа на отдельном имени хоста: загрузки с адресацией по содержимому, локальное или S3-совместимое хранилище и авторизация kind-24242 для вашего Nostr-релея.

Обзор

nostrfy может работать как сервер блобов Blossom: клиенты загружают файлы, адресуемые по их 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, если хост не является loopback (например, локальный MinIO для тестирования).

Структура хранилища

Оба бэкенда используют иерархию <npub1...> с ключом по SHA-256 файла:

  • local — файлы в <local_path>/<npub1...>/<sha256>
  • s3 / R2 — объекты <npub1...>/<sha256> в настроенном бакете

Байты блобов никогда не попадают в базу данных релея — LMDB хранит только отображение sha256 → владелец и список разрешённых загрузок.

Конечные точки

МетодПутьАвторизацияОписание
GET/—Информация о сервере Blossom
GET / HEAD/<sha256>[.ext]—Получение / проверка блоба (байтовые диапазоны, 206)
PUT/uploadkind 24242 (t=upload, x=sha256, expiration)Загрузка блоба — 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)Блобы, загруженные запрашивающим ключом (курсор + limit)
DELETE/<sha256>kind 24242 (t=delete, x=sha256, expiration)Удаление блоба (только загрузивший)

Заметки о безопасности

  • Загруженные пользователями байты отдаются с 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 и сохранённым типом содержимого.
  • Pubkey, заблокированный через NIP-86 banpubkey, отклоняется на каждой конечной точке.

Пример

sh
# Информация о сервере
curl https://media.example.com/

# Загрузка (auth-событие из вашего Blossom-клиента, например через nak или blossom-хелпер nostr-tools)
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>

# Список своих загрузок (auth-событие с t=list; pubkey в пути должен быть вашим)
curl -H "Authorization: Nostr <auth>" https://media.example.com/list/<pubkey-hex>

# Удаление (auth-событие с t=delete и x=<sha256>)
curl -X DELETE -H "Authorization: Nostr <auth>" https://media.example.com/<sha256>

Ограничение загрузок

Установите restrict_uploads = true в секции [blossom]:

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.

Резервные копии и миграция

Создавайте резервные копии как настроенного хранилища блобов, так и database.path, чтобы сохранить полную инвентаризацию и состояние авторизации. Отображение sha256 → владелец сохраняется в LMDB, поэтому перезапуски мгновенны и не требуют индекса в памяти или сканирования при старте — поиск читает отображение напрямую из базы данных. Автоматическая одноразовая миграция перестраивает отображение из устаревших блобов при первом запуске после обновления; маркер пропускает последующие перезапуски.