Files
archy/apps/podsteadr/manifest.yml
T
ssmithx 395e26b524 feat(apps): package podsteadr as a full Archipelago app (3-container manifest set)
Adds apps/podsteadr (main Fastify+Vue app, container.build from the podsteadr
repo), apps/podsteadr-mediamtx (RTMP/WHIP ingest, HLS, recording), and
apps/podsteadr-blossom (BUD-02 media blobs), wired together on a dedicated
podsteadr-net bridge network per the multi-container pattern documented in
docs/app-developer-guide.md (indeedhub's api/relay/minio/redis/postgres
siblings). All podsteadr ports are auth: none with a rationale, since it's a
public podcast/livestream server whose RSS feeds, HLS playback, and blob
reads must stay reachable by third-party clients with no Archipelago session
— the app already gates its own sensitive routes with NIP-98 and per-stream
secret keys.

Also updates apps/PORTS.md, apps/README.md, and bumps the reviewed
unauthenticated-port count in core/container/src/manifest.rs's
unauthenticated_ports_are_all_accounted_for test (25 -> 31) to acknowledge
the six new auth:none ports. Regenerated catalog-derived files
(core/archipelago/src/fips/app_ports.rs,
neode-ui/src/views/appSession/generatedAppSessionConfig.ts) via
scripts/generate-app-catalog.py.

All three manifests pass scripts/validate-app-manifest.sh and
`cargo test -p archipelago-container manifest`.
2026-08-08 00:18:02 +00:00

144 lines
5.2 KiB
YAML

app:
id: podsteadr
name: podsteadr
version: "1.0.0"
description: Self-hosted, nostr-native podcast publishing and livestreaming. Log in with Nostr, upload episodes or go live via OBS/WebRTC, publish to RSS with Podcasting 2.0 lightning payments.
category: media
# Container/DNS-alias name deliberately NOT "podsteadr" — on a host whose own
# hostname happens to be "podsteadr", the host's own /etc/hosts self-hostname
# entry (127.0.1.1, e.g. from cloud-init) shadows the container network's DNS
# alias for other containers looking up "podsteadr", and mediamtx's auth-webhook
# callback resolves to the host's loopback instead of this container — every
# RTMP publish gets rejected with "connection refused" (observed on
# podsteadr.atobitcoin.io, 2026-07-30; see docker-compose.yml in the podsteadr
# repo for the original writeup). Carried forward unchanged into the manifest.
container_name: podsteadr-app
container:
# Built locally from the podsteadr repo (source lives outside this tree —
# see apps/podsteadr/README.md + build-from-prototype.sh), same pattern as
# apps/indeedhub. Not yet pushed to the shared registry; push-to-registry.sh
# is there for when fleet-wide install is needed.
image: localhost/podsteadr:1.0.0
pull_policy: if-not-present
network: podsteadr-net
network_aliases: [podsteadr-app]
derived_env:
- key: PUBLIC_URL
template: "http://{{HOST_MDNS}}:8095"
- key: MEDIAMTX_RTMP_PUBLIC
template: "rtmp://{{HOST_MDNS}}:1935"
- key: MEDIAMTX_WHIP_PUBLIC
template: "http://{{HOST_MDNS}}:8889"
- key: MEDIAMTX_HLS_PUBLIC
template: "http://{{HOST_MDNS}}:8890"
- key: BLOSSOM_URL_DEFAULT
template: "http://{{HOST_MDNS}}:8098"
# node:22-bookworm-slim's built-in `node` user is uid:gid 1000:1000. The
# image's own Dockerfile chowns /data to node:node, but that only affects
# the image layer — the actual runtime mount is the bind volume below, so
# the host directory needs the same ownership or the read-only-root,
# non-root `node` process can't open the SQLite DB (unverified against a
# real node install; flagging per this repo's convention of documenting
# bind-mount ownership assumptions, e.g. apps/botfights/manifest.yml).
data_uid: "1000:1000"
dependencies:
- app_id: podsteadr-mediamtx
- app_id: podsteadr-blossom
- storage: 2Gi
resources:
cpu_limit: 2
memory_limit: 1Gi
disk_limit: 2Gi
security:
capabilities: []
readonly_root: true
no_new_privileges: true
network_policy: isolated
ports:
- host: 8095
container: 8095
protocol: tcp
auth: none
auth_rationale: >-
podsteadr is a public podcast/livestream server: RSS feeds and the
marketplace/catalog API must stay fetchable by third-party podcast
clients, crawlers, and other podsteadr instances with no Archipelago
session, and the app already gates its own sensitive routes with
NIP-98 signed-request auth (see server/src/plugins/nostr-auth.ts in
the podsteadr repo). Putting the node's session gate in front would
block every external RSS/API consumer without adding real protection.
volumes:
- type: bind
source: /var/lib/archipelago/podsteadr
target: /data
options: [rw]
# Shares podsteadr-mediamtx's recordings directory (rw there, ro here) so
# the app can list/remux finished recordings for one-click episode
# publishing without granting it write access to live segments.
- type: bind
source: /var/lib/archipelago/podsteadr/recordings
target: /recordings
options: [ro]
environment:
- NODE_ENV=production
- PORT=8095
- DATA_DIR=/data
- RECORDINGS_DIR=/recordings
- MEDIAMTX_API_URL=http://mediamtx:9997
- BLOSSOM_URL_INTERNAL=http://blossom:3000
- NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://relay.nostr.band
- CASHU_MINT_URL_DEFAULT=https://mint.minibits.cash/Bitcoin
# podsteadr's Fastify server (fastify-static) isn't the nginx-served SPA
# shape the platform auto-patches for NIP-07 injection (see "Nostr Signer
# Bridge" in docs/app-developer-guide.md) — its frontend already
# self-references /nostr-provider.js from index.html and vendors a copy at
# build time (podsteadr commit 133558d). That vendored copy goes stale
# across archy OTA releases, so re-copy the canonical host script over it
# on every install/reinstall instead of trusting the baked-in one.
hooks:
post_install:
- copy_from_host:
src: "web-ui/nostr-provider.js"
dest: /app/public/nostr-provider.js
health_check:
type: http
endpoint: http://localhost:8095
path: /api/health
interval: 30s
timeout: 5s
retries: 3
interfaces:
main:
name: Web UI
description: Podcast dashboard, upload/live wizard, and stream management
type: ui
port: 8095
protocol: http
path: /
metadata:
author: podsteadr
icon: /assets/img/app-icons/podsteadr.png
repo: http://146.59.87.168:3000/ssmithx/podsteadr
license: MIT
tags:
- nostr
- podcast
- livestream
- media
- rss
- lightning
launch:
open_in_new_tab: false