Commit Graph
15 Commits
Author SHA1 Message Date
Ivan RazinandClaude Fable 5 9638cf5b47 Fix backup failing on tmpfs /tmp: stage in backup store, check free space
On Ubuntu 24.10+ /tmp is a size-limited tmpfs, so mktemp -d staging could
not hold the uncompressed copy of the panel, web roots and certs. Stage
backup/restore work dirs inside /var/backups/x-ui instead, and verify
free space (2x estimated size + 100 MB margin) before touching anything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 14:31:01 +03:00
Ivan RazinandClaude Opus 4.8 906822b42b Fix MTR failing under systemd: grant CAP_NET_RAW via ambient capabilities
mtr ran fine as the mtr-backend user in a shell but failed under the
systemd service with:
  mtr-packet: Failure to open IPv4 sockets: Permission denied

Cause: the unit sets NoNewPrivileges=yes, which strips file capabilities,
and AmbientCapabilities/CapabilityBoundingSet were empty — so mtr-packet
(the helper that opens the raw ICMP socket) had no CAP_NET_RAW. Interactive
runs worked because they had no NoNewPrivileges.

- Grant CAP_NET_RAW the systemd-native way (AmbientCapabilities +
  CapabilityBoundingSet), which survives NoNewPrivileges. Fixed in both
  the full installer and the patch service unit.
- setcap now also targets mtr-packet, not just mtr.
- Add proxy_intercept_errors off to the diag /api/mtr location: the
  server-level "proxy_intercept_errors on" was rewriting a backend 500 into
  an HTML 404, which broke the frontend's response.json() and left the
  client IP stuck on "detecting…".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:25:01 +03:00
Ivan RazinandClaude Opus 4.8 fb68047c02 Fix diag 404: derive panel domain from webCertFile, purge stale vhosts
The diagnostics page 404'd because the fresh panel vhost (with the diag
SSO bridge) was written to the wrong filename and never served: the
nginx-scan domain detection picked the wrong panel domain when stale /
duplicate vhost files from older installs lingered (the loop result is
glob/locale-order dependent, and a leftover panel vhost shadowed the real
one by server_name). The vhost that actually served traffic was a stale,
pre-bridge config, so /<panel>/diag fell through to a 404.

- Panel domain now comes from the authoritative source, the cert x-ui is
  configured with (DB webCertFile -> /root/cert/<domain>/ or
  /etc/letsencrypt/live/<domain>/); nginx scan is only a fallback, and it
  no longer overwrites an already-resolved domain.
- Skip 00-maps.conf in the detection loop.
- At activation: remove stale panel/reality vhost FILES whose name doesn't
  match the managed domains, wipe ALL sites-enabled symlinks, then relink
  only the four managed configs — so nothing stale can stay loaded and
  shadow the panel vhost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 09:55:57 +03:00
Ivan RazinandClaude Opus 4.8 e4fc8a60bb Fix nginx emerg "unknown serve_clash_yaml variable"
The Clash maps ($is_clash_ua, $serve_clash_yaml) were defined only inside
the panel vhost file, but snippets/includes.conf consumes $serve_clash_yaml
and is included by BOTH the panel and reality vhosts. Any state where the
reality vhost loaded while the panel file did not left the variable
undefined globally -> "[emerg] unknown \"serve_clash_yaml\" variable" and
nginx -t failed.

Move both clash maps into a standalone http-level file
(sites-available/00-maps.conf) that is always symlinked into sites-enabled,
so the shared snippet's variable is defined independently of any single
vhost. Reproduced and verified the fix (and the failure without it) with
nginx 1.30.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 09:42:40 +03:00
Ivan RazinandClaude Opus 4.8 88466b9b21 Fix diagnostics 500: harden auth bridge + repair missing panel cert
Root cause: nginx auth_request returns a raw 500 to the browser whenever
the /__diag_auth subrequest to the panel returns anything other than
2xx/401/403. Two ways that happens:
  - the panel's TLS cert is recorded in the DB (webCertFile) but the file
    or symlink is missing on disk (e.g. dangling /root/cert symlink), so
    the panel can't serve HTTPS and proxy_pass https:// yields a 502; or
  - the panel returns a login 302 for the AJAX auth probe.
Either way the browser got a 500 on the diagnostics page.

- Both scripts: add proxy_intercept_errors on + an error_page in
  /__diag_auth that coerces every non-allow status (3xx/4xx/5xx, incl.
  401/403 so the server-level "=404" trap can't rewrite a genuine deny)
  into a 401 -> @diag_denied, so the flow degrades to a panel-login
  redirect instead of a 500. Verified with nginx 1.30.3 across
  200/401/302/502 upstream backends: all return a clean 302.
- x-ui-patch.sh: the panel-HTTPS check now also treats a configured-but-
  missing cert (-e follows symlinks) as needing repair, re-symlinking the
  Let's Encrypt cert and re-running `x-ui cert`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 09:34:40 +03:00
Ivan RazinandClaude Opus 4.8 2ed4984989 Patch: also print the direct diagnostics URL at the end
The results block already shows the SSO-bridge entry (/panel/diag); add
the direct diag page URL (https://domain/net-XXX/) so the actual page is
visible after patching.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:35:42 +03:00
Ivan RazinandClaude Opus 4.8 65e095133f Patch: enforce panel HTTPS, fix set -e SIGPIPE abort, README one-liner
- x-ui-patch.sh: detect when the panel has no TLS cert (empty webCertFile)
  and enable HTTPS the way the main installer does — symlink the Let's
  Encrypt cert into /root/cert/$domain and register it via `x-ui cert`.
  Without this the panel serves plain HTTP and every proxy_pass
  https://127.0.0.1:panel_port (including the diag SSO bridge) fails.
- x-ui-patch.sh: guard the `tr </dev/urandom | head -c N` random generators
  with `|| true`. head closes the pipe, tr dies with SIGPIPE (141), and
  under `pipefail`+`errexit` that intermittently aborts the whole script.
- README: replace the two-step patch download/run block with a single
  curl|bash one-liner (also drops a stale wget filename mismatch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:34:27 +03:00
Ivan RazinandClaude Fable 5 24492dfd3f Fix diag redirects pointing at internal :7443 (unreachable by browser)
The panel vhost listens on 7443 behind the SNI stream (public port is
443). nginx builds absolute redirect Location headers from the server's
listen port, so `return 302 /path` in the diag SSO bridge emitted
`Location: https://<domain>:7443/...`. Browsers followed that to port
7443, which UFW blocks (only 22/80/443 open) → the diagnostics page
hung and never loaded.

Set `absolute_redirect off` on the panel vhost so nginx emits relative
Location headers; the browser resolves them against the real origin
(:443). Verified with nginx: listen 7443 + return 302 goes from
`http://host:7443/panelXXXX/` to a bare `/panelXXXX/`.

This was the actual cause of the "diagnostics page doesn't load after
panel login" report; earlier fixes addressed real but secondary issues
on the same path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:16:00 +03:00
Ivan RazinandClaude Fable 5 f864864501 Fix diagnostics SSO deny path returning a 302 with no Location
The bridge used `error_page 401 403 =302 /<panel>/`, which does an
nginx internal redirect: it serves the panel login page body with a
302 status but sends NO Location header. Browsers (and curl) can't
follow that, so an unauthenticated or expired-session visit to the
diag link rendered a blank page ("doesn't load").

Route the deny path through a named location that does a real
`return 302 /<panel>/`, so the client gets a proper Location and
lands on the panel login. The authorized path (auth_request 200 →
@diag_sso_ok → 302 + diag cookie) is unchanged.

Verified with nginx + a TLS mock panel: unauthenticated hit now
redirects to the panel login; logged-in hit (browser-like cookie
handling) resolves to the diag page in 1-2 redirects, no loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:10:11 +03:00
Ivan RazinandClaude Fable 5 ba6d9ca1d2 Add h2/http1.1 ALPN to tls hosts; make raw diag link self-authorize
Host ALPN: ws/xhttp/trojan hosts now advertise ["h2","http/1.1"];
reality host gets [] (its ALPN comes from the reality stream).

Diagnostics access: opening the diag path directly without the
diag_key cookie previously 404'd, so a bookmarked link only worked
after manually visiting /<panel_path>/diag. Now the diag page
302-bounces unauthenticated hits through the SSO bridge, which
validates the 3x-ui panel session and mints the cookie, then redirects
back - so the link works once you're logged into the panel. API and
asset sub-locations still 404 without the cookie (only the HTML page
redirects, never XHR/asset requests).

Verified end to end with nginx + the auth semantics of the installed
3x-ui v3.4.2: logged-in raw hit resolves to the page with no loop,
anonymous hit lands on the panel login.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:54:56 +03:00
Ivan RazinandClaude Fable 5 716840a691 Replace externalProxy arrays with hosts table entries
3x-ui now renders share-link endpoints from the hosts table, which
supersedes the legacy externalProxy arrays in stream_settings. Remove
those arrays from all four inbounds and insert one host per inbound
instead (inbound_id resolved by tag):

- REALITY: panel domain, 443, security=same (inherits reality params)
- ws / xhttp / trojan-grpc: panel domain, 443, security=tls,
  fingerprint firefox

Also switch the REALITY inbound uTLS fingerprint from chrome to
firefox to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:26:47 +03:00
Ivan RazinandClaude Fable 5 d816b5edcc Gate diagnostics behind 3x-ui panel login via nginx SSO bridge
The 3x-ui session cookie is Path-scoped to the panel base path, so the
diag vhost locations can never see it directly. Instead a bridge
location under the panel path (/<panel_path>/diag) validates the
session with auth_request against GET <basePath>/panel/ (sent with
X-Requested-With: XMLHttpRequest so 3x-ui answers 401 instead of a
login redirect), then issues a path-scoped diag_key cookie and
redirects to the diagnostics page. All diag locations return 404
without that cookie.

auth_request runs in the access phase while "return" runs in the
rewrite phase, so the success path hops through try_files to a named
location; the cookie is set only there, never on the 401 redirect.

Replaces the previous ?key= token link, which is removed entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:05:29 +03:00
Ivan RazinandClaude Fable 5 bd2c47753e Fix installer bugs, version-aware http2 directive, cleaner results screen
Bug fixes:
- Root check: exit with message instead of "sudo su -" (which continued
  the script as non-root after the shell exited)
- Move destructive cleanup into clean_previous_install(), called after
  domain validation - wrong args no longer wipe a working install
- x25519 keygen: use xray-linux-$(_arch) instead of hardcoded amd64,
  with fallback for renamed ARM binaries
- Cert renewal: certs are standalone-issued, so renew with pre/post
  hooks stopping nginx instead of --nginx (which could not bind :80)
- Drop ngx_stream_geoip2 load_module insertion: module was never
  installed or used and broke nginx -t when the .so was absent
- emoji_flag: 10s curl timeout + fallback when ipwho.is unavailable
- Uninstall: rm -f /usr/bin/x-ui (trailing slash made rm fail on file)

nginx >= 1.25.1 deprecates "listen ... http2"; emit "http2 on;" there
and keep the old syntax on older versions (Debian 12, Ubuntu 24.04).

Results screen: drop certbot/ssl cert dumps and Clash sub links, keep
panel URL + credentials + diagnostics. Update CLAUDE.md to current
repo state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:29:46 +03:00
Ivan RazinandClaude Fable 5 de1d95e6e0 Replace speed test with LibreSpeed engine: fix 4x-slow upload
Single 512 MB POST over HTTP/2 was throttled by the per-stream h2
flow-control window, reading ~4x low. LibreSpeed uses parallel upload
streams measured via XHR progress events, which amortizes the window
limit (h2 must stay on for trojan-gRPC).

- Vendor speedtest.js + speedtest_worker.js (LGPL) into assets
- mtr-backend.py: ThreadingHTTPServer, /api/st/up upload sink,
  /api/st/getip; parallel streams need concurrent handling
- nginx: speedtest locations without limit_req (limit_conn instead),
  ping answered by nginx directly, h2 preread/body buffer tuning
- No telemetry, no database
- Drop 512 MB test file; patch script now also substitutes
  __SERVER_DOMAIN__/__SERVER_IP__ placeholders

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:15:22 +03:00
Ivan RazinandClaude Sonnet 4.6 cb9aa79470 Back up /usr/bin/x-ui (management CLI) in addition to /usr/local/x-ui
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 16:20:35 +03:00