Files
ssmithxandClaude Opus 5.5 3215da07ed feat: installable FIPS-aware player for any podcast RSS feed
The player now takes any RSS feed URL as well as a podsteadr server,
and plays podsteadr feeds hosted on the FIPS mesh.

A browser can't speak FIPS, and an HTTPS page can't fetch or play
http:// URLs, so .fips URLs are routed one of two ways (src/lib/net.ts):
- served over the mesh (http://<npub>.fips:8097/), the page fetches
  .fips URLs directly over the device's own FIPS connection;
- served over HTTPS (player.atobitcoin.io), .fips URLs are rewritten to
  the host's /fips/<host>/<port>/<path> gateway, which relays only
  podsteadr feeds (8095) and blossom blobs (8098) over the host's FIPS
  link (deploy/player-domain.conf).

Also:
- "Add" accepts a feed, an OPML catalog, or a server address and works
  out which; old saved servers migrate to the new subscription format
- PWA: manifest, icons, service worker caching only the app shell
  (never feeds, audio or the gateway), install button, safe areas
- lock-screen controls and artwork via the Media Session API
- artwork, dates and durations in the episode list
- no secure-context-only APIs on the mesh copy (randomUUID, clipboard)
- buying episodes over FIPS is refused with a clear message: NIP-98
  signs the server's public URL, which a .fips address never matches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:16:38 +00:00

96 lines
3.7 KiB
Plaintext

# podsteadr-player on its own HTTPS domain, plus the FIPS gateway.
#
# Install: /etc/nginx/sites-available/player-domain, symlinked into sites-enabled.
# Certificate: certbot certonly --nginx -d player.atobitcoin.io (needs the port-80
# block live first — see deploy/README.md).
#
# The player itself runs in the podsteadr-player container on 127.0.0.1:8097. That
# container is also published on the FIPS mesh (http://<npub>.fips:8097/), where it
# fetches .fips feeds directly; this HTTPS copy can't (browsers block http:// from
# https:// pages), so it uses the /fips/ gateway below instead.
limit_req_zone $binary_remote_addr zone=player_fips_feeds:10m rate=5r/s;
limit_conn_zone $binary_remote_addr zone=player_fips_conns:10m;
server {
listen 80;
listen [::]:80;
server_name player.atobitcoin.io;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name player.atobitcoin.io;
ssl_certificate /etc/letsencrypt/live/player.atobitcoin.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/player.atobitcoin.io/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy no-referrer always;
location / {
proxy_pass http://127.0.0.1:8097;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ---- FIPS gateway ------------------------------------------------------------
# /fips/<host>.fips/<port>/<path> -> http://<host>.fips:<port>/<path>, over this
# node's own FIPS link. Deliberately NOT a general mesh proxy: anyone on the
# internet can call it, and mesh peers expose things (node UIs on :80, the peer
# API on :5679) that must never be reachable from here. Only podcast content
# passes: podsteadr catalogs/feeds on 8095 and blossom blobs on 8098, GET/HEAD
# only, no client cookies or credentials forwarded.
#
# .fips names resolve via the fips daemon's DNS on 127.0.0.1:5354 (AAAA only).
# Not via systemd-resolved: on this node it routes ~fips to [::1]:5354, where the
# daemon isn't listening.
location ~ "^/fips/(?<fips_host>(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+fips)/8095/(?<fips_path>catalog\.opml|feeds/[A-Za-z0-9_-]+/feed\.xml)$" {
limit_except GET { deny all; }
limit_req zone=player_fips_feeds burst=20 nodelay;
resolver 127.0.0.1:5354 ipv4=off valid=60s;
resolver_timeout 5s;
# Host is what makes podsteadr write mesh (.fips) enclosure URLs into the feed.
proxy_pass http://$fips_host:8095/$fips_path;
proxy_set_header Host $fips_host:8095;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
proxy_connect_timeout 20s;
proxy_read_timeout 30s;
}
location ~ "^/fips/(?<fips_host>(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+fips)/8098/(?<fips_path>[0-9a-f]{64}(?:\.[a-z0-9]{1,8})?)$" {
limit_except GET { deny all; }
limit_conn player_fips_conns 8;
resolver 127.0.0.1:5354 ipv4=off valid=60s;
resolver_timeout 5s;
# Range/If-Range pass through untouched, so seeking works; stream, don't buffer.
proxy_pass http://$fips_host:8098/$fips_path;
proxy_set_header Host $fips_host:8098;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
proxy_http_version 1.1;
proxy_buffering off;
proxy_connect_timeout 20s;
proxy_read_timeout 120s;
}
location /fips/ {
return 404;
}
}