Files
church-translate/docs/PUBLIC-ACCESS.md
michaelandClaude Opus 5.5 825a25b626 Document opening Waters Guest WiFi at North Attleboro to translate
The guest network blocks private ranges twice: a zone default on the
gateway, and the guest hotspot's Post-Authorization Restrictions, which
the access points enforce on their own. With only a firewall allow, a
phone on Waters Guest worked for about two minutes and then timed out.

docs/examples/waters-church-na/ gains the three scripts used to fix it:
zone-allow-policy.py (allow Hotspot -> 10.1.1.4 on 80/443),
guest-restrictions.py (drop 10.0.0.0/8 from the restriction list via
the legacy guest_access setting, since system policies cannot be edited
through the Integration API) and wifi-broadcast-devices.py (put the
Waters Guest SSID on the Auditorium, Main Lobby and Offices APs). The
runbook gets a section on all three, and PUBLIC-ACCESS.md points at it
from the troubleshooting notes.

Confirmed on site: translate.watersmedia.org loads on Waters Guest and
stays up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:15:46 -04:00

6.5 KiB

Letting people off-site watch

Optional. Everything works on the building's WiFi without any of this. Read on only if you want people on cell data, or watching from home, to follow along.

Because each campus runs its own copy, this is a per-campus decision — you can publish one campus and not another.


The easy way: Cloudflare Tunnel

Free, needs no port forwarding, no firewall changes, no certificate, and no static IP. It runs on the same Mac as the app.

  1. Install cloudflared

    brew install cloudflared
    

    No Homebrew on that Mac? Download the cloudflared-darwin-arm64.tgz binary from Cloudflare's releases page and drop it in /usr/local/bin.

  2. Log in and create the tunnel

    cloudflared tunnel login
    cloudflared tunnel create translate-main
    
  3. Point a hostname at the app

    cloudflared tunnel route dns translate-main translate.watersmedia.org
    
  4. Run it

    cloudflared tunnel run --url http://127.0.0.1:3100 translate-main
    

    To keep it running after a reboot, sudo cloudflared service install.

  5. Tell the app its public name

    Operator page → Public address → https://translate.watersmedia.org → Save. That is what the QR code encodes, so it must be the public name or the code only works for people already on your WiFi.

WebSockets pass through Cloudflare tunnels with nothing extra to configure, which is the main reason this is the lower-risk option.

For a second campus, repeat with its own name — translate-north, north.translate.watersmedia.org.


The other way: a name on your existing reverse proxy

You already run a reverse proxy on the NAS at 10.1.1.4 (Boba-Fett, Synology DSM) fronting check.watersmedia.org. It can front this too, though it means the traffic goes through the NAS network to reach the booth Mac.

This is what North Attleboro runs as of 2026-10-15, in the LAN-only form: http://translate.watersmedia.org works on the building's WiFi at both the Apollo Beach and North Attleboro sites and nowhere else. Nothing was opened to the WAN. The pieces, with the exact scripts and a step-by-step runbook under examples/waters-church-na/:

Piece Where What
Local DNS UniFi consoles at AB and NA (Settings → Policy Table → DNS) A record translate.watersmedia.org → 10.1.1.4, TTL 300, created with unifi-dns-record.py and a per-console Network API key
Proxy rule DSM → Control Panel → Login Portal → Advanced → Reverse Proxy HTTP translate.watersmedia.org:80 → HTTP 10.1.7.39:3100, WebSocket headers, X-Forwarded-Host: $http_host, read timeout 3600 s
HTTPS /usr/local/etc/nginx/sites-enabled/translate-redirect.conf on the NAS https:// answers a 301 to http://; the name has no certificate, so browsers warn before the redirect

Both NAS pieces come from boba-fett-translate-proxy.sh, run as ssh -t boba-fett sudo bash translate-proxy.sh because DSM's reverse-proxy API is root-only. Going through DSM rather than a hand-edited nginx file means the rule shows up in the DSM UI and survives DSM updates. DSM adds Host, X-Real-IP, X-Forwarded-For and X-Forwarded-Proto on its own.

Then: Operator page → Public address → http://translate.watersmedia.org (no trailing slash), so the QR code and poster use the name instead of the IP and port.

To let people off-site use the same name later: add a public DNS record for translate.watersmedia.org pointing at the NA WAN address, get a certificate in DSM (Control Panel → Security → Certificate → Let's Encrypt), replace the redirect with an HTTPS rule to the same destination, and switch Public address to https://. The app behaves the same either way, including the /operator 404 through a proxy.

If the proxy is plain nginx instead of DSM

DSM writes most of this for you. On a hand-managed nginx the server block needs all of it:

server {
    listen 443 ssl http2;
    server_name translate.watersmedia.org;

    ssl_certificate     /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    location / {
        proxy_pass http://10.1.7.39:3100;      # the booth Mac

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host  $host;

        # ---- required: captions ride a WebSocket ----
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        # A sermon is long and mostly silent between sentences. Without this the
        # proxy closes the caption socket after 60s of quiet and every phone in
        # the room reconnects at once.
        proxy_read_timeout  3600s;
        proxy_send_timeout  3600s;

        # Live audio: do not hold HLS segments back.
        proxy_buffering off;
    }
}

Either way the booth Mac needs a fixed address or a DHCP reservation, or the proxy breaks the next time DHCP moves it.


Checking it worked

curl -s http://translate.watersmedia.org/healthz      # https:// once published

Should return {"ok":true,…}, the same as curl -s http://10.1.7.39:3100/healthz. A proxy that passes the WebSocket answers HTTP/1.1 101 Switching Protocols to:

curl -si -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
  -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
  http://translate.watersmedia.org/ws | head -1

Then open the page on a phone: for the LAN-only setup on the guest WiFi, for a published one with WiFi turned off. That is the real test. If the page loads but captions never appear, the WebSocket headers are missing. /operator returning 404 through the proxy is by design; staff use http://10.1.7.39:3100/operator on the LAN.

Guest WiFi on UniFi. Guest networks block every private range by default, so the proxy is unreachable from them until two things change: a firewall policy that allows the guest zone to reach the proxy on 80/443, and the proxy's range removed from the guest hotspot's Post-Authorization Restrictions. The second one is easy to miss: the access points enforce that list themselves, so with only the firewall policy a phone works for a minute or two and then times out. North Attleboro's fix, with scripts, is in examples/waters-church-na/ section 3.