Running without parameters previously skipped package install and failed at certbot on a clean server. Packages now install by default; use -install n to skip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.3 KiB
x-ui-pro-refactor
Refactored single-file installer for 3x-ui VPN panel (based on x-ui-pro).
Repository structure
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)
x-ui-adguard.sh — optional: AdGuard Home on the panel domain (DoH at
/dns-query, admin UI at random /adg-<rand>/ path)
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)
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.
What x-ui-latest.sh does
- Checks OS (Ubuntu 24.04/26.04, Debian 12/13) and rejects QEMU-emulated CPUs
- Parses CLI arguments (
-install nto skip package install — defaulty,-subdomain,-reality_domain,-auto_domain y,-version <ver>,-uninstall y) - Validates domains (panel ≠ REALITY), then stops/cleans any previous install
- Installs packages (
install_packages) — nginx-full, certbot, sqlite3, ufw, mtr, python3 … - Obtains Let's Encrypt certs via certbot standalone (
get_ssl_certs) — panel + reality domains - Installs 3x-ui panel from MHSanaei/3x-ui latest release (
install_panel) - Configures nginx (
configure_nginx) — SNI stream (443 → reality:8443 / panel:7443), per-domain vhosts, shared includes snippet, rate-limit zones - Pushes all settings and inbounds into x-ui.db (
configure_xui_db). Share-link endpoints use thehoststable (supersedes legacyexternalProxyarrays in stream_settings): one host per inbound — REALITY getssecurity=same, the rest front through nginx :443 withsecurity=tls, fingerprint firefox - Installs Clash subscription template (
install_clash_sub) →/var/www/subpage/clash.yaml.tpl; Clash/Mihomo user agents get generated clash.yaml,?provider=1bypasses it - Downloads a random fake cover site (
install_fake_site) →/var/www/html/ - Installs network diagnostics (
install_diagnostics) →/var/www/diagnostics/+mtr-backendsystemd service (hardened, dedicated user, localhost-only). Access is gated by the 3x-ui panel session via an nginx SSO bridge at/<panel_path>/diag: auth_request validates the session againstGET <basePath>/panel/(with X-Requested-With → 401 instead of a login redirect), then issues a path-scopeddiag_keycookie and redirects to the diag page. Hitting the diag path directly without the cookie 302-bounces through that bridge, so a bookmarked diag link "just works" once logged into the panel (API/asset sub-locations stay 404 without the cookie). The 3x-ui session cookie is Path-scoped to the panel base path, which is why the bridge must live under the panel path - Tunes kernel/BBR (
tune_system) - 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) - Configures UFW (
setup_firewall) — 22/80/443 tcp, 443 udp - Prints panel URL + credentials (
show_results)
Speed test (LibreSpeed)
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 |
Running
bash <(curl -fsSL https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/x-ui-latest.sh) \
-subdomain panel.example.com -reality_domain r.example.com
Patch an existing install (re-reads ports/paths from x-ui.db and nginx):
bash <(curl -fsSL https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/x-ui-patch.sh)
Add AdGuard Home on the panel domain (standalone, re-run safe, -uninstall y
to remove). AGH binds localhost only; nginx bridges /dns-query (DoH,
allow_unencrypted_doh) and a random /adg-<rand>/ admin path via
snippets/adguard.conf included in the panel vhost. Installer/patch
regenerate the vhost and drop that include — re-run this script after them:
bash <(curl -fsSL https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main/x-ui-adguard.sh)