Files
XC_VM/lb_configs/nginx.conf
T
obscuremindandClaude Opus 5 3eabc7a6f9 fix(auth): catch-up routing, proxy-only check and adaptive variants
- Timeshift through a proxy read $rChannelInfo, which the timeshift path
  never sets, so catch-up always bypassed the archive server's proxy (and
  an archive server that requires one refused it). It now uses the
  archive server's proxies, like live.
- The cached pre-check compared the type to 'archive', which requests
  never carry, so catch-up fell into the live check and a channel whose
  live stream was down refused its own catch-up. The same block used
  variables that are not set yet (and SERVER_ID, not yet defined there).
- The proxy-only rule trusted the client-set X-IP header; any client could
  name a public proxy IP and pass. nginx now passes the TCP peer
  ($realip_remote_addr) as XC_PEER_ADDR and auth checks that; the header
  counts only from an XC_VM server/whitelisted peer, or on an nginx config
  too old to pass the peer.
- Adaptive masters skip a variant with no server (or no proxy) instead of
  building URLs from a failed redirect.
- Connection uuids come from random_bytes() rather than md5(uniqid()).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:19:50 +01:00

310 lines
12 KiB
Nginx Configuration File

user xc_vm;
worker_processes auto;
worker_rlimit_nofile 300000;
events {
worker_connections 16000;
use epoll;
multi_accept on;
}
thread_pool pool_xc_vm threads=32 max_queue=0;
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
reset_timedout_connection on;
gzip off;
fastcgi_read_timeout 200;
access_log off;
error_log /dev/null;
keepalive_timeout 10;
include balance.conf;
send_timeout 20m;
sendfile_max_chunk 512k;
lingering_close off;
aio threads=pool_xc_vm;
client_body_timeout 13s;
client_header_timeout 13s;
client_max_body_size 8m;
real_ip_header X-Forwarded-For;
include realip_cdn.conf;
include realip_cloudflare.conf;
include realip_xc_vm.conf;
include limit.conf;
limit_req_zone $binary_remote_addr zone=one:30m rate=20r/s;
server {
include ports/*.conf;
include ssl.conf;
include custom.conf;
index index.php index.html index.htm;
try_files $uri $uri.html $uri/ @extensionless-php;
root /home/xc_vm/Public/;
server_tokens off;
chunked_transfer_encoding off;
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;
absolute_redirect off;
if ($request_method !~ ^(GET|POST)$) {
return 200;
}
location ^~ /.well-known/acme-challenge/ {
root /home/xc_vm/certbot-webroot;
try_files $uri =404;
}
rewrite_log on;
rewrite ^/key/(?<token>[^/]*)$ /stream/key?token=$token break;
rewrite ^/hls/(?<token>[^/]*)$ /stream/segment?token=$token break;
rewrite ^/tsauth/(?<token>[^/]*)$ /stream/timeshift?token=$token break;
rewrite ^/thauth/(?<token>[^/]*)$ /stream/thumb?token=$token break;
rewrite ^/auth/(?<token>[^/]*)$ /stream/live?token=$token break;
rewrite ^/vauth/(?<token>[^/]*)$ /stream/vod?token=$token break;
rewrite ^/subauth/(?<token>[^/]*)$ /stream/subtitle?token=$token break;
location = /streaming/rtmp.php {
return 302 /stream/rtmp?$args;
}
# ─── Legacy Stream PHP Compatibility (no direct www execution) ──
location ~ ^/stream/(auth|key|segment|live|vod|timeshift|thumb|subtitle|rtmp)\.php$ {
return 302 /stream/$1?$args;
}
location = /probe.php {
return 302 /stream/probe?$args;
}
# ─── Streaming Hot Path → Stream Gateway ─────────────────
location ~ ^/stream/(auth|key|segment|live|vod|timeshift|thumb|subtitle|rtmp|probe)$ {
limit_req zone=one burst=8;
include limit_queue.conf;
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/stream/index.php;
fastcgi_param SCRIPT_NAME /public/stream/index.php;
fastcgi_param XC_STREAM $1;
# The TCP peer before real_ip rewrites REMOTE_ADDR to the client:
# auth checks it (not a client-set header) for proxy-only servers.
fastcgi_param XC_PEER_ADDR $realip_remote_addr;
}
# ─── Loopback handlers only → Admin Gateway ──────────────
# A server higher in the tree pulls streams whose source is this LB
# via /admin/{live,timeshift,thumb,vod}. Expose ONLY those handlers —
# never index|api|proxy_api — so the admin panel is not served from a LB.
location ~ ^/admin/(live|timeshift|thumb|vod)$ {
limit_req zone=one burst=8;
include limit_queue.conf;
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 128 32k;
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 128k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/admin/index.php;
fastcgi_param SCRIPT_NAME /public/admin/index.php;
fastcgi_param XC_ADMIN $1;
}
location ~ ^/admin/(live|timeshift|thumb|vod)\.php$ {
return 302 /admin/$1?$args;
}
# ─── HLS X-Accel internal (P1) ───────────────────────────
# segment.php authenticates, then hands the .ts/.enc to nginx via
# `X-Accel-Redirect: /xc_hls/<file>`; nginx sendfile()s it from STREAMS_PATH.
# `internal` = unreachable directly by clients, only via the internal redirect.
location /xc_hls/ {
internal;
alias /home/xc_vm/content/streams/;
}
# ─── Live fan-out X-Accel internal (P2, ADR 0002) ────────
# live.php authenticates + registers the source with the xc_fanout
# daemon, then hands the byte path to nginx via
# `X-Accel-Redirect: /xc_fanout/<id>`. nginx proxies the viewer to the
# daemon's client socket (in the app bin tree, next to the binary, like
# the php-fpm sockets); PHP-FPM is freed immediately. `internal` =
# reachable only via the internal redirect, never directly by clients.
# NOTE: the target MUST stay two path segments (/xc_fanout/<id>) — the
# server-level rewrites above run on internal redirects too, and the
# 3-segment catch-all `^/(user)/(pass)/(stream)` would otherwise hijack a
# `/xc_fanout/live/<id>` form into /stream/auth. Same reason P1's
# /xc_hls/<file> target is two segments.
location ^~ /xc_fanout/ {
internal;
rewrite ^/xc_fanout/(.*)$ /live/$1 break;
proxy_pass http://unix:/home/xc_vm/bin/xc_fanout/sockets/http.sock;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# ─── Daemon in-RAM HLS segment X-Accel internal (P2/B, ADR 0003) ──
# segment.php authenticates a daemon HLS segment token, then hands the
# segment to nginx via `X-Accel-Redirect: /xc_fanout_hls/<id>_<seq>`,
# which we map to the daemon's /hls/<id>/<seq>.ts. Target stays TWO path
# segments (id_seq) for the same rewrite-hijack reason as /xc_fanout/.
location ^~ /xc_fanout_hls/ {
internal;
rewrite ^/xc_fanout_hls/(\d+)_(\d+)$ /hls/$1/$2.ts break;
proxy_pass http://unix:/home/xc_vm/bin/xc_fanout/sockets/http.sock;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location ~* ^/images/(.*/)?index\.html$ {
return 404;
}
location ^~ /images/ {
alias /home/xc_vm/storage/images/;
location ~* \.(php|php\d*|phtml|phar)$ {
return 403;
}
}
location ~ ^/status$ {
allow 127.0.0.1;
deny all;
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/stream/index.php;
fastcgi_param SCRIPT_NAME /public/stream/index.php;
fastcgi_param XC_STREAM status;
}
location /nginx_status {
allow 127.0.0.1;
deny all;
stub_status on;
}
location = /progress {
limit_req zone=one burst=8;
include limit_queue.conf;
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/progress/index.php;
fastcgi_param SCRIPT_NAME /public/progress/index.php;
}
location = /progress.php {
return 302 /progress?$args;
}
# ─── Streaming API → Front Controller ───────────────────────
location ~ ^/api/(player_api|enigma2|xplugin|epg|playlist)$ {
limit_req zone=one burst=8;
include limit_queue.conf;
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/index.php;
fastcgi_param SCRIPT_NAME /public/index.php;
fastcgi_param XC_SCOPE api;
fastcgi_param XC_API $1;
}
location ~ ^/(player_api|enigma2|xplugin|epg|playlist)\.php$ {
limit_req zone=one burst=8;
include limit_queue.conf;
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/index.php;
fastcgi_param SCRIPT_NAME /public/index.php;
fastcgi_param XC_SCOPE api;
fastcgi_param XC_API $1;
}
# ─── Internal API → Front Controller (server-to-server) ────
# Аутентификация: InternalApiController проверяет password + IP whitelist
location = /api {
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/index.php;
fastcgi_param SCRIPT_NAME /public/index.php;
fastcgi_param XC_SCOPE api;
fastcgi_param XC_API internal;
}
location = /api.php {
fastcgi_index index.php;
fastcgi_pass php;
include fastcgi_params;
fastcgi_buffering on;
fastcgi_buffers 96 32k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0;
fastcgi_keep_conn on;
fastcgi_param SCRIPT_FILENAME /home/xc_vm/Public/index.php;
fastcgi_param SCRIPT_NAME /public/index.php;
fastcgi_param XC_SCOPE api;
fastcgi_param XC_API internal;
}
# ─── Ministra Legacy Portal (L-6 pending) ────────────────
location = /c/portal.php {
return 404;
}
location = /portal.php {
return 404;
}
location ~ \.php$ {
return 404;
}
location @extensionless-php {
return 404;
}
}
}