From bd2c47753edc4dd6cd004d2fed765b74936c911a Mon Sep 17 00:00:00 2001 From: Ivan Razin Date: Fri, 3 Jul 2026 14:29:46 +0300 Subject: [PATCH] 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 --- CLAUDE.md | 82 ++++++++++++++++++++++++++++++++------------------ x-ui-latest.sh | 66 ++++++++++++++++++++++++---------------- x-ui-patch.sh | 25 +++++++++------ 3 files changed, 108 insertions(+), 65 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a7ddc60..7197d45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,47 +6,63 @@ Refactored single-file installer for 3x-ui VPN panel (based on x-ui-pro). ``` x-ui-latest.sh — main installer script (single file, run remotely) +x-ui-patch.sh — apply current features to an existing install (no DB changes) assets/ + backup/x-ui-backup.sh — backup / restore / list script + clash/clash.yaml — Clash/Mihomo subscription template (served by UA sniffing) + diagnostics/ + index.html — network diagnostics page (speed test, MTR, test files) + mtr-backend.py — localhost-only backend: MTR, LibreSpeed endpoints, clash.yaml generator + librespeed/ — vendored LibreSpeed engine (speedtest.js, speedtest_worker.js, LGPL) fake-sites/ - site-01/ … site-50/ — static HTML cover pages (index.html per site) -x-ui-installer/ — reference project (do not modify) -x-ui-pro/ — original project (do not modify) + site-01 … site-50/ — static HTML cover pages (index.html per site) ``` -## What the script does +Scripts download assets at install time from this repo's raw GitHub URL +(`https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/...`) — changes take +effect on servers only after push to `main`. -1. Stops / cleans any previous install +## What x-ui-latest.sh does + +1. Checks OS (Ubuntu 24.04/26.04, Debian 12/13) and rejects QEMU-emulated CPUs 2. Parses CLI arguments (`-install y`, `-subdomain`, `-reality_domain`, `-auto_domain y`, `-uninstall y`) -3. Installs packages (`install_packages`) — nginx-full, certbot, sqlite3, curl, wget, jq, ufw -4. Obtains Let's Encrypt certs via certbot standalone (`get_ssl_certs`) — two domains: panel + reality -5. Installs 3x-ui panel from MHSanaei/3x-ui latest release (`install_panel`) -6. Configures nginx (`configure_nginx`) — SNI stream, per-domain vhosts, shared includes snippet -7. Pushes all settings and inbounds into x-ui.db (`configure_xui_db`) — untouched from original -8. Downloads a random fake cover site from this repo (`install_fake_site`) → `/var/www/html/` -9. Tunes kernel/BBR (`tune_system`) -10. Sets up cron (`setup_cron`) — daily x-ui restart + nginx reload, monthly certbot renew -11. Configures UFW (`setup_firewall`) — 22/80/443 tcp, 443 udp -12. Prints panel URL + credentials (`show_results`) +3. Validates domains (panel ≠ REALITY), then stops/cleans any previous install +4. Installs packages (`install_packages`) — nginx-full, certbot, sqlite3, ufw, mtr, python3 … +5. Obtains Let's Encrypt certs via certbot standalone (`get_ssl_certs`) — panel + reality domains +6. Installs 3x-ui panel from MHSanaei/3x-ui latest release (`install_panel`) +7. Configures nginx (`configure_nginx`) — SNI stream (443 → reality:8443 / panel:7443), + per-domain vhosts, shared includes snippet, rate-limit zones +8. Pushes all settings and inbounds into x-ui.db (`configure_xui_db`) +9. Installs Clash subscription template (`install_clash_sub`) → `/var/www/subpage/clash.yaml.tpl`; + Clash/Mihomo user agents get generated clash.yaml, `?provider=1` bypasses it +10. Downloads a random fake cover site (`install_fake_site`) → `/var/www/html/` +11. Installs network diagnostics (`install_diagnostics`) → `/var/www/diagnostics/` + + `mtr-backend` systemd service (hardened, dedicated user, localhost-only) +12. Tunes kernel/BBR (`tune_system`) +13. Sets up cron (`setup_cron`) — daily x-ui restart + nginx reload; monthly certbot renew + with pre/post hooks stopping/starting nginx (certs are standalone-issued) +14. Configures UFW (`setup_firewall`) — 22/80/443 tcp, 443 udp +15. Prints panel URL + credentials (`show_results`) -## What was intentionally removed vs original +## Speed test (LibreSpeed) -- `sub2singbox` binary install and `@reboot` cron entry -- Custom web subscription page (`/var/www/subpage`, clash.yaml, sub-3x-ui.html) -- `web_path` and `sub2singbox_path` nginx locations -- Call to external `randomfakehtml.sh` → replaced with `install_fake_site()` using local assets +Upload over HTTP/2 is throttled per-stream by the h2 flow-control window, so a +single big POST measures ~4x low. The diagnostics page uses the vendored +LibreSpeed engine: parallel streams, XHR-progress measurement, no telemetry, no +database. h2 must stay enabled on the vhosts — trojan-gRPC needs it. Endpoints: +download = static `testfiles/test-100m.bin`; upload = `api/st/up` (Python sink, +`proxy_request_buffering off`); ping = `api/st/ping` (nginx `return 200`); +IP = `api/st/getip`. Speedtest locations use `limit_conn`, not `limit_req` +(the engine fires many requests). ## Inbounds created -| Protocol | Port | Transport | -|--------------|---------------|-----------| -| vless | 8443 | REALITY / TCP | -| vless | `$ws_port` | WebSocket | -| vless | UDS socket | XHTTP (gRPC) | -| trojan | `$trojan_port`| gRPC | - -## GitHub raw URL for fake sites - -`https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/assets/fake-sites/site-NN/index.html` +| Protocol | Port | Transport | +|----------|----------------|----------------| +| vless | 8443 | REALITY / TCP | +| vless | `$ws_port` | WebSocket | +| vless | UDS socket | XHTTP (gRPC) | +| trojan | `$trojan_port` | gRPC | ## Running @@ -54,3 +70,9 @@ x-ui-pro/ — original project (do not modify) bash <(curl -fsSL https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/x-ui-latest.sh) \ -install y -subdomain panel.example.com -reality_domain r.example.com ``` + +Patch an existing install (re-reads ports/paths from x-ui.db and nginx): + +```bash +bash <(curl -fsSL https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/x-ui-patch.sh) +``` diff --git a/x-ui-latest.sh b/x-ui-latest.sh index 8a9368f..81643a8 100644 --- a/x-ui-latest.sh +++ b/x-ui-latest.sh @@ -1,6 +1,6 @@ #!/bin/bash #################### x-ui-pro-refactor @ github.com/mozaroc ############################# -[[ $EUID -ne 0 ]] && echo "not root!" && sudo su - +[[ $EUID -ne 0 ]] && { echo "Run as root: sudo bash $0"; exit 1; } # ─── Output helpers ────────────────────────────────────────────────────────── msg_ok() { echo -e "\e[1;42m $1 \e[0m"; } @@ -62,14 +62,16 @@ INSTALL="n" AUTODOMAIN="n" CFALLOW="n" -# ─── Stop & clean previous install ─────────────────────────────────────────── -systemctl stop x-ui 2>/dev/null || true -rm -rf /etc/systemd/system/x-ui.service -rm -rf /usr/local/x-ui -rm -rf /etc/x-ui -rm -rf /etc/nginx/sites-enabled/* -rm -rf /etc/nginx/sites-available/* -rm -rf /etc/nginx/stream-enabled/* +# ─── Stop & clean previous install (called from main, after domain validation) ─ +clean_previous_install() { + systemctl stop x-ui 2>/dev/null || true + rm -rf /etc/systemd/system/x-ui.service + rm -rf /usr/local/x-ui + rm -rf /etc/x-ui + rm -rf /etc/nginx/sites-enabled/* + rm -rf /etc/nginx/sites-available/* + rm -rf /etc/nginx/stream-enabled/* +} # ─── Port / path generators ────────────────────────────────────────────────── get_port() { @@ -136,7 +138,8 @@ Pak=$(type apt &>/dev/null && echo "apt" || echo "yum") # ───────────────────────────────────────────────────────────────────────────── uninstall_xui() { printf 'y\n' | x-ui uninstall 2>/dev/null || true - rm -rf /etc/x-ui/ /usr/local/x-ui/ /usr/bin/x-ui/ + rm -rf /etc/x-ui/ /usr/local/x-ui/ + rm -f /usr/bin/x-ui $Pak -y remove nginx nginx-common nginx-core nginx-full python3-certbot-nginx $Pak -y purge nginx nginx-common nginx-core nginx-full python3-certbot-nginx $Pak -y autoremove @@ -269,6 +272,16 @@ get_ssl_certs() { configure_nginx() { mkdir -p /etc/nginx/stream-enabled /etc/nginx/snippets + # nginx >= 1.25.1 deprecates "listen ... http2" in favor of "http2 on;"; + # older versions (Debian 12 / Ubuntu 24.04) don't know the new directive + local ngx_ver http2_listen="" http2_on="" + ngx_ver=$(nginx -v 2>&1 | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' || echo 0) + if [[ "$(printf '%s\n' 1.25.1 "$ngx_ver" | sort -V | head -1)" == "1.25.1" ]]; then + http2_on="http2 on;" + else + http2_listen=" http2" + fi + # SNI-based stream: reality → 8443, domain → 7443 cat > /etc/nginx/stream-enabled/stream.conf <> /etc/nginx/nginx.conf grep -xqFR "load_module modules/ngx_stream_module.so;" /etc/nginx/* \ || sed -i '1s/^/load_module \/usr\/lib\/nginx\/modules\/ngx_stream_module.so; /' /etc/nginx/nginx.conf - grep -xqFR "load_module modules/ngx_stream_geoip2_module.so;" /etc/nginx* \ - || sed -i '2s/^/load_module \/usr\/lib\/nginx\/modules\/ngx_stream_geoip2_module.so; /' /etc/nginx/nginx.conf grep -xqFR "worker_rlimit_nofile 16384;" /etc/nginx/* \ || echo "worker_rlimit_nofile 16384;" >> /etc/nginx/nginx.conf sed -i "/worker_connections/c\worker_connections 4096;" /etc/nginx/nginx.conf @@ -432,8 +443,9 @@ map "\$is_clash_ua:\$arg_provider" \$serve_clash_yaml { server { server_tokens off; server_name ${domain}; - listen 7443 ssl http2 proxy_protocol; - listen [::]:7443 ssl http2 proxy_protocol; + listen 7443 ssl${http2_listen} proxy_protocol; + listen [::]:7443 ssl${http2_listen} proxy_protocol; + ${http2_on} index index.html index.htm index.php; root /var/www/html/; real_ip_header proxy_protocol; @@ -562,8 +574,9 @@ EOF server { server_tokens off; server_name ${reality_domain}; - listen 9443 ssl http2; - listen [::]:9443 ssl http2; + listen 9443 ssl${http2_listen}; + listen [::]:9443 ssl${http2_listen}; + ${http2_on} index index.html index.htm index.php; root /var/www/html/; ssl_protocols TLSv1.2 TLSv1.3; @@ -696,12 +709,16 @@ configure_xui_db() { x-ui stop 2>/dev/null || true - local output private_key public_key trojan_pass emoji_flag - output=$(/usr/local/x-ui/bin/xray-linux-amd64 x25519) + local output private_key public_key trojan_pass emoji_flag xray_bin + # install_panel renames armv5/6/7 binaries to xray-linux-arm + xray_bin="/usr/local/x-ui/bin/xray-linux-$(_arch)" + [[ -f "$xray_bin" ]] || xray_bin="/usr/local/x-ui/bin/xray-linux-arm" + output=$("$xray_bin" x25519) private_key=$(echo "$output" | grep "^PrivateKey:" | awk '{print $2}') public_key=$(echo "$output" | grep "^Password" | awk '{print $3}') trojan_pass=$(gen_random_string 10) - emoji_flag=$(LC_ALL=en_US.UTF-8 curl -s https://ipwho.is/ | jq -r '.flag.emoji') + emoji_flag=$(LC_ALL=en_US.UTF-8 curl -s --max-time 10 https://ipwho.is/ | jq -r '.flag.emoji' 2>/dev/null) + [[ -z "$emoji_flag" || "$emoji_flag" == "null" ]] && emoji_flag="🌐" local sub_uri="https://${domain}/${sub_path}/" local json_uri="https://${domain}/${json_path}?name=" @@ -1061,7 +1078,9 @@ tune_system() { setup_cron() { crontab -l 2>/dev/null | grep -v "certbot\|x-ui\|cloudflareips" | crontab - (crontab -l 2>/dev/null; echo '@daily x-ui restart > /dev/null 2>&1 && nginx -s reload') | crontab - - (crontab -l 2>/dev/null; echo '@monthly certbot renew --nginx --non-interactive --post-hook "nginx -s reload" > /dev/null 2>&1') | crontab - + # Certs were issued with --standalone: renewal needs port 80 free, + # so stop nginx for the few seconds certbot runs + (crontab -l 2>/dev/null; echo '@monthly certbot renew --non-interactive --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx" > /dev/null 2>&1') | crontab - } # ───────────────────────────────────────────────────────────────────────────── @@ -1084,16 +1103,10 @@ show_results() { if systemctl is-active --quiet x-ui; then printf '0\n' | x-ui | grep --color=never -i ':' msg_inf "────────────────────────────────────────────────────────────────────────────────" - nginx -T | grep -i 'ssl_certificate\|ssl_certificate_key' - msg_inf "────────────────────────────────────────────────────────────────────────────────" - certbot certificates | grep -i 'Path:\|Domains:\|Expiry Date:' - msg_inf "────────────────────────────────────────────────────────────────────────────────" msg_inf "X-UI Secure Panel: https://${domain}/${panel_path}/\n" echo -e "Username: ${config_username}\n" echo -e "Password: ${config_password}\n" msg_inf "────────────────────────────────────────────────────────────────────────────────" - msg_inf "Clash Sub (auto by UA): https://${domain}/${sub_path}/\n" - msg_inf "────────────────────────────────────────────────────────────────────────────────" msg_inf "Network Diagnostics: https://${domain}${diag_path}\n" msg_inf "────────────────────────────────────────────────────────────────────────────────" msg_inf "Please save this screen!" @@ -1109,6 +1122,7 @@ show_results() { # ───────────────────────────────────────────────────────────────────────────── main() { validate_domains + clean_previous_install install_packages get_server_ip get_ssl_certs diff --git a/x-ui-patch.sh b/x-ui-patch.sh index 4dc6442..7270253 100644 --- a/x-ui-patch.sh +++ b/x-ui-patch.sh @@ -136,6 +136,16 @@ blue "Regenerating nginx configs..." mkdir -p /etc/nginx/stream-enabled /etc/nginx/snippets \ /etc/nginx/sites-available /etc/nginx/sites-enabled +# nginx >= 1.25.1 deprecates "listen ... http2" in favor of "http2 on;"; +# older versions (Debian 12 / Ubuntu 24.04) don't know the new directive +http2_listen="" ; http2_on="" +ngx_ver=$(nginx -v 2>&1 | grep -oP '[0-9]+\.[0-9]+\.[0-9]+' || echo 0) +if [[ "$(printf '%s\n' 1.25.1 "$ngx_ver" | sort -V | head -1)" == "1.25.1" ]]; then + http2_on="http2 on;" +else + http2_listen=" http2" +fi + # ── SNI stream ──────────────────────────────────────────────────────────────── cat > /etc/nginx/stream-enabled/stream.conf <> /etc/nginx/nginx.conf grep -xqFR "load_module modules/ngx_stream_module.so;" /etc/nginx/* \ || sed -i '1s/^/load_module \/usr\/lib\/nginx\/modules\/ngx_stream_module.so; /' /etc/nginx/nginx.conf -grep -xqFR "load_module modules/ngx_stream_geoip2_module.so;" /etc/nginx/* \ - || sed -i '2s/^/load_module \/usr\/lib\/nginx\/modules\/ngx_stream_geoip2_module.so; /' /etc/nginx/nginx.conf grep -xqFR "worker_rlimit_nofile 16384;" /etc/nginx/* \ || echo "worker_rlimit_nofile 16384;" >> /etc/nginx/nginx.conf sed -i "/worker_connections/c\worker_connections 4096;" /etc/nginx/nginx.conf @@ -293,8 +301,9 @@ map "\$is_clash_ua:\$arg_provider" \$serve_clash_yaml { server { server_tokens off; server_name ${domain}; - listen 7443 ssl http2 proxy_protocol; - listen [::]:7443 ssl http2 proxy_protocol; + listen 7443 ssl${http2_listen} proxy_protocol; + listen [::]:7443 ssl${http2_listen} proxy_protocol; + ${http2_on} index index.html index.htm index.php; root /var/www/html/; real_ip_header proxy_protocol; @@ -408,8 +417,9 @@ cat > "/etc/nginx/sites-available/${reality_domain}" <