From 9e3f031f958a0c19600e6641c0c2172a2d7b3c27 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Fri, 17 Apr 2026 09:05:37 -0400 Subject: [PATCH] feat(webrtc): add -rtp-host flag + TrueNAS Docker deploy - core/webrtc: NewSourceOn(streamID, host, port) allows binding the RTP UDP socket on something other than 127.0.0.1, required when the PoC runs in a container and must accept RTP from LAN publishers. NewSource(streamID, port) stays as a convenience wrapper on 127.0.0.1 for existing tests and tight local tests. - cmd/webrtc-poc: new -rtp-host flag (default 127.0.0.1 for safety). - deploy/docker/Dockerfile: two-stage build, scratch runtime, ~14 MB. - deploy/truenas/docker-compose.yml: host-networked stack template driven by a .env file. Host networking is required for WebRTC ICE to work without NAT rewriting per-candidate. - deploy/truenas/README.md: operator runbook with port picking, bring-up, verification curls, and security notes. --- cmd/webrtc-poc/main.go | 3 +- core/webrtc/source.go | 20 ++++++++- deploy/docker/Dockerfile | 34 +++++++++++++++ deploy/truenas/README.md | 70 +++++++++++++++++++++++++++++++ deploy/truenas/docker-compose.yml | 36 ++++++++++++++++ 5 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 deploy/docker/Dockerfile create mode 100644 deploy/truenas/README.md create mode 100644 deploy/truenas/docker-compose.yml diff --git a/cmd/webrtc-poc/main.go b/cmd/webrtc-poc/main.go index fe734bb..6fffd71 100644 --- a/cmd/webrtc-poc/main.go +++ b/cmd/webrtc-poc/main.go @@ -17,6 +17,7 @@ import ( func main() { var ( streamID = flag.String("stream", "test", "stream id to serve") + rtpHost = flag.String("rtp-host", "127.0.0.1", "bind address for RTP UDP socket (use 0.0.0.0 for LAN publishers)") rtpPort = flag.Int("rtp-port", 10000, "UDP port to receive RTP on") listen = flag.String("listen", ":8787", "WHEP HTTP listen address") publicIP = flag.String("public-ip", "", "server public IP for NAT1To1 (optional)") @@ -27,7 +28,7 @@ func main() { cfg.WHEPListen = *listen cfg.PublicIP = *publicIP - src, err := webrtc.NewSource(*streamID, *rtpPort) + src, err := webrtc.NewSourceOn(*streamID, *rtpHost, *rtpPort) if err != nil { log.Fatalf("NewSource: %v", err) } diff --git a/core/webrtc/source.go b/core/webrtc/source.go index fc61e44..521ec7f 100644 --- a/core/webrtc/source.go +++ b/core/webrtc/source.go @@ -22,9 +22,25 @@ type Source struct { } // NewSource binds a UDP socket on 127.0.0.1:port. Pass port=0 to let the OS -// assign an ephemeral port (useful for tests). +// assign an ephemeral port (useful for tests). Equivalent to +// NewSourceOn(streamID, "127.0.0.1", port). func NewSource(streamID string, port int) (*Source, error) { - addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port} + return NewSourceOn(streamID, "127.0.0.1", port) +} + +// NewSourceOn binds a UDP socket on host:port. Use "0.0.0.0" to accept +// RTP from any LAN publisher — required when running in a container +// with host networking that needs to receive from other hosts. Empty +// host is treated as 127.0.0.1 for backward compatibility. +func NewSourceOn(streamID, host string, port int) (*Source, error) { + if host == "" { + host = "127.0.0.1" + } + ip := net.ParseIP(host) + if ip == nil { + return nil, fmt.Errorf("webrtc: invalid host %q", host) + } + addr := &net.UDPAddr{IP: ip, Port: port} conn, err := net.ListenUDP("udp4", addr) if err != nil { return nil, fmt.Errorf("webrtc: listen udp: %w", err) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile new file mode 100644 index 0000000..ef00f75 --- /dev/null +++ b/deploy/docker/Dockerfile @@ -0,0 +1,34 @@ +# Dockerfile for the Dragon Fork WebRTC PoC (M1). +# +# Two-stage: +# 1. builder: compile a static linux/amd64 binary inside the repo +# 2. runtime: minimal scratch image with the binary only +# +# The PoC has no outbound HTTPS needs and no dynamic libraries, so +# `scratch` is safe. Image size ~14 MB. +# +# The binary's flags (-stream, -rtp-port, -listen, -public-ip) are +# passed via `command:` in docker-compose (or `docker run ...`). + +# ---- builder ---- +FROM golang:1.24-alpine AS builder + +WORKDIR /src +COPY . . + +# Static, stripped, no CGO — no shared libs needed in runtime stage. +ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 +RUN go build -trimpath -ldflags="-s -w" \ + -o /out/webrtc-poc \ + ./cmd/webrtc-poc + +# ---- runtime ---- +FROM scratch AS runtime + +COPY --from=builder /out/webrtc-poc /webrtc-poc + +# Defaults — override via `command:` or `docker run ...`. +EXPOSE 8787/tcp +EXPOSE 10000/udp + +ENTRYPOINT ["/webrtc-poc"] diff --git a/deploy/truenas/README.md b/deploy/truenas/README.md new file mode 100644 index 0000000..b6030f0 --- /dev/null +++ b/deploy/truenas/README.md @@ -0,0 +1,70 @@ +# TrueNAS deploy — WebRTC PoC (M1) + +Host-networked Docker stack that runs `cmd/webrtc-poc` on TrueNAS for +manual end-to-end testing. Not wired into the Core binary. + +## Prereqs + +- Docker on the TrueNAS host (TrueNAS SCALE includes it) +- LAN or public IP that clients can reach +- One free TCP port (WHEP) and one free UDP port (RTP ingest) + +## One-time setup + +``` +# On TrueNAS: +sudo mkdir -p /mnt/NVME/Docker/dragonfork-webrtc-poc +cd /mnt/NVME/Docker/dragonfork-webrtc-poc + +# Copy the repo's deploy/truenas/docker-compose.yml in here, and the +# whole repo (or just cmd/ + core/ + go.mod + vendor/) somewhere the +# Dockerfile build context can see. Simplest: clone the repo adjacent +# and symlink docker-compose.yml, or point `context:` at the clone. + +cat > .env <