NADIPLAYER — Stream Proxy upgrade kit (v1.3.1 — CLEAN & HARDENED)
=================================================================

WHAT'S NEW vs the previous "sign-counter" build
-----------------------------------------------
1. Hardened cURL for IPTV upstreams on non-standard ports (:8080,
   :25461, etc.):
      • CURLOPT_HTTP_VERSION = HTTP/1.1   (some Xtream servers
        reject HTTP/2 frames on non-standard ports)
      • CURLOPT_SSL_VERIFYPEER = false    (cPanel CA bundles are
        commonly stale; we relay public bytes anyway, privacy is
        provided by the HMAC token, not by upstream TLS verify)
      • CURLOPT_NOSIGNAL = 1              (avoid SIGALRM crashes
        on shared FPM pools mid-segment-fetch)
      • CURLOPT_TCP_NODELAY = 1           (lower TTFB on small
        playlist responses)
      • Connection: close                 (don't hog FPM workers
        with keep-alive on shared hosts)

2. Verbose error logger:
   Every cURL failure (and every non-2xx upstream response) now
   writes a single line to:
        public_html/<your-domain>/nadiplayer/api/error_log
   with the format:
        [YYYY-MM-DD HH:MM:SS] [proxy:WHERE] curl_errno=N  http=X
                              url=...  err="..."
   If the directory isn't writable, falls back to PHP's standard
   error_log() (visible in cPanel → Errors).

3. /api/proxy/stats now returns "last_upstream_error" — the most
   recent failure (where, url, errno, http, msg, ts). The dashboard
   widget renders it as a red-bordered panel ABOVE the KPI tiles.
   No more SSH'ing in to grep logs to find out why a stream failed.

4. Verbose-mode toggle:
   Set the env var NDP_PROXY_VERBOSE=1 in php.ini to log full
   cURL handshake details to error_log for every fetch (very noisy,
   only enable temporarily during deep debugging).

5. /api/proxy/stats response body is now passed through to the
   client as readable text on 502 (instead of a generic JSON error),
   so even from `curl -i` you instantly see:
        HTTP/1.1 502
        x-proxy-kind: upstream-fetch-failed
        Content-Type: text/plain

        upstream playlist unavailable
        curl_errno=28
        message=Connection timed out after 10000 milliseconds
        url=http://sts.mydroon.com:8080/live/...

WHERE TO UPLOAD (cPanel File Manager)
-------------------------------------
public_html/<your-domain>/nadiplayer/api/index.php                     (OVERWRITE)
public_html/<your-domain>/nadiplayer/api/routes/proxy.php              (OVERWRITE)
public_html/<your-domain>/nadiplayer/api/lib/proxy_signer.php          (OVERWRITE — unchanged but safe to upload)
public_html/<your-domain>/nadiplayer/api/lib/segment_cache.php         (OVERWRITE)
public_html/<your-domain>/nadiplayer/admin_panel/views/dashboard.php   (OVERWRITE)


====================================================================
HOW TO READ THE NEW DASHBOARD PANEL
====================================================================

After upload, refresh /admin_panel/. With your subscriber's APK
hitting POST /api/proxy/sign (sign_requests > 0) but Active Upstreams
still at 0, the next playback attempt will surface the actual
upstream error in a red panel like:

    LAST UPSTREAM ERROR
    when:  2026-05-06 08:42:11 · where: live-playlist · curl_errno=28
    url:   http://sts.mydroon.com:8080/live/Meskidau/m5n4oCPAt1rBb3I/7757.m3u8
    msg:   Connection timed out after 10000 milliseconds

    Common causes: cPanel host's outbound firewall blocks port 8080
    (ask your provider to whitelist sts.mydroon.com), upstream
    IP-blocks the cPanel datacentre IP, or upstream credentials
    expired.

cURL errno cheat-sheet for THIS use-case:
   6   = "Could not resolve host"     → DNS issue on cPanel
   7   = "Couldn't connect to host"   → outbound firewall blocks port
   28  = "Operation timed out"        → host firewall silently drops
                                        OR upstream IP-bans cPanel IP
   35  = "SSL connect error"          → TLS handshake failed (when
                                        upstream is https://)
   56  = "Recv failure: Connection reset by peer" → upstream killed
                                        the connection mid-stream
   60  = "SSL certificate problem"    → would only fire if SSL_VERIFY
                                        were on; we keep it off

If you see errno 7 or 28 + http=0, your shared host is firewalling
outbound traffic to the upstream IP. Open a cPanel support ticket
and ask:
   "Please whitelist outbound TCP traffic to <upstream-host>:8080
    from my account's PHP-FPM pool."

If you see http=403 or http=401 with a body containing "Account
expired" / "Wrong credentials", the Xtream account you set in
Shared Profile config was rejected by the provider.

If you see http=200 but Active Upstreams stays at 0, the playlist
fetched fine but the SEGMENT fetches are failing — scroll the
dashboard, the panel should now show "where: segment" instead.


====================================================================
SMOKE TEST (replace YOUR_DOMAIN)
====================================================================

TOKEN=$(curl -s -X POST https://YOUR_DOMAIN/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"YOUR_ADMIN_PW"}' | jq -r .access_token)

# 1) Confirm new last_upstream_error key is exposed:
curl -s -H "Authorization: Bearer $TOKEN" \
  https://YOUR_DOMAIN/api/proxy/stats | jq '.last_upstream_error, .sign_requests, .backend'

# 2) Trigger a playlist fetch (replace SP_ID + SID):
TOK=$(curl -s -X POST https://YOUR_DOMAIN/api/proxy/sign \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"profile_id":"shared:SP_ID","kind":"live","stream_id":"7757"}' | jq -r .proxy_url)
curl -i "https://YOUR_DOMAIN$TOK"

# 3) Re-check last_upstream_error to see what failed:
curl -s -H "Authorization: Bearer $TOKEN" \
  https://YOUR_DOMAIN/api/proxy/stats | jq '.last_upstream_error'

# 4) Tail the new per-API error log:
tail -50 ~/public_html/YOUR_DOMAIN/nadiplayer/api/error_log

