Files
archy/apps/podsteadr-blossom/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

126 lines
3.8 KiB
YAML

app:
id: podsteadr-blossom
name: podsteadr Blossom
version: "4"
description: Blossom (BUD-02) sha256-addressed media blob server backing podsteadr's episode uploads and covers.
category: media
# Hyphenated name matches the podsteadr repo's docker-compose container_name
# (podsteadr-blossom); alias `blossom` is the short hostname podsteadr's
# server reaches it by (BLOSSOM_URL_INTERNAL=http://blossom:3000).
container_name: podsteadr-blossom
container:
image: ghcr.io/hzrd149/blossom-server:4
pull_policy: if-not-present
network: podsteadr-net
network_aliases: [blossom]
# Image runs as container-root (no USER directive) writing to a
# bind-mounted /app/data — CHOWN/DAC_OVERRIDE cover the fresh-bind-dir
# ownership gap the same way apps/botfights and apps/immich document.
# Unverified against a real install; check first-boot logs.
data_uid: "0:0"
dependencies:
- storage: 20Gi
resources:
cpu_limit: 1
memory_limit: 512Mi
disk_limit: 20Gi
security:
capabilities: [CHOWN, DAC_OVERRIDE, FOWNER]
readonly_root: false
no_new_privileges: true
network_policy: isolated
ports:
- host: 8098
container: 3000
protocol: tcp
auth: none
auth_rationale: >-
Media blobs (episode audio/video, covers) must be publicly fetchable
by podcast clients as RSS enclosure URLs — that's the entire purpose
of this port. Uploads are separately gated by blossom's own BUD-02
signed-nostr-event auth (upload.requireAuth below), not a node
session; reads are intentionally public per the config's own header
comment.
volumes:
- type: bind
source: /var/lib/archipelago/podsteadr-blossom/data
target: /app/data
options: [rw]
- type: bind
source: /var/lib/archipelago/podsteadr-blossom/config/config.yml
target: /app/config.yml
options: [ro]
environment: []
files:
- path: /var/lib/archipelago/podsteadr-blossom/config/config.yml
overwrite: true
content: |
# blossom-server (v4.x) configuration for podsteadr.
# Uploads require a signed nostr auth event (BUD-02, kind 24242);
# reads are public so podcast apps can fetch enclosures.
#
# NOTE (blossom-server 4.4.1 gotcha, do not rediscover): `rules:` MUST
# be nested under `storage:` — a top-level `rules:` key is silently
# ignored, the ruleset ends up empty, and every upload fails 401
# "Server dose not accept video/mp4 blobs" (typo is theirs). The
# GitHub master branch is a Deno rewrite with a different schema
# (storage.rules, BUD-11, range support); the `:4` image is the older
# node/koa codebase this config targets.
publicDomain: ""
databasePath: data/sqlite.db
dashboard:
enabled: false
discovery:
nostr:
enabled: false
relays: []
upstream:
enabled: false
domains: []
storage:
backend: local
local:
dir: ./data/blobs
removeWhenNoOwners: false
# "expiration" is time since a blob was last accessed — unaccessed
# blobs get pruned after this. Podcast media should effectively
# never expire, so keep this long.
rules:
- type: "*"
expiration: 10 years
upload:
enabled: true
requireAuth: true
requirePubkeyInRule: false
list:
requireAuth: false
allowListOthers: true
tor:
enabled: false
proxy: ""
health_check:
# No documented health endpoint; TCP liveness on the app port.
type: tcp
endpoint: localhost:3000
interval: 30s
timeout: 5s
retries: 3