Goal
Stand up a clean, greenfield Hetzner deployment that hosts everything — the backend and the audio streaming stack — on Hetzner Cloud, and retire both the AWS Lightsail backend and the NodeMediaServer relay. No legacy dependencies carried forward (no NMS, no FLV/HLS-from-NMS, no AWS). Cheaper bandwidth, one provider, co-located components, and the unpatched 960-day-uptime relay box gone.
This is the umbrella for the consolidation. Move A (lift-and-shift the backend) already shipped as tooling; this ticket covers the full greenfield target and absorbs/reframes the Phase 2–4 streaming work.
Why
- Cost: Hetzner includes ~20 TB/mo egress per server (vs. metered AWS); compute is far cheaper. Streaming + recording bandwidth becomes effectively free at our scale (~300 peak listeners ≈ 38 Mbps).
- Simplicity / SPOF: one provider, co-located backend + producer + Icecast (localhost hops, no cross-cloud RTMP). Retire the ancient relay VM (kernel 5.4, 960-day uptime, unpatched).
- iOS: validated — MP3-over-Icecast plays natively in iOS Safari (see Phase-2 harness). NMS's HLS/FLV split is no longer needed.
Target architecture (greenfield, no NMS)
Broadcaster browser ──WS /ws/stream──▶ Backend (Hetzner, ffmpeg WebM/Opus→AAC)
│ localhost
▼
Liquidsoap (Hetzner) ──▶ Icecast-KH (Hetzner)
│ nginx + certbot TLS
▼
listeners (MP3/AAC <audio>, incl. iOS)
recordings ─────────────────────────────────────────────────────▶ Cloudflare R2 (unchanged)
Open design decision — how the backend feeds the producer (no NMS):
- (A) Backend ffmpeg outputs directly to Icecast (
-f mp3 icecast://…). Simplest; drops Liquidsoap. Loses fallback-silence/scheduling.
- (B) Backend ffmpeg → Liquidsoap harbor (Icecast SOURCE input) → Liquidsoap
mksafe → Icecast. Keeps the always-up mount + room for jingles/scheduling. Preferred.
- (C) Keep a thin RTMP ingest (nginx-rtmp) so the existing backend RTMP push code is unchanged, Liquidsoap pulls localhost. Most code-compatible, but reintroduces an RTMP server.
Recommend (B); decide during implementation. The backend RTMP target is already config (RTMP_URL/RTMP_STREAM_KEY).
Scope / tasks
Relationship to existing issues
Acceptance
- Backend serves prod from Hetzner (admin domain green over TLS), real DB migrated, recordings still land in R2.
- Live audio flows broadcaster → backend → producer → Icecast → listeners (incl. a real iPhone), TLS, no NMS in the path.
- Lightsail + the NMS relay are powered off and deleted after a successful soak.
- CI deploys to Hetzner.
Docs
docs/stream-rework/backend-hetzner-migration.md (Move A cutover)
docs/stream-rework/phase-2-icecast-runbook.md (Icecast/Liquidsoap, Tier-2 relay sequence — adapt host targets to the Hetzner box)
docs/stream-rework/local-test-harness/ (validated end-to-end, incl. iOS)
Goal
Stand up a clean, greenfield Hetzner deployment that hosts everything — the backend and the audio streaming stack — on Hetzner Cloud, and retire both the AWS Lightsail backend and the NodeMediaServer relay. No legacy dependencies carried forward (no NMS, no FLV/HLS-from-NMS, no AWS). Cheaper bandwidth, one provider, co-located components, and the unpatched 960-day-uptime relay box gone.
This is the umbrella for the consolidation. Move A (lift-and-shift the backend) already shipped as tooling; this ticket covers the full greenfield target and absorbs/reframes the Phase 2–4 streaming work.
Why
Target architecture (greenfield, no NMS)
Open design decision — how the backend feeds the producer (no NMS):
-f mp3 icecast://…). Simplest; drops Liquidsoap. Loses fallback-silence/scheduling.mksafe→ Icecast. Keeps the always-up mount + room for jingles/scheduling. Preferred.Recommend (B); decide during implementation. The backend RTMP target is already config (
RTMP_URL/RTMP_STREAM_KEY).Scope / tasks
backend/scripts/deploy_hetzner.sh(done as tooling) + SQLite cutover perdocs/stream-rework/backend-hetzner-migration.md.radio.live.moafunk.de) and the admin domain.VITE_STREAM_HLS_URL+VITE_STREAM_FLV_URL→ a single Icecast mount URL (env flip).HETZNER_IP/HETZNER_SSH_KEYBitwarden secrets; switch.github/workflows/backend.ymldeploy step todeploy_hetzner.sh.Relationship to existing issues
docs/stream-rework/).Acceptance
Docs
docs/stream-rework/backend-hetzner-migration.md(Move A cutover)docs/stream-rework/phase-2-icecast-runbook.md(Icecast/Liquidsoap, Tier-2 relay sequence — adapt host targets to the Hetzner box)docs/stream-rework/local-test-harness/(validated end-to-end, incl. iOS)