#!/bin/bash #################### x-ui-pro-refactor @ github.com/mozaroc ############################# [[ $EUID -ne 0 ]] && echo "not root!" && sudo su - # ─── Output helpers ────────────────────────────────────────────────────────── msg_ok() { echo -e "\e[1;42m $1 \e[0m"; } msg_err() { echo -e "\e[1;41m $1 \e[0m"; } msg_inf() { echo -e "\e[1;34m$1\e[0m"; } echo; msg_inf ' ___ _ _ _ ' msg_inf ' \/ __ | | | __ |_) |_) / \ ' msg_inf ' /\ |_| _|_ | | \ \_/ '; echo # ─── Pre-flight checks ─────────────────────────────────────────────────────── check_os() { local os_id os_version os_id=$(grep -oP '(?<=^ID=).+' /etc/os-release 2>/dev/null | tr -d '"') os_version=$(grep -oP '(?<=^VERSION_ID=").+(?=")' /etc/os-release 2>/dev/null) case "${os_id}" in ubuntu) [[ "$os_version" == "24.04" || "$os_version" == "26.04" ]] && return 0 ;; debian) [[ "$os_version" == "12" || "$os_version" == "13" ]] && return 0 ;; esac msg_err "Unsupported OS: ${os_id} ${os_version}" echo -e "\nThis script supports:\n Ubuntu 24.04 / 26.04\n Debian 12 / 13" echo -e "\nPlease reinstall your server with one of the supported OS versions and try again." exit 1 } check_cpu() { local cpu_model cpu_model=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2-) if echo "$cpu_model" | grep -qi 'QEMU'; then msg_err "QEMU virtual CPU detected!" echo -e "\nYour VPS is running with an emulated QEMU processor." echo -e "Please contact your hosting provider and ask them to switch the CPU type" echo -e "to \e[1;33mhost-passthrough\e[0m (expose real CPU model to the VM)." echo -e "\nThis is required for correct operation of the Xray core." exit 1 fi } check_os check_cpu # ─── Constants ─────────────────────────────────────────────────────────────── XUIDB="/etc/x-ui/x-ui.db" GITHUB_RAW="https://raw.githubusercontent.com/mozaroc/3x-ui-pro/main" FAKE_SITE_COUNT=50 # ─── Default argument values ───────────────────────────────────────────────── domain="" reality_domain="" UNINSTALL="x" 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/* # ─── Port / path generators ────────────────────────────────────────────────── get_port() { echo $(( ((RANDOM<<15)|RANDOM) % 49152 + 10000 )) } gen_random_string() { local length="$1" head -c 4096 /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c "$length" echo } check_free() { nc -z 127.0.0.1 "$1" &>/dev/null return $? } make_port() { while true; do local PORT PORT=$(get_port) if ! check_free "$PORT"; then echo "$PORT" break fi done } # ─── Generate ports & paths (done once at startup) ─────────────────────────── sub_port=$(make_port) panel_port=$(make_port) ws_port=$(make_port) trojan_port=$(make_port) sub_path=$(gen_random_string 10) json_path=$(gen_random_string 10) panel_path=$(gen_random_string 10) ws_path=$(gen_random_string 10) trojan_path=$(gen_random_string 10) xhttp_path=$(gen_random_string 10) config_username=$(gen_random_string 10) config_password=$(gen_random_string 10) diag_path="/net-$(gen_random_string 12)/" mtr_backend_port=$(make_port) # ─── Argument parsing ──────────────────────────────────────────────────────── while [ "$#" -gt 0 ]; do case "$1" in -auto_domain) AUTODOMAIN="$2"; shift 2 ;; -install) INSTALL="$2"; shift 2 ;; -subdomain) domain="$2"; shift 2 ;; -reality_domain) reality_domain="$2"; shift 2 ;; -ONLY_CF_IP_ALLOW) CFALLOW="$2"; shift 2 ;; -uninstall) UNINSTALL="$2"; shift 2 ;; *) shift 1 ;; esac done # ─── Detect package manager ─────────────────────────────────────────────────── Pak=$(type apt &>/dev/null && echo "apt" || echo "yum") # ───────────────────────────────────────────────────────────────────────────── # UNINSTALL # ───────────────────────────────────────────────────────────────────────────── 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/ $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 $Pak -y autoclean rm -rf /var/www/html/ /var/www/diagnostics/ /etc/nginx/ /usr/share/nginx/ systemctl stop mtr-backend 2>/dev/null || true systemctl disable mtr-backend 2>/dev/null || true rm -f /etc/systemd/system/mtr-backend.service rm -rf /usr/local/lib/3x-ui-pro/ systemctl daemon-reload 2>/dev/null || true } if [[ ${UNINSTALL} == *"y"* ]]; then uninstall_xui clear && msg_ok "Completely Uninstalled!" && exit 0 fi # ───────────────────────────────────────────────────────────────────────────── # GET SERVER IP # ───────────────────────────────────────────────────────────────────────────── IP4_REGEX="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" IP6_REGEX="([a-f0-9:]+:+)+[a-f0-9]+" get_server_ip() { IP4=$(ip route get 8.8.8.8 2>&1 | grep -Po -- 'src \K\S*') IP6=$(ip route get 2620:fe::fe 2>&1 | grep -Po -- 'src \K\S*') [[ $IP4 =~ $IP4_REGEX ]] || IP4=$(curl -s ipv4.icanhazip.com | tr -d '[:space:]') [[ $IP6 =~ $IP6_REGEX ]] || IP6=$(curl -s ipv6.icanhazip.com | tr -d '[:space:]') } # Early IP fetch for auto-domain IP4=$(ip route get 8.8.8.8 2>&1 | grep -Po -- 'src \K\S*') [[ $IP4 =~ $IP4_REGEX ]] || IP4=$(curl -s ipv4.icanhazip.com | tr -d '[:space:]') if [[ ${AUTODOMAIN} == *"y"* ]]; then domain="${IP4}.cdn-one.org" reality_domain="${IP4//./-}.cdn-one.org" fi # ───────────────────────────────────────────────────────────────────────────── # DOMAIN VALIDATION # ───────────────────────────────────────────────────────────────────────────── validate_domains() { while true; do [[ -n "$domain" ]] && break echo -en "Enter available subdomain (sub.domain.tld): " && read -r domain done domain=$(echo "$domain" | tr -d '[:space:]') SubDomain=$(echo "$domain" | sed 's/^[^ ]* \|\..*//g') MainDomain=$(echo "$domain" | sed 's/.*\.\([^.]*\..*\)$/\1/') [[ "${SubDomain}.${MainDomain}" != "${domain}" ]] && MainDomain=${domain} while true; do [[ -n "$reality_domain" ]] && break echo -en "Enter available subdomain for REALITY (sub.domain.tld): " && read -r reality_domain done reality_domain=$(echo "$reality_domain" | tr -d '[:space:]') RealitySubDomain=$(echo "$reality_domain" | sed 's/^[^ ]* \|\..*//g') RealityMainDomain=$(echo "$reality_domain" | sed 's/.*\.\([^.]*\..*\)$/\1/') [[ "${RealitySubDomain}.${RealityMainDomain}" != "${reality_domain}" ]] && RealityMainDomain=${reality_domain} } # ───────────────────────────────────────────────────────────────────────────── # INSTALL PACKAGES # ───────────────────────────────────────────────────────────────────────────── install_packages() { ufw disable 2>/dev/null || true if [[ ${INSTALL} == *"y"* ]]; then local version version=$(grep -oP '(?<=VERSION_ID=")[0-9]+' /etc/os-release) [[ "$version" == "20" || "$version" == "22" ]] && echo "System: Ubuntu $version" $Pak -y update $Pak -y install curl wget jq bash sudo nginx-full certbot python3-certbot-nginx sqlite3 ufw netcat-openbsd mtr python3 libcap2-bin systemctl daemon-reload && systemctl enable --now nginx fi apt-get install -yqq --no-install-recommends ca-certificates } # ───────────────────────────────────────────────────────────────────────────── # SSL CERTIFICATES # ───────────────────────────────────────────────────────────────────────────── get_ssl_certs() { systemctl stop nginx 2>/dev/null || true fuser -k 80/tcp 80/udp 443/tcp 443/udp 2>/dev/null || true if [[ ${AUTODOMAIN} == *"y"* ]]; then local resolve_ok=true for d in "$domain" "$reality_domain"; do local a a=$(getent ahostsv4 "$d" 2>/dev/null | awk 'NR==1{print $1}') if [[ "$a" != "$IP4" ]]; then msg_err "Auto-domain $d does not resolve to $IP4. Fix DNS and retry." resolve_ok=false fi done [[ $resolve_ok == false ]] && exit 1 fi certbot certonly --standalone --non-interactive --agree-tos \ --register-unsafely-without-email -d "$domain" if [[ ! -d "/etc/letsencrypt/live/${domain}/" ]]; then systemctl start nginx >/dev/null 2>&1 msg_err "$domain SSL could not be generated! Check Domain/IP." && exit 1 fi certbot certonly --standalone --non-interactive --agree-tos \ --register-unsafely-without-email -d "$reality_domain" if [[ ! -d "/etc/letsencrypt/live/${reality_domain}/" ]]; then systemctl start nginx >/dev/null 2>&1 msg_err "$reality_domain SSL could not be generated! Check Domain/IP." && exit 1 fi mkdir -p /root/cert/${domain} chmod 755 /root/cert/* ln -sf /etc/letsencrypt/live/${domain}/fullchain.pem /root/cert/${domain}/fullchain.pem ln -sf /etc/letsencrypt/live/${domain}/privkey.pem /root/cert/${domain}/privkey.pem } # ───────────────────────────────────────────────────────────────────────────── # CONFIGURE NGINX # ───────────────────────────────────────────────────────────────────────────── configure_nginx() { mkdir -p /etc/nginx/stream-enabled /etc/nginx/snippets # 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 # HTTP → HTTPS redirect cat > /etc/nginx/sites-available/80.conf < /etc/nginx/snippets/includes.conf <\d+)/(?.*)\$ { if (\$hack = 1) { return 404; } client_max_body_size 0; client_body_timeout 1d; grpc_read_timeout 1d; grpc_socket_keepalive on; proxy_read_timeout 1d; proxy_http_version 1.1; proxy_buffering off; proxy_request_buffering off; proxy_socket_keepalive on; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; if (\$content_type ~* "GRPC") { grpc_pass grpc://127.0.0.1:\$fwdport\$is_args\$args; break; } if (\$http_upgrade ~* "(WEBSOCKET|WS)") { proxy_pass http://127.0.0.1:\$fwdport\$is_args\$args; break; } if (\$request_method ~* ^(PUT|POST|GET)\$) { proxy_pass http://127.0.0.1:\$fwdport\$is_args\$args; break; } } location / { try_files \$uri \$uri/ =404; } EOF # Main domain vhost (TLS termination at 7443, proxy_protocol) cat > "/etc/nginx/sites-available/${domain}" <|\.\.\.|\.\.\/|\/\/\/)") { set \$hack 1; } error_page 400 401 402 403 500 501 502 503 504 =404 /404; proxy_intercept_errors on; location /${panel_path}/ { proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_pass https://127.0.0.1:${panel_port}; } location /${panel_path} { proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_pass https://127.0.0.1:${panel_port}; } # ── Network diagnostics page ───────────────────────────────────────────── location ^~ ${diag_path} { limit_req zone=diag_page burst=10 nodelay; limit_conn per_ip 5; alias /var/www/diagnostics/; index index.html; try_files \$uri \$uri/ /index.html; add_header Cache-Control "no-store" always; add_header X-Robots-Tag "noindex, nofollow" always; } # ── Diagnostics MTR API ────────────────────────────────────────────────── location ^~ ${diag_path}api/mtr { limit_req zone=diag_api burst=2 nodelay; limit_conn per_ip 2; proxy_pass http://127.0.0.1:${mtr_backend_port}/api/mtr; proxy_http_version 1.1; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_read_timeout 120s; proxy_send_timeout 120s; } # ── Speed test upload receiver ─────────────────────────────────────────── # proxy_request_buffering off = nginx streams body to backend in real time; # backend responds only after reading all bytes → accurate timing on client location ^~ ${diag_path}api/upload { limit_req zone=diag_page burst=5 nodelay; proxy_pass http://127.0.0.1:${mtr_backend_port}/api/upload; proxy_http_version 1.1; proxy_set_header X-Real-IP \$remote_addr; proxy_request_buffering off; client_max_body_size 128m; proxy_read_timeout 120s; proxy_send_timeout 120s; add_header Cache-Control "no-store" always; } # ── Download test files ────────────────────────────────────────────────── location ^~ ${diag_path}testfiles/ { alias /var/www/diagnostics/testfiles/; access_log off; add_header Cache-Control "no-store, no-cache, must-revalidate" always; add_header Content-Disposition "attachment" always; } include /etc/nginx/snippets/includes.conf; } EOF # Reality domain vhost (plain TLS at 9443, no proxy_protocol) cat > "/etc/nginx/sites-available/${reality_domain}" <|\.\.\.|\.\.\/|\/\/\/)") { set \$hack 1; } error_page 400 401 402 403 500 501 502 503 504 =404 /404; proxy_intercept_errors on; location /${panel_path}/ { proxy_redirect off; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:${panel_port}; } location /${panel_path} { proxy_redirect off; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:${panel_port}; } include /etc/nginx/snippets/includes.conf; } EOF # Activate configs if [[ -f "/etc/nginx/sites-available/${domain}" ]]; then rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default ln -sf "/etc/nginx/sites-available/${domain}" /etc/nginx/sites-enabled/ ln -sf "/etc/nginx/sites-available/${reality_domain}" /etc/nginx/sites-enabled/ ln -sf "/etc/nginx/sites-available/80.conf" /etc/nginx/sites-enabled/ else msg_err "${domain} nginx config not found!" && exit 1 fi if [[ $(nginx -t 2>&1 | grep -o 'successful') != "successful" ]]; then msg_err "nginx config check failed!" && exit 1 fi systemctl start nginx } # ───────────────────────────────────────────────────────────────────────────── # INSTALL PANEL (3x-ui) # ───────────────────────────────────────────────────────────────────────────── _arch() { case "$(uname -m)" in x86_64|x64|amd64) echo 'amd64' ;; i*86|x86) echo '386' ;; armv8*|armv8|arm64|aarch64) echo 'arm64' ;; armv7*|armv7|arm) echo 'armv7' ;; armv6*|armv6) echo 'armv6' ;; armv5*|armv5) echo 'armv5' ;; s390x) echo 's390x' ;; *) echo "Unsupported CPU architecture!" && exit 1 ;; esac } _panel_initial_config() { /usr/local/x-ui/x-ui setting -username "asdfasdf" -password "asdfasdf" -port "2096" -webBasePath "asdfasdf" /usr/local/x-ui/x-ui migrate } install_panel() { local tag_version apt-get update && apt-get install -y -q wget curl tar tzdata cd /usr/local/ tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" \ | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [[ -z "$tag_version" ]]; then tag_version=$(curl -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" \ | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') fi if [[ -z "$tag_version" ]]; then echo "Failed to fetch 3x-ui version." && exit 1 fi echo "Installing 3x-ui ${tag_version} ..." wget -N -O /usr/local/x-ui-linux-$(_arch).tar.gz \ "https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(_arch).tar.gz" [[ $? -ne 0 ]] && echo "Download failed." && exit 1 wget -O /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh [[ $? -ne 0 ]] && echo "Failed to download x-ui.sh" && exit 1 [[ -d /usr/local/x-ui/ ]] && systemctl stop x-ui 2>/dev/null; rm -rf /usr/local/x-ui/ tar zxvf x-ui-linux-$(_arch).tar.gz rm -f x-ui-linux-$(_arch).tar.gz cd x-ui chmod +x x-ui x-ui.sh if [[ $(_arch) == "armv5" || $(_arch) == "armv6" || $(_arch) == "armv7" ]]; then mv bin/xray-linux-$(_arch) bin/xray-linux-arm chmod +x bin/xray-linux-arm fi chmod +x bin/xray-linux-$(_arch) mv -f /usr/bin/x-ui-temp /usr/bin/x-ui chmod +x /usr/bin/x-ui _panel_initial_config cp -f x-ui.service.debian /etc/systemd/system/x-ui.service systemctl daemon-reload systemctl enable x-ui systemctl start x-ui msg_ok "3x-ui ${tag_version} installed." } # ───────────────────────────────────────────────────────────────────────────── # CONFIGURE X-UI DATABASE # ───────────────────────────────────────────────────────────────────────────── configure_xui_db() { if [[ ! -f $XUIDB ]]; then msg_err "x-ui.db not found — panel may not be installed." && exit 1 fi 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) 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') local sub_uri="https://${domain}/${sub_path}/" local json_uri="https://${domain}/${json_path}?name=" # Prepare short IDs for REALITY local shor shor=($(openssl rand -hex 8) $(openssl rand -hex 8) $(openssl rand -hex 8) $(openssl rand -hex 8) \ $(openssl rand -hex 8) $(openssl rand -hex 8) $(openssl rand -hex 8) $(openssl rand -hex 8)) sqlite3 $XUIDB </dev/null || true chmod 644 /var/www/html/index.html msg_ok "Fake cover site '${site_id}' installed." else msg_err "Failed to download fake site ${site_id} from GitHub." fi } # ───────────────────────────────────────────────────────────────────────────── # INSTALL NETWORK DIAGNOSTICS PAGE # ───────────────────────────────────────────────────────────────────────────── install_diagnostics() { local diag_webroot="/var/www/diagnostics" local backend_script="/usr/local/lib/3x-ui-pro/mtr-backend.py" # Diagnostics HTML page mkdir -p "${diag_webroot}" curl -fsSL "${GITHUB_RAW}/assets/diagnostics/index.html" -o "${diag_webroot}/index.html" sed -i \ -e "s|__DIAG_PATH__|${diag_path}|g" \ -e "s|__SERVER_DOMAIN__|${domain}|g" \ -e "s|__SERVER_IP__|${IP4}|g" \ "${diag_webroot}/index.html" # Test download files local testfiles="${diag_webroot}/testfiles" mkdir -p "${testfiles}" [[ -f "${testfiles}/test-15k.bin" ]] || dd if=/dev/zero bs=1024 count=15 of="${testfiles}/test-15k.bin" status=none [[ -f "${testfiles}/test-17k.bin" ]] || dd if=/dev/zero bs=1024 count=17 of="${testfiles}/test-17k.bin" status=none [[ -f "${testfiles}/test-100m.bin" ]] || dd if=/dev/zero bs=1048576 count=100 of="${testfiles}/test-100m.bin" status=none [[ -f "${testfiles}/test-512m.bin" ]] || dd if=/dev/zero bs=1048576 count=512 of="${testfiles}/test-512m.bin" status=none [[ -f "${testfiles}/test-1g.bin" ]] || dd if=/dev/zero bs=1048576 count=1024 of="${testfiles}/test-1g.bin" status=none chown -R www-data:www-data "${diag_webroot}" 2>/dev/null || true # MTR backend Python script mkdir -p "$(dirname "${backend_script}")" curl -fsSL "${GITHUB_RAW}/assets/diagnostics/mtr-backend.py" -o "${backend_script}" chmod 755 "${backend_script}" # Grant mtr raw socket capability (runs as restricted user, no root needed) command -v setcap &>/dev/null && setcap cap_net_raw+ep "$(command -v mtr)" 2>/dev/null || true # Dedicated system user for mtr-backend id mtr-backend &>/dev/null || \ useradd --system --no-create-home --shell /usr/sbin/nologin mtr-backend # Systemd service for mtr-backend cat > /etc/systemd/system/mtr-backend.service <> /etc/sysctl.conf done sysctl -p } # ───────────────────────────────────────────────────────────────────────────── # CRON JOBS # ───────────────────────────────────────────────────────────────────────────── 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 - } # ───────────────────────────────────────────────────────────────────────────── # FIREWALL # ───────────────────────────────────────────────────────────────────────────── setup_firewall() { ufw disable ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw allow 443/udp ufw --force enable } # ───────────────────────────────────────────────────────────────────────────── # SHOW RESULTS # ───────────────────────────────────────────────────────────────────────────── show_results() { clear 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 "Network Diagnostics: https://${domain}${diag_path}\n" msg_inf "────────────────────────────────────────────────────────────────────────────────" msg_inf "Please save this screen!" else nginx -t printf '0\n' | x-ui | grep --color=never -i ':' msg_err "x-ui or nginx check failed. Try on a clean Linux install." fi } # ───────────────────────────────────────────────────────────────────────────── # MAIN # ───────────────────────────────────────────────────────────────────────────── main() { validate_domains install_packages get_server_ip get_ssl_certs if systemctl is-active --quiet x-ui; then x-ui restart else install_panel fi configure_nginx configure_xui_db install_fake_site install_diagnostics tune_system setup_cron setup_firewall if ! systemctl is-enabled --quiet x-ui; then systemctl daemon-reload && systemctl enable x-ui.service fi x-ui restart show_results } main