ssmithxandClaude Sonnet 5 e04d35b131 feat: recorded-episode pricing, Cashu-token payment, podcast editing
- Editable podcast settings: new /podcasts/:id/settings page, reusing
  PodcastForm.vue in an edit mode (PUT instead of POST) since it was
  previously create-only with no way to fix a field (e.g. lightning
  address) after the fact.
- Recorded episodes can now be priced same as uploads: "Publish
  recording" gained an optional price_sats field, wired through the
  existing episode paywall machinery. Live streams themselves stay
  unpaywalled by design — only the resulting recording can be priced.
- Accept Cashu tokens as an alternative to a Lightning invoice:
  POST .../purchase/token redeems a pasted token directly (via the
  mint's swap/receive flow) and finalizes the purchase in one step,
  no quote/confirm round trip. A token worth more than the price is
  treated as a tip (seller gets the full amount); worth less is
  rejected. Added a "pay with a Cashu token instead" option next to
  the existing invoice flow.
- cashu.ts: fixed payout() always requesting an invoice for the full
  held balance with no room for the mint's routing-fee reserve, which
  made a balance that exactly matched one sale's price permanently
  unwithdrawable (needed slightly more than held to cover the fee).
  Now shrinks the request and requotes once if the first quote doesn't
  fit.
- docker-compose.yml / mediamtx.yml: renamed the podsteadr container's
  DNS alias away from the literal string "podsteadr" — on a host whose
  own hostname is "podsteadr", cloud-init's self-hostname /etc/hosts
  entry shadowed the container-network alias, so mediamtx's auth
  webhook callback resolved to the wrong address and rejected every
  RTMP publish attempt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 23:18:36 +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
260 KiB
Languages
TypeScript 69.6%
Vue 24.8%
JavaScript 4.3%
Dockerfile 0.6%
CSS 0.6%
Other 0.1%