ssmithxandClaude Sonnet 5 e92ef7fef9 feat: Cashu marketplace for paid episodes, resale, and cross-instance discovery
Paid episodes:
- server/services/cashu.ts: self-custodied Cashu wallet against a configured
  mint (NUT-04 mint quote -> bolt11 invoice -> mint/store proofs -> LNURL-pay
  payout on withdraw). Buyers pay a plain Lightning invoice, no Cashu wallet
  needed on their end.
- routes/marketplace.ts: purchase/confirm flow, download-url paywall gate,
  reseller certification/revocation, earnings ledger + withdraw.
- services/marketplace.ts: producer + certified-reseller source resolution,
  with a naive per-seller sales-count reputation signal.

Discovery:
- services/rss.ts: <podsteadr:source> RSS tag on priced episodes (producer +
  resellers, price, sales count, url) so pricing/sources are discoverable
  straight from the feed, not just a separate API call. Locked episodes point
  their <enclosure> at an info page instead of the raw file.
- routes/feeds.ts: /catalog.opml lists every podcast this instance hosts, for
  peer podsteadr servers or any OPML-aware crawler to discover without a
  central directory.

Frontend: episode wizard price/reseller controls, sources display, earnings
dashboard.

Also fixes CORS and a container-image reference:
- app.ts: register @fastify/cors, open on the catalog/feed/sources endpoints
  (already deliberately public/crawlable) and on purchase/confirm (already
  accept stateless NIP-98 header auth for exactly this case). Credentials
  stay off, so the session cookie never crosses origins — cookie-authed
  admin routes stay same-origin-only. Needed so external clients like
  podsteadr-player can browse/buy/play from a different origin.
- docker-compose.yml: fully qualify the mediamtx image reference
  (docker.io/bluenviron/mediamtx:1.19.2) — some Podman hosts have no
  unqualified-search registry configured and fail to resolve short names.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 12:43:46 +00:00

podsteadr

Self-hosted, nostr-native podcast publishing and livestreaming.

  • Log in with nostr — NIP-07 browser extension (Alby, nos2x, …); the server verifies NIP-98 signed requests. No passwords, no email.
  • Upload an mp4 → it is stored on a Blossom server (bundled, or point at any external one) and published in an RSS 2.0 podcast feed with Podcasting 2.0 <podcast:value> lightning-address payment info.
  • Go live — stream from OBS (RTMP) or straight from the browser (WebRTC/WHIP) through MediaMTX; viewers watch via HLS and the stream is announced on nostr as a NIP-53 (kind 30311) live event.
  • Publish recordings — live streams are recorded; one click remuxes and publishes a recording as a podcast episode.

Quick start

cp .env.example .env   # edit PUBLIC_HOST if not localhost
docker compose up --build -d

Then open http://localhost:8095, log in with a NIP-07 extension, and follow the wizard.

Services / ports

Service Host port Purpose
podsteadr 8095 Web UI + API + RSS feeds
MediaMTX 1935 RTMP ingest (OBS)
MediaMTX 8889 (+8189/udp) WebRTC/WHIP ingest (browser)
MediaMTX 8890 HLS playback
blossom-server 8098 Media blob storage (sha256-addressed)

Streaming with OBS

Create a stream in the UI; it gives you:

  • Server: rtmp://<host>:1935/live
  • Stream key: <streamId>?key=<secret>

The HLS playback URL (http://<host>:8890/live/<streamId>/index.m3u8) is public and never contains the secret.

Development

docker compose up mediamtx blossom -d   # backends
cd server   && npm install && npm run dev   # API on :8095
cd frontend && npm install && npm run dev   # Vite on :5173, proxies /api + /feeds

Tests: cd server && npm test.

Data

Everything lives in named docker volumes: podsteadr-data (SQLite, server nostr key, covers), mediamtx-recordings (stream recordings, 7-day retention), blossom-data (media blobs). docker compose down keeps them; down -v wipes them.

Verification

  • Unit tests: cd server && npm test (35 tests).
  • Against a running stack: node scripts/e2e.mjs (14 checks: login, blossom upload, feed, stream keys, MediaMTX auth webhook).

Status & internals

See docs/STATUS.md for the architecture, the full verification record, known gotchas (blossom v4 config nesting, no range requests, split-horizon blossom URL, MediaMTX polling rationale), and the remaining-work list (Archipelago packaging, git remote).

S
Description
No description provided
Readme
240 KiB
Languages
TypeScript 69.6%
Vue 24.8%
JavaScript 4.3%
Dockerfile 0.6%
CSS 0.6%
Other 0.1%