diff --git a/README.ar_EG.md b/README.ar_EG.md index 0dd359f8..dc4c4490 100644 --- a/README.ar_EG.md +++ b/README.ar_EG.md @@ -1,4 +1,4 @@ -[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[中文](/README.zh_CN.md) | [English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)

@@ -7,50 +7,45 @@

-[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v3.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v3)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v3) - -**3X-UI** — لوحة تحكم متقدمة مفتوحة المصدر تعتمد على الويب مصممة لإدارة خادم Xray-core. توفر واجهة سهلة الاستخدام لتكوين ومراقبة بروتوكولات VPN والوكيل المختلفة. +**3X-UI** — لوحة تحكم متقدمة مفتوحة المصدر لإدارة خوادم Xray-core. توفر واجهة سهلة الاستخدام لتكوين ومراقبة بروتوكولات VPN والخوادم الوكيلة المختلفة. > [!IMPORTANT] -> هذا المشروع مخصص للاستخدام الشخصي والاتصال فقط، يرجى عدم استخدامه لأغراض غير قانونية، يرجى عدم استخدامه في بيئة الإنتاج. +> هذا المشروع مخصص للاستخدام الشخصي والبحث في الاتصالات فقط. يرجى عدم استخدامه لأغراض غير قانونية أو في بيئة الإنتاج. -كمشروع محسن من مشروع X-UI الأصلي، يوفر 3X-UI استقرارًا محسنًا ودعمًا أوسع للبروتوكولات وميزات إضافية. +كمحاكاة محسنة من مشروع X-UI الأصلي، يوفر 3X-UI استقرارًا أفضل ودعمًا أوسع للبروتوكولات وميزات إضافية. ## البدء السريع -``` +### سكريبت التثبيت بنقرة واحدة + +**المصدر الرسمي:** +```bash bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` -للحصول على الوثائق الكاملة، يرجى زيارة [ويكي المشروع](https://github.com/MHSanaei/3x-ui/wiki). +**تسريع GitHub (موصى به للمستخدمين في الصين):** -## شكر خاص إلى +الطريقة الأولى (استخدام عنوان التسريع الافتراضي): +```bash +bash <(curl -Ls https://gh.kejilion.pro/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +الطريقة الثانية (استخدام عنوان تسريع مخصص): +```bash +bash <(curl -Ls https://عنوان-المرآة-المخصص.com/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +للوثائق الكاملة، يرجى زيارة [ويكي المشروع](https://github.com/MHSanaei/3x-ui/wiki). + +## شكر خاص - [alireza0](https://github.com/alireza0/) -## الاعتراف +## الإشادة -- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (الترخيص: **GPL-3.0**): _قواعد توجيه v2ray/xray و v2ray/xray-clients المحسنة مع النطاقات الإيرانية المدمجة وتركيز على الأمان وحظر الإعلانات._ -- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (الترخيص: **GPL-3.0**): _يحتوي هذا المستودع على قواعد توجيه V2Ray محدثة تلقائيًا بناءً على بيانات النطاقات والعناوين المحظورة في روسيا._ +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (الترخيص: **GPL-3.0**): _قواعد توجيه محسنة لـ v2ray/xray مع نطاقات إيرانية._ +- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (الترخيص: **GPL-3.0**): _قواعد V2Ray المحدثة تلقائيًا بناءً على النطاقات المحظورة في روسيا._ ## دعم المشروع -**إذا كان هذا المشروع مفيدًا لك، فقد ترغب في إعطائه**:star2: - - -Buy Me A Coffee - -
- - Crypto donation button by NOWPayments - - -## النجوم عبر الزمن - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) +**إذا كان هذا المشروع مفيدًا لك، يمكنك إعطائه**:star2: diff --git a/README.es_ES.md b/README.es_ES.md index 50758f08..7f22e20b 100644 --- a/README.es_ES.md +++ b/README.es_ES.md @@ -1,4 +1,4 @@ -[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[中文](/README.zh_CN.md) | [English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)

@@ -7,28 +7,35 @@

-[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v3.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v3)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v3) - -**3X-UI** — panel de control avanzado basado en web de código abierto diseñado para gestionar el servidor Xray-core. Ofrece una interfaz fácil de usar para configurar y monitorear varios protocolos VPN y proxy. +**3X-UI** — panel de control web avanzado de código abierto diseñado para gestionar servidores Xray-core. Ofrece una interfaz fácil de usar para configurar y monitorear varios protocolos VPN y proxy. > [!IMPORTANT] -> Este proyecto es solo para uso personal y comunicación, por favor no lo use para fines ilegales, por favor no lo use en un entorno de producción. +> Este proyecto es solo para uso personal e investigación en comunicaciones. Por favor, no lo use para fines ilegales ni en entornos de producción. Como una versión mejorada del proyecto X-UI original, 3X-UI proporciona mayor estabilidad, soporte más amplio de protocolos y características adicionales. ## Inicio Rápido -``` +### Script de Instalación con Un Clic + +**Fuente Oficial:** +```bash bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` -Para documentación completa, visita la [Wiki del proyecto](https://github.com/MHSanaei/3x-ui/wiki). +**Aceleración de GitHub (Recomendado para usuarios en China):** + +Método 1 (Usando la dirección de aceleración predeterminada): +```bash +bash <(curl -Ls https://gh.kejilion.pro/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +Método 2 (Usando una dirección de aceleración personalizada): +```bash +bash <(curl -Ls https://su-direccion-mirror.com/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +Para documentación completa, visite la [Wiki del proyecto](https://github.com/MHSanaei/3x-ui/wiki). ## Un Agradecimiento Especial a @@ -36,22 +43,9 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M ## Reconocimientos -- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas para v2ray/xray y v2ray/xray-clients con dominios iraníes incorporados y un enfoque en seguridad y bloqueo de anuncios._ -- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Licencia: **GPL-3.0**): _Este repositorio contiene reglas de enrutamiento V2Ray actualizadas automáticamente basadas en datos de dominios y direcciones bloqueadas en Rusia._ +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas para v2ray/xray con dominios iraníes._ +- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Licencia: **GPL-3.0**): _Reglas V2Ray actualizadas automáticamente basadas en dominios bloqueados en Rusia._ ## Apoyar el Proyecto **Si este proyecto te es útil, puedes darle una**:star2: - - -Buy Me A Coffee - - -
- - Crypto donation button by NOWPayments - - -## Estrellas a lo Largo del Tiempo - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) diff --git a/README.fa_IR.md b/README.fa_IR.md index e08fd8c2..131a15b9 100644 --- a/README.fa_IR.md +++ b/README.fa_IR.md @@ -1,4 +1,4 @@ -[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[中文](/README.zh_CN.md) | [English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)

@@ -7,51 +7,45 @@

-[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v3.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v3)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v3) - -**3X-UI** — یک پنل کنترل پیشرفته مبتنی بر وب با کد باز که برای مدیریت سرور Xray-core طراحی شده است. این پنل یک رابط کاربری آسان برای پیکربندی و نظارت بر پروتکل‌های مختلف VPN و پراکسی ارائه می‌دهد. +**3X-UI** — پنل مدیریت وب پیشرفته متن‌باز برای مدیریت سرورهای Xray-core. این پنل یک رابط کاربری آسان برای پیکربندی و نظارت بر پروتکل‌های مختلف VPN و پروکسی فراهم می‌کند. > [!IMPORTANT] -> این پروژه فقط برای استفاده شخصی و ارتباطات است، لطفاً از آن برای اهداف غیرقانونی استفاده نکنید، لطفاً از آن در محیط تولید استفاده نکنید. +> این پروژه فقط برای استفاده شخصی و تحقیقات ارتباطی است. لطفاً از آن برای اهداف غیرقانونی استفاده نکنید و در محیط تولید به کار نبرید. به عنوان یک نسخه بهبود یافته از پروژه اصلی X-UI، 3X-UI پایداری بهتر، پشتیبانی گسترده‌تر از پروتکل‌ها و ویژگی‌های اضافی را ارائه می‌دهد. ## شروع سریع -``` +### اسکریپت نصب با یک کلیک + +**منبع رسمی:** +```bash bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` +**شتاب‌دهی GitHub (برای کاربران چینی توصیه می‌شود):** + +روش اول (استفاده از آدرس شتاب پیش‌فرض): +```bash +bash <(curl -Ls https://gh.kejilion.pro/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +روش دوم (استفاده از آدرس شتاب سفارشی): +```bash +bash <(curl -Ls https://آدرس-آینه-سفارشی.com/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + برای مستندات کامل، لطفاً به [ویکی پروژه](https://github.com/MHSanaei/3x-ui/wiki) مراجعه کنید. -## تشکر ویژه از +## تقدیر ویژه - [alireza0](https://github.com/alireza0/) -## قدردانی +## تشکر و قدردانی -- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**): _قوانین مسیریابی بهبود یافته v2ray/xray و v2ray/xray-clients با دامنه‌های ایرانی داخلی و تمرکز بر امنیت و مسدود کردن تبلیغات._ -- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**): _این مخزن شامل قوانین مسیریابی V2Ray به‌روزرسانی شده خودکار بر اساس داده‌های دامنه‌ها و آدرس‌های مسدود شده در روسیه است._ +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**): _قوانین مسیریابی بهبود یافته v2ray/xray با دامنه‌های ایرانی._ +- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**): _قوانین V2Ray که بر اساس دامنه‌های مسدود شده در روسیه به‌روزرسانی می‌شوند._ -## پشتیبانی از پروژه +## حمایت از پروژه -**اگر این پروژه برای شما مفید است، می‌توانید به آن یک**:star2: بدهید - - -Buy Me A Coffee - - -
- - Crypto donation button by NOWPayments - - -## ستاره‌ها در طول زمان - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) +**اگر این پروژه برای شما مفید است، می‌توانید به آن**:star2: **بدهید** diff --git a/README.md b/README.md index 4009acdb..4cb68b24 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[中文](/README.zh_CN.md) | [English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)

@@ -7,27 +7,34 @@

-[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v3.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v3)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v3) - -**3X-UI** — advanced, open-source web-based control panel designed for managing Xray-core server. It offers a user-friendly interface for configuring and monitoring various VPN and proxy protocols. +**3X-UI** — An advanced, open-source web-based control panel designed for managing Xray-core servers. It offers a user-friendly interface for configuring and monitoring various VPN and proxy protocols. > [!IMPORTANT] -> This project is only for personal usage, please do not use it for illegal purposes, and please do not use it in a production environment. +> This project is for personal learning and communication research only. Please do not use it for illegal purposes or in production environments. -As an enhanced fork of the original X-UI project, 3X-UI provides improved stability, broader protocol support, and additional features. +As an enhanced version of the original X-UI project, 3X-UI provides better stability, broader protocol support, and additional features. ## Quick Start +### One-Click Installation Script + +**Official Source:** ```bash bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` +**GitHub Acceleration (Recommended for users in China):** + +Method 1 (Using default acceleration address): +```bash +bash <(curl -Ls https://gh.kejilion.pro/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +Method 2 (Using custom acceleration address): +```bash +bash <(curl -Ls https://your-custom-mirror.com/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki). ## A Special Thanks to @@ -39,19 +46,6 @@ For full documentation, please visit the [project Wiki](https://github.com/MHSan - [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._ -## Support project +## Support Project **If this project is helpful to you, you may wish to give it a**:star2: - - -Buy Me A Coffee - - -
- - Crypto donation button by NOWPayments - - -## Stargazers over Time - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) diff --git a/README.ru_RU.md b/README.ru_RU.md index 8ec606a7..c97b6bd3 100644 --- a/README.ru_RU.md +++ b/README.ru_RU.md @@ -1,4 +1,4 @@ -[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[中文](/README.zh_CN.md) | [English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)

@@ -7,51 +7,45 @@

-[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v3.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v3)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v3) - -**3X-UI** — продвинутая панель управления с открытым исходным кодом на основе веб-интерфейса, разработанная для управления сервером Xray-core. Предоставляет удобный интерфейс для настройки и мониторинга различных VPN и прокси-протоколов. +**3X-UI** — Панель управления веб-интерфейса Xray-core. Она предлагает удобный интерфейс для настройки и мониторинга различных VPN и прокси-протоколов. > [!IMPORTANT] -> Этот проект предназначен только для личного использования, пожалуйста, не используйте его в незаконных целях и в производственной среде. +> Этот проект предназначен только для личного использования и исследований связи. Пожалуйста, не используйте его в незаконных целях и не используйте в производственной среде. -Как улучшенная версия оригинального проекта X-UI, 3X-UI обеспечивает повышенную стабильность, более широкую поддержку протоколов и дополнительные функции. +В качестве улучшенной версии оригинального проекта X-UI, 3X-UI обеспечивает лучшую стабильность, более широкую поддержку протоколов и дополнительные функции. ## Быстрый старт -``` +### Скрипт быстрой установки + +**Официальный источник:** +```bash bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` -Полную документацию смотрите в [вики проекта](https://github.com/MHSanaei/3x-ui/wiki). +**Ускорение GitHub (рекомендуется для пользователей из Китая):** + +Способ 1 (Использование адреса ускорения по умолчанию): +```bash +bash <(curl -Ls https://gh.kejilion.pro/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +Способ 2 (Использование пользовательского адреса ускорения): +```bash +bash <(curl -Ls https://ваш-адрес-зеркала.com/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +Полную документацию смотрите в [Wiki проекта](https://github.com/MHSanaei/3x-ui/wiki). ## Особая благодарность - [alireza0](https://github.com/alireza0/) -## Благодарности +## Признание -- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Лицензия: **GPL-3.0**): _Улучшенные правила маршрутизации для v2ray/xray и v2ray/xray-clients со встроенными иранскими доменами и фокусом на безопасность и блокировку рекламы._ -- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Лицензия: **GPL-3.0**): _Этот репозиторий содержит автоматически обновляемые правила маршрутизации V2Ray на основе данных о заблокированных доменах и адресах в России._ +- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Лицензия: **GPL-3.0**): _Улучшенные правила маршрутизации v2ray/xray с иранскими доменами._ +- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Лицензия: **GPL-3.0**): _Автоматически обновляемые правила V2Ray на основе заблокированных в России доменов._ -## Поддержка проекта +## Поддержать проект -**Если этот проект полезен для вас, вы можете поставить ему**:star2: - - -Buy Me A Coffee - - -
- - Crypto donation button by NOWPayments - - -## Звезды с течением времени - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) +**Если этот проект полезен для вас, вы можете поставить**:star2: diff --git a/README.zh_CN.md b/README.zh_CN.md index 12c7f9cb..d5d27565 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -1,4 +1,4 @@ -[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) +[中文](/README.zh_CN.md) | [English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)

@@ -7,27 +7,34 @@

-[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) -[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions) -[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) -[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) -[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v3.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v3) -[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v3)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v3) - **3X-UI** — 一个基于网页的高级开源控制面板,专为管理 Xray-core 服务器而设计。它提供了用户友好的界面,用于配置和监控各种 VPN 和代理协议。 > [!IMPORTANT] -> 本项目仅用于个人使用和通信,请勿将其用于非法目的,请勿在生产环境中使用。 +> 本项目仅用于个人学习和通信研究,请勿将其用于非法目的,请勿在生产环境中使用。 作为原始 X-UI 项目的增强版本,3X-UI 提供了更好的稳定性、更广泛的协议支持和额外的功能。 ## 快速开始 -``` +### 一键安装脚本 + +**官方源安装:** +```bash bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` +**GitHub 加速安装(推荐国内用户):** + +方式一(使用默认加速地址): +```bash +bash <(curl -Ls https://gh.kejilion.pro/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + +方式二(使用自定义加速地址): +```bash +bash <(curl -Ls https://your-custom-mirror.com/https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) +``` + 完整文档请参阅 [项目Wiki](https://github.com/MHSanaei/3x-ui/wiki)。 ## 特别感谢 @@ -42,16 +49,3 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install. ## 支持项目 **如果这个项目对您有帮助,您可以给它一个**:star2: - - -Buy Me A Coffee - - -
- - Crypto donation button by NOWPayments - - -## 随时间变化的星标数 - -[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) diff --git a/config/name b/config/name index 491114af..7c8c2186 100644 --- a/config/name +++ b/config/name @@ -1 +1 @@ -x-ui \ No newline at end of file +3x-ui \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 52fddcce..1412bac4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "3x-ui-frontend", + "name": "3x-ui", "private": true, "version": "0.0.1", "type": "module", diff --git a/frontend/src/i18n/index.js b/frontend/src/i18n/index.js index 1e21d215..46be8484 100644 --- a/frontend/src/i18n/index.js +++ b/frontend/src/i18n/index.js @@ -29,6 +29,7 @@ import { LanguageManager } from '@/utils'; // "lazy" here effectively means "load only what this page needs for // its lifetime." const FALLBACK = 'en-US'; +const DEFAULT_LOCALE = 'zh-CN'; const lazyModules = import.meta.glob('../../../web/translation/*.json'); const eagerModules = import.meta.glob('../../../web/translation/*.json', { eager: true }); @@ -38,10 +39,10 @@ function moduleKeyFor(code) { // Resolve the active locale via LanguageManager so the cookie set on // the legacy panel keeps working after a user upgrades. Falls back -// to en-US when the cookie names a language we don't have. +// to zh-CN (default) when the cookie names a language we don't have. let active = LanguageManager.getLanguage(); if (!Object.prototype.hasOwnProperty.call(lazyModules, moduleKeyFor(active))) { - active = FALLBACK; + active = DEFAULT_LOCALE; } const messages = {}; diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 61027017..e76e8845 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -840,11 +840,11 @@ export class LanguageManager { if (LanguageManager.isSupportLanguage(lang)) { CookieManager.setCookie("lang", lang); } else { - CookieManager.setCookie("lang", "en-US"); + CookieManager.setCookie("lang", "zh-CN"); window.location.reload(); } } else { - CookieManager.setCookie("lang", "en-US"); + CookieManager.setCookie("lang", "zh-CN"); window.location.reload(); } } @@ -854,7 +854,7 @@ export class LanguageManager { static setLanguage(language) { if (!LanguageManager.isSupportLanguage(language)) { - language = "en-US"; + language = "zh-CN"; } CookieManager.setCookie("lang", language); diff --git a/install.sh b/install.sh index 1908c34c..952aa3cf 100644 --- a/install.sh +++ b/install.sh @@ -11,10 +11,12 @@ cur_dir=$(pwd) xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}" xui_service="${XUI_SERVICE:=/etc/systemd/system}" -# check root +GITHUB_MIRROR_DEFAULT="https://gh.kejilion.pro" +GITHUB_RAW_DEFAULT="https://raw.githubusercontent.com" + +check root [[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 -# Check OS and set release variable if [[ -f /etc/os-release ]]; then source /etc/os-release release=$ID @@ -42,7 +44,6 @@ arch() { echo "Arch: $(arch)" -# Simple helpers is_ipv4() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 } @@ -56,7 +57,6 @@ is_domain() { [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1 } -# Port helpers is_port_in_use() { local port="$1" if command -v ss > /dev/null 2>&1; then @@ -73,6 +73,24 @@ is_port_in_use() { return 1 } +get_github_mirror() { + local custom_mirror="${GITHUB_MIRROR:-}" + if [[ -n "$custom_mirror" ]]; then + echo "$custom_mirror" + else + echo "$GITHUB_MIRROR_DEFAULT" + fi +} + +get_github_raw() { + local mirror=$(get_github_mirror) + if [[ "$mirror" == "$GITHUB_MIRROR_DEFAULT" ]]; then + echo "$GITHUB_RAW_DEFAULT" + else + echo "$mirror" + fi +} + install_base() { case "${release}" in ubuntu | debian | armbian) @@ -131,7 +149,6 @@ setup_ssl_certificate() { echo -e "${green}Setting up SSL certificate...${plain}" - # Check if acme.sh is installed if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then install_acme if [ $? -ne 0 ]; then @@ -140,11 +157,9 @@ setup_ssl_certificate() { fi fi - # Create certificate directory local certPath="/root/cert/${domain}" mkdir -p "$certPath" - # Issue certificate echo -e "${green}Issuing SSL certificate for ${domain}...${plain}" echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}" @@ -159,7 +174,6 @@ setup_ssl_certificate() { return 1 fi - # Install certificate ~/.acme.sh/acme.sh --installcert -d ${domain} \ --key-file /root/cert/${domain}/privkey.pem \ --fullchain-file /root/cert/${domain}/fullchain.pem \ @@ -170,13 +184,10 @@ setup_ssl_certificate() { return 1 fi - # Enable auto-renew ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 - # Secure permissions: private key readable only by owner chmod 600 $certPath/privkey.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null - # Set certificate for panel local webCertFile="/root/cert/${domain}/fullchain.pem" local webKeyFile="/root/cert/${domain}/privkey.pem" @@ -190,17 +201,14 @@ setup_ssl_certificate() { fi } -# Issue Let's Encrypt IP certificate with shortlived profile (~6 days validity) -# Requires acme.sh and port 80 open for HTTP-01 challenge setup_ip_certificate() { local ipv4="$1" - local ipv6="$2" # optional + local ipv6="$2" echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}" echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}" echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}" - # Check for acme.sh if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then install_acme if [ $? -ne 0 ]; then @@ -209,7 +217,6 @@ setup_ip_certificate() { fi fi - # Validate IP address if [[ -z "$ipv4" ]]; then echo -e "${red}IPv4 address is required${plain}" return 1 @@ -220,21 +227,17 @@ setup_ip_certificate() { return 1 fi - # Create certificate directory local certDir="/root/cert/ip" mkdir -p "$certDir" - # Build domain arguments local domain_args="-d ${ipv4}" if [[ -n "$ipv6" ]] && is_ipv6 "$ipv6"; then domain_args="${domain_args} -d ${ipv6}" echo -e "${green}Including IPv6 address: ${ipv6}${plain}" fi - # Set reload command for auto-renewal (add || true so it doesn't fail during first install) local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true" - # Choose port for HTTP-01 listener (default 80, prompt override) local WebPort="" read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort WebPort="${WebPort:-80}" @@ -247,7 +250,6 @@ setup_ip_certificate() { echo -e "${yellow}Reminder: Let's Encrypt still connects on port 80; forward external port 80 to ${WebPort}.${plain}" fi - # Ensure chosen port is available while true; do if is_port_in_use "${WebPort}"; then echo -e "${yellow}Port ${WebPort} is in use.${plain}" @@ -271,7 +273,6 @@ setup_ip_certificate() { fi done - # Issue certificate with shortlived profile echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1 @@ -287,7 +288,6 @@ setup_ip_certificate() { if [ $? -ne 0 ]; then echo -e "${red}Failed to issue IP certificate${plain}" echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}" - # Cleanup acme.sh data for both IPv4 and IPv6 if specified rm -rf ~/.acme.sh/${ipv4} 2> /dev/null [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null rm -rf ${certDir} 2> /dev/null @@ -296,18 +296,13 @@ setup_ip_certificate() { echo -e "${green}Certificate issued successfully, installing...${plain}" - # Install certificate - # Note: acme.sh may report "Reload error" and exit non-zero if reloadcmd fails, - # but the cert files are still installed. We check for files instead of exit code. ~/.acme.sh/acme.sh --installcert -d ${ipv4} \ --key-file "${certDir}/privkey.pem" \ --fullchain-file "${certDir}/fullchain.pem" \ --reloadcmd "${reloadCmd}" 2>&1 || true - # Verify certificate files exist (don't rely on exit code - reloadcmd failure causes non-zero) if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then echo -e "${red}Certificate files not found after installation${plain}" - # Cleanup acme.sh data for both IPv4 and IPv6 if specified rm -rf ~/.acme.sh/${ipv4} 2> /dev/null [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null rm -rf ${certDir} 2> /dev/null @@ -316,14 +311,11 @@ setup_ip_certificate() { echo -e "${green}Certificate files installed successfully${plain}" - # Enable auto-upgrade for acme.sh (ensures cron job runs) ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 - # Secure permissions: private key readable only by owner chmod 600 ${certDir}/privkey.pem 2> /dev/null chmod 644 ${certDir}/fullchain.pem 2> /dev/null - # Configure panel to use the certificate echo -e "${green}Setting certificate paths for the panel...${plain}" ${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" @@ -342,12 +334,10 @@ setup_ip_certificate() { return 0 } -# Comprehensive manual SSL certificate issuance via acme.sh ssl_cert_issue() { local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##') local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]') - # check for acme.sh first if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then echo "acme.sh could not be found. Installing now..." cd ~ || return 1 @@ -360,11 +350,10 @@ ssl_cert_issue() { fi fi - # get the domain here, and we need to verify it local domain="" while true; do read -rp "Please enter your domain name: " domain - domain="${domain// /}" # Trim whitespace + domain="${domain// /}" if [[ -z "$domain" ]]; then echo -e "${red}Domain name cannot be empty. Please try again.${plain}" @@ -381,7 +370,6 @@ ssl_cert_issue() { echo -e "${green}Your domain is: ${domain}, checking it...${plain}" SSL_ISSUED_DOMAIN="${domain}" - # detect existing certificate and reuse it if present local cert_exists=0 if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then cert_exists=1 @@ -392,7 +380,6 @@ ssl_cert_issue() { echo -e "${green}Your domain is ready for issuing certificates now...${plain}" fi - # create a directory for the certificate certPath="/root/cert/${domain}" if [ ! -d "$certPath" ]; then mkdir -p "$certPath" @@ -401,7 +388,6 @@ ssl_cert_issue() { mkdir -p "$certPath" fi - # get the port number for the standalone server local WebPort=80 read -rp "Please choose which port to use (default is 80): " WebPort if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then @@ -410,12 +396,10 @@ ssl_cert_issue() { fi echo -e "${green}Will use port: ${WebPort} to issue certificates. Please make sure this port is open.${plain}" - # Stop panel temporarily echo -e "${yellow}Stopping panel temporarily...${plain}" systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null if [[ ${cert_exists} -eq 0 ]]; then - # issue the certificate ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force if [ $? -ne 0 ]; then @@ -430,7 +414,6 @@ ssl_cert_issue() { echo -e "${green}Using existing certificate, installing certificates...${plain}" fi - # Setup reload command reloadCmd="systemctl restart x-ui || rc-service x-ui restart" echo -e "${green}Default --reloadcmd for ACME is: ${yellow}systemctl restart x-ui || rc-service x-ui restart${plain}" echo -e "${green}This command will run on every certificate issue and renew.${plain}" @@ -456,7 +439,6 @@ ssl_cert_issue() { esac fi - # install the certificate local installOutput="" installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \ --key-file /root/cert/${domain}/privkey.pem \ @@ -480,26 +462,21 @@ ssl_cert_issue() { return 1 fi - # enable auto-renew ~/.acme.sh/acme.sh --upgrade --auto-upgrade if [ $? -ne 0 ]; then echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}" ls -lah /root/cert/${domain}/ - # Secure permissions: private key readable only by owner chmod 600 $certPath/privkey.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null else echo -e "${green}Auto renew succeeded, certificate details:${plain}" ls -lah /root/cert/${domain}/ - # Secure permissions: private key readable only by owner chmod 600 $certPath/privkey.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null fi - # start panel systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null - # Prompt user to set panel paths after successful certificate installation read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then local webCertFile="/root/cert/${domain}/fullchain.pem" @@ -524,8 +501,6 @@ ssl_cert_issue() { return 0 } -# Reusable interactive SSL setup (domain or IP) -# Sets global `SSL_HOST` to the chosen domain/IP for Access URL usage prompt_and_setup_ssl() { local panel_port="$1" local web_base_path="$2" @@ -542,16 +517,14 @@ prompt_and_setup_ssl() { echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths." echo -e "${blue}Note:${plain} Option 4 serves the panel over plain HTTP — only safe behind nginx/Caddy or an SSH tunnel." read -rp "Choose an option (default 2 for IP): " ssl_choice - ssl_choice="${ssl_choice// /}" # Trim whitespace + ssl_choice="${ssl_choice// /}" - # Default to 2 (IP cert) if input is empty or invalid (not 1, 3 or 4) if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" && "$ssl_choice" != "4" ]]; then ssl_choice="2" fi case "$ssl_choice" in 1) - # User chose Let's Encrypt domain option echo -e "${green}Using Let's Encrypt for domain certificate...${plain}" if ssl_cert_issue; then local cert_domain="${SSL_ISSUED_DOMAIN}" @@ -572,15 +545,12 @@ prompt_and_setup_ssl() { fi ;; 2) - # User chose Let's Encrypt IP certificate option echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}" - # Ask for optional IPv6 local ipv6_addr="" read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr - ipv6_addr="${ipv6_addr// /}" # Trim whitespace + ipv6_addr="${ipv6_addr// /}" - # Stop panel if running (port 80 needed) if [[ $release == "alpine" ]]; then rc-service x-ui stop > /dev/null 2>&1 else @@ -597,20 +567,16 @@ prompt_and_setup_ssl() { fi ;; 3) - # User chose Custom Paths (User Provided) option echo -e "${green}Using custom existing certificate...${plain}" local custom_cert="" local custom_key="" local custom_domain="" - # 3.1 Request Domain to compose Panel URL later read -rp "Please enter domain name certificate issued for: " custom_domain - custom_domain="${custom_domain// /}" # Remove spaces + custom_domain="${custom_domain// /}" - # 3.2 Loop for Certificate Path while true; do read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert - # Strip quotes if present custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'") if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then @@ -624,10 +590,8 @@ prompt_and_setup_ssl() { fi done - # 3.3 Loop for Private Key Path while true; do read -rp "Input private key path (keywords: .key / privatekey): " custom_key - # Strip quotes if present custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'") if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then @@ -641,10 +605,8 @@ prompt_and_setup_ssl() { fi done - # 3.4 Apply Settings via x-ui binary ${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" > /dev/null 2>&1 - # Set SSL_HOST for composing Panel URL if [[ -n "$custom_domain" ]]; then SSL_HOST="$custom_domain" else @@ -702,7 +664,6 @@ config_after_install() { local existing_hasDefaultCredential=$(${xui_folder}/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}') local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##') local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') - # Properly detect empty cert by checking if cert: line exists and has content after it local existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]') local URL_lists=( "https://api4.ipify.org" @@ -763,7 +724,6 @@ config_after_install() { prompt_and_setup_ssl "${config_port}" "${config_webBasePath}" "${server_ip}" - # Display final credentials and access information echo "" echo -e "${green}═══════════════════════════════════════════${plain}" echo -e "${green} Panel Installation Complete! ${plain}" @@ -786,7 +746,6 @@ config_after_install() { ${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}" echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}" - # If the panel is already installed but no certificate is configured, prompt for SSL now if [[ -z "${existing_cert}" ]]; then echo "" echo -e "${green}═══════════════════════════════════════════${plain}" @@ -797,7 +756,6 @@ config_after_install() { prompt_and_setup_ssl "${existing_port}" "${config_webBasePath}" "${server_ip}" echo -e "${green}Access URL: ${SSL_SCHEME}://${SSL_HOST}:${existing_port}/${config_webBasePath}${plain}" else - # If a cert already exists, just show the access URL echo -e "${green}Access URL: https://${server_ip}:${existing_port}/${config_webBasePath}${plain}" fi fi @@ -817,8 +775,6 @@ config_after_install() { echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}" fi - # Existing install: if no cert configured, prompt user for SSL setup - # Properly detect empty cert by checking if cert: line exists and has content after it existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]') if [[ -z "$existing_cert" ]]; then echo "" @@ -840,19 +796,20 @@ config_after_install() { install_x-ui() { cd ${xui_folder%/x-ui}/ - # Download resources + local github_raw=$(get_github_raw) + if [ $# == 0 ]; then - tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + tag_version=$(curl -Ls "https://api.github.com/repos/mhsanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [[ ! -n "$tag_version" ]]; then echo -e "${yellow}Trying to fetch version with IPv4...${plain}" - tag_version=$(curl -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + tag_version=$(curl -4 -Ls "https://api.github.com/repos/mhsanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [[ ! -n "$tag_version" ]]; then echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}" exit 1 fi fi echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." - curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz + curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/mhsanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz if [[ $? -ne 0 ]]; then echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" exit 1 @@ -867,7 +824,7 @@ install_x-ui() { exit 1 fi - url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" + url="https://github.com/mhsanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" echo -e "Beginning to install x-ui $1" curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz ${url} if [[ $? -ne 0 ]]; then @@ -875,13 +832,12 @@ install_x-ui() { exit 1 fi fi - curl -4fLRo /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh + curl -4fLRo /usr/bin/x-ui-temp ${github_raw}/mhsanaei/3x-ui/main/x-ui.sh if [[ $? -ne 0 ]]; then echo -e "${red}Failed to download x-ui.sh${plain}" exit 1 fi - # Stop x-ui service and remove old resources if [[ -e ${xui_folder}/ ]]; then if [[ $release == "alpine" ]]; then rc-service x-ui stop @@ -891,7 +847,6 @@ install_x-ui() { rm ${xui_folder}/ -rf fi - # Extract resources and set permissions tar zxvf x-ui-linux-$(arch).tar.gz rm x-ui-linux-$(arch).tar.gz -f @@ -899,20 +854,17 @@ install_x-ui() { chmod +x x-ui chmod +x x-ui.sh - # Check the system's architecture and rename the file accordingly 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 x-ui bin/xray-linux-$(arch) - # Update x-ui cli and se set permission mv -f /usr/bin/x-ui-temp /usr/bin/x-ui chmod +x /usr/bin/x-ui mkdir -p /var/log/x-ui config_after_install - # Etckeeper compatibility if [ -d "/etc/.git" ]; then if [ -f "/etc/.gitignore" ]; then if ! grep -q "x-ui/x-ui.db" "/etc/.gitignore"; then @@ -927,7 +879,7 @@ install_x-ui() { fi if [[ $release == "alpine" ]]; then - curl -4fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc + curl -4fLRo /etc/init.d/x-ui ${github_raw}/mhsanaei/3x-ui/main/x-ui.rc if [[ $? -ne 0 ]]; then echo -e "${red}Failed to download x-ui.rc${plain}" exit 1 @@ -936,7 +888,6 @@ install_x-ui() { rc-update add x-ui rc-service x-ui start else - # Install systemd service file service_installed=false if [ -f "x-ui.service" ]; then @@ -979,18 +930,17 @@ install_x-ui() { esac fi - # If service file not found in tar.gz, download from GitHub if [ "$service_installed" = false ]; then echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}" case "${release}" in ubuntu | debian | armbian) - curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian > /dev/null 2>&1 + curl -4fLRo ${xui_service}/x-ui.service ${github_raw}/mhsanaei/3x-ui/main/x-ui.service.debian > /dev/null 2>&1 ;; arch | manjaro | parch) - curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch > /dev/null 2>&1 + curl -4fLRo ${xui_service}/x-ui.service ${github_raw}/mhsanaei/3x-ui/main/x-ui.service.arch > /dev/null 2>&1 ;; *) - curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel > /dev/null 2>&1 + curl -4fLRo ${xui_service}/x-ui.service ${github_raw}/mhsanaei/3x-ui/main/x-ui.service.rhel > /dev/null 2>&1 ;; esac diff --git a/update.sh b/update.sh index 1c9f1ae9..ac2c3ef3 100755 --- a/update.sh +++ b/update.sh @@ -6,41 +6,16 @@ blue='\033[0;34m' yellow='\033[0;33m' plain='\033[0m' +cur_dir=$(pwd) + xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}" xui_service="${XUI_SERVICE:=/etc/systemd/system}" -# Don't edit this config -b_source="${BASH_SOURCE[0]}" -while [ -h "$b_source" ]; do - b_dir="$(cd -P "$(dirname "$b_source")" > /dev/null 2>&1 && pwd || pwd -P)" - b_source="$(readlink "$b_source")" - [[ $b_source != /* ]] && b_source="$b_dir/$b_source" -done -cur_dir="$(cd -P "$(dirname "$b_source")" > /dev/null 2>&1 && pwd || pwd -P)" -script_name=$(basename "$0") +GITHUB_MIRROR_DEFAULT="https://gh.kejilion.pro" +GITHUB_RAW_DEFAULT="https://raw.githubusercontent.com" -# Check command exist function -_command_exists() { - type "$1" &> /dev/null -} +[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 -# Fail, log and exit script function -_fail() { - local msg=${1} - echo -e "${red}${msg}${plain}" - exit 2 -} - -# check root -[[ $EUID -ne 0 ]] && _fail "FATAL ERROR: Please run this script with root privilege." - -if _command_exists curl; then - curl_bin=$(which curl) -else - _fail "ERROR: Command 'curl' not found." -fi - -# Check OS and set release variable if [[ -f /etc/os-release ]]; then source /etc/os-release release=$ID @@ -48,7 +23,8 @@ elif [[ -f /usr/lib/os-release ]]; then source /usr/lib/os-release release=$ID else - _fail "Failed to check the system OS, please contact the author!" + echo "Failed to check the system OS, please contact the author!" >&2 + exit 1 fi echo "The OS release is: $release" @@ -61,909 +37,106 @@ arch() { armv6* | armv6) echo 'armv6' ;; armv5* | armv5) echo 'armv5' ;; s390x) echo 's390x' ;; - *) echo -e "${red}Unsupported CPU architecture!${plain}" && rm -f "${cur_dir}/${script_name}" > /dev/null 2>&1 && exit 2 ;; + *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f update.sh && exit 1 ;; esac } echo "Arch: $(arch)" -# Simple helpers -is_ipv4() { - [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 -} -is_ipv6() { - [[ "$1" =~ : ]] && return 0 || return 1 -} -is_ip() { - is_ipv4 "$1" || is_ipv6 "$1" -} -is_domain() { - [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1 -} - -# Port helpers -is_port_in_use() { - local port="$1" - if command -v ss > /dev/null 2>&1; then - ss -ltn 2> /dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}' - return - fi - if command -v netstat > /dev/null 2>&1; then - netstat -lnt 2> /dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}' - return - fi - if command -v lsof > /dev/null 2>&1; then - lsof -nP -iTCP:${port} -sTCP:LISTEN > /dev/null 2>&1 && return 0 - fi - return 1 -} - -gen_random_string() { - local length="$1" - openssl rand -base64 $((length * 2)) \ - | tr -dc 'a-zA-Z0-9' \ - | head -c "$length" -} - -install_base() { - echo -e "${green}Updating and install dependency packages...${plain}" - case "${release}" in - ubuntu | debian | armbian) - apt-get update > /dev/null 2>&1 && apt-get install -y -q cron curl tar tzdata socat openssl > /dev/null 2>&1 - ;; - fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) - dnf -y update > /dev/null 2>&1 && dnf install -y -q cronie curl tar tzdata socat openssl > /dev/null 2>&1 - ;; - centos) - if [[ "${VERSION_ID}" =~ ^7 ]]; then - yum -y update > /dev/null 2>&1 && yum install -y -q cronie curl tar tzdata socat openssl > /dev/null 2>&1 - else - dnf -y update > /dev/null 2>&1 && dnf install -y -q cronie curl tar tzdata socat openssl > /dev/null 2>&1 - fi - ;; - arch | manjaro | parch) - pacman -Syu > /dev/null 2>&1 && pacman -Syu --noconfirm cronie curl tar tzdata socat openssl > /dev/null 2>&1 - ;; - opensuse-tumbleweed | opensuse-leap) - zypper refresh > /dev/null 2>&1 && zypper -q install -y cron curl tar timezone socat openssl > /dev/null 2>&1 - ;; - alpine) - apk update > /dev/null 2>&1 && apk add dcron curl tar tzdata socat openssl > /dev/null 2>&1 - ;; - *) - apt-get update > /dev/null 2>&1 && apt install -y -q cron curl tar tzdata socat openssl > /dev/null 2>&1 - ;; - esac -} - -install_acme() { - echo -e "${green}Installing acme.sh for SSL certificate management...${plain}" - cd ~ || return 1 - curl -s https://get.acme.sh | sh > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${red}Failed to install acme.sh${plain}" - return 1 +get_github_mirror() { + local custom_mirror="${GITHUB_MIRROR:-}" + if [[ -n "$custom_mirror" ]]; then + echo "$custom_mirror" else - echo -e "${green}acme.sh installed successfully${plain}" - fi - return 0 -} - -setup_ssl_certificate() { - local domain="$1" - local server_ip="$2" - local existing_port="$3" - local existing_webBasePath="$4" - - echo -e "${green}Setting up SSL certificate...${plain}" - - # Check if acme.sh is installed - if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then - install_acme - if [ $? -ne 0 ]; then - echo -e "${yellow}Failed to install acme.sh, skipping SSL setup${plain}" - return 1 - fi - fi - - # Create certificate directory - local certPath="/root/cert/${domain}" - mkdir -p "$certPath" - - # Issue certificate - echo -e "${green}Issuing SSL certificate for ${domain}...${plain}" - echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}" - - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1 - ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force - - if [ $? -ne 0 ]; then - echo -e "${yellow}Failed to issue certificate for ${domain}${plain}" - echo -e "${yellow}Please ensure port 80 is open and try again later with: x-ui${plain}" - rm -rf ~/.acme.sh/${domain} 2> /dev/null - rm -rf "$certPath" 2> /dev/null - return 1 - fi - - # Install certificate - ~/.acme.sh/acme.sh --installcert -d ${domain} \ - --key-file /root/cert/${domain}/privkey.pem \ - --fullchain-file /root/cert/${domain}/fullchain.pem \ - --reloadcmd "systemctl restart x-ui" > /dev/null 2>&1 - - if [ $? -ne 0 ]; then - echo -e "${yellow}Failed to install certificate${plain}" - return 1 - fi - - # Enable auto-renew - ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 - chmod 600 $certPath/privkey.pem 2> /dev/null - chmod 644 $certPath/fullchain.pem 2> /dev/null - - # Set certificate for panel - local webCertFile="/root/cert/${domain}/fullchain.pem" - local webKeyFile="/root/cert/${domain}/privkey.pem" - - if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then - ${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" > /dev/null 2>&1 - echo -e "${green}SSL certificate installed and configured successfully!${plain}" - return 0 - else - echo -e "${yellow}Certificate files not found${plain}" - return 1 + echo "$GITHUB_MIRROR_DEFAULT" fi } -# Issue Let's Encrypt IP certificate with shortlived profile (~6 days validity) -# Requires acme.sh and port 80 open for HTTP-01 challenge -setup_ip_certificate() { - local ipv4="$1" - local ipv6="$2" # optional - - echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}" - echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}" - echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}" - - # Check for acme.sh - if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then - install_acme - if [ $? -ne 0 ]; then - echo -e "${red}Failed to install acme.sh${plain}" - return 1 - fi - fi - - # Validate IP address - if [[ -z "$ipv4" ]]; then - echo -e "${red}IPv4 address is required${plain}" - return 1 - fi - - if ! is_ipv4 "$ipv4"; then - echo -e "${red}Invalid IPv4 address: $ipv4${plain}" - return 1 - fi - - # Create certificate directory - local certDir="/root/cert/ip" - mkdir -p "$certDir" - - # Build domain arguments - local domain_args="-d ${ipv4}" - if [[ -n "$ipv6" ]] && is_ipv6 "$ipv6"; then - domain_args="${domain_args} -d ${ipv6}" - echo -e "${green}Including IPv6 address: ${ipv6}${plain}" - fi - - # Set reload command for auto-renewal (add || true so it doesn't fail if service stopped) - local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true" - - # Choose port for HTTP-01 listener (default 80, prompt override) - local WebPort="" - read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort - WebPort="${WebPort:-80}" - if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then - echo -e "${red}Invalid port provided. Falling back to 80.${plain}" - WebPort=80 - fi - echo -e "${green}Using port ${WebPort} for standalone validation.${plain}" - if [[ "${WebPort}" -ne 80 ]]; then - echo -e "${yellow}Reminder: Let's Encrypt still connects on port 80; forward external port 80 to ${WebPort}.${plain}" - fi - - # Ensure chosen port is available - while true; do - if is_port_in_use "${WebPort}"; then - echo -e "${yellow}Port ${WebPort} is currently in use.${plain}" - - local alt_port="" - read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port - alt_port="${alt_port// /}" - if [[ -z "${alt_port}" ]]; then - echo -e "${red}Port ${WebPort} is busy; cannot proceed.${plain}" - return 1 - fi - if ! [[ "${alt_port}" =~ ^[0-9]+$ ]] || ((alt_port < 1 || alt_port > 65535)); then - echo -e "${red}Invalid port provided.${plain}" - return 1 - fi - WebPort="${alt_port}" - continue - else - echo -e "${green}Port ${WebPort} is free and ready for standalone validation.${plain}" - break - fi - done - - # Issue certificate with shortlived profile - echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1 - - ~/.acme.sh/acme.sh --issue \ - ${domain_args} \ - --standalone \ - --server letsencrypt \ - --certificate-profile shortlived \ - --days 6 \ - --httpport ${WebPort} \ - --force - - if [ $? -ne 0 ]; then - echo -e "${red}Failed to issue IP certificate${plain}" - echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}" - # Cleanup acme.sh data for both IPv4 and IPv6 if specified - rm -rf ~/.acme.sh/${ipv4} 2> /dev/null - [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null - rm -rf ${certDir} 2> /dev/null - return 1 - fi - - echo -e "${green}Certificate issued successfully, installing...${plain}" - - # Install certificate - # Note: acme.sh may report "Reload error" and exit non-zero if reloadcmd fails, - # but the cert files are still installed. We check for files instead of exit code. - ~/.acme.sh/acme.sh --installcert -d ${ipv4} \ - --key-file "${certDir}/privkey.pem" \ - --fullchain-file "${certDir}/fullchain.pem" \ - --reloadcmd "${reloadCmd}" 2>&1 || true - - # Verify certificate files exist (don't rely on exit code - reloadcmd failure causes non-zero) - if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then - echo -e "${red}Certificate files not found after installation${plain}" - # Cleanup acme.sh data for both IPv4 and IPv6 if specified - rm -rf ~/.acme.sh/${ipv4} 2> /dev/null - [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null - rm -rf ${certDir} 2> /dev/null - return 1 - fi - - echo -e "${green}Certificate files installed successfully${plain}" - - # Enable auto-upgrade for acme.sh (ensures cron job runs) - ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 - - chmod 600 ${certDir}/privkey.pem 2> /dev/null - chmod 644 ${certDir}/fullchain.pem 2> /dev/null - - # Configure panel to use the certificate - echo -e "${green}Setting certificate paths for the panel...${plain}" - ${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" - if [ $? -ne 0 ]; then - echo -e "${yellow}Warning: Could not set certificate paths automatically.${plain}" - echo -e "${yellow}You may need to set them manually in the panel settings.${plain}" - echo -e "${yellow}Cert path: ${certDir}/fullchain.pem${plain}" - echo -e "${yellow}Key path: ${certDir}/privkey.pem${plain}" +get_github_raw() { + local mirror=$(get_github_mirror) + if [[ "$mirror" == "$GITHUB_MIRROR_DEFAULT" ]]; then + echo "$GITHUB_RAW_DEFAULT" else - echo -e "${green}Certificate paths set successfully!${plain}" - fi - - echo -e "${green}IP certificate installed and configured successfully!${plain}" - echo -e "${green}Certificate valid for ~6 days, auto-renews via acme.sh cron job.${plain}" - echo -e "${yellow}Panel will automatically restart after each renewal.${plain}" - return 0 -} - -# Comprehensive manual SSL certificate issuance via acme.sh -ssl_cert_issue() { - local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##') - local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]') - - # check for acme.sh first - if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then - echo "acme.sh could not be found. Installing now..." - cd ~ || return 1 - curl -s https://get.acme.sh | sh - if [ $? -ne 0 ]; then - echo -e "${red}Failed to install acme.sh${plain}" - return 1 - else - echo -e "${green}acme.sh installed successfully${plain}" - fi - fi - - # get the domain here, and we need to verify it - local domain="" - while true; do - read -rp "Please enter your domain name: " domain - domain="${domain// /}" # Trim whitespace - - if [[ -z "$domain" ]]; then - echo -e "${red}Domain name cannot be empty. Please try again.${plain}" - continue - fi - - if ! is_domain "$domain"; then - echo -e "${red}Invalid domain format: ${domain}. Please enter a valid domain name.${plain}" - continue - fi - - break - done - echo -e "${green}Your domain is: ${domain}, checking it...${plain}" - SSL_ISSUED_DOMAIN="${domain}" - - # detect existing certificate and reuse it if present - local cert_exists=0 - if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then - cert_exists=1 - local certInfo=$(~/.acme.sh/acme.sh --list 2> /dev/null | grep -F "${domain}") - echo -e "${yellow}Existing certificate found for ${domain}, will reuse it.${plain}" - [[ -n "${certInfo}" ]] && echo "$certInfo" - else - echo -e "${green}Your domain is ready for issuing certificates now...${plain}" - fi - - # create a directory for the certificate - certPath="/root/cert/${domain}" - if [ ! -d "$certPath" ]; then - mkdir -p "$certPath" - else - rm -rf "$certPath" - mkdir -p "$certPath" - fi - - # get the port number for the standalone server - local WebPort=80 - read -rp "Please choose which port to use (default is 80): " WebPort - if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then - echo -e "${yellow}Your input ${WebPort} is invalid, will use default port 80.${plain}" - WebPort=80 - fi - echo -e "${green}Will use port: ${WebPort} to issue certificates. Please make sure this port is open.${plain}" - - # Stop panel temporarily - echo -e "${yellow}Stopping panel temporarily...${plain}" - systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null - - if [[ ${cert_exists} -eq 0 ]]; then - # issue the certificate - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force - ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force - if [ $? -ne 0 ]; then - echo -e "${red}Issuing certificate failed, please check logs.${plain}" - rm -rf ~/.acme.sh/${domain} - systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null - return 1 - else - echo -e "${green}Issuing certificate succeeded, installing certificates...${plain}" - fi - else - echo -e "${green}Using existing certificate, installing certificates...${plain}" - fi - - # Setup reload command - reloadCmd="systemctl restart x-ui || rc-service x-ui restart" - echo -e "${green}Default --reloadcmd for ACME is: ${yellow}systemctl restart x-ui || rc-service x-ui restart${plain}" - echo -e "${green}This command will run on every certificate issue and renew.${plain}" - read -rp "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd - if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then - echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; systemctl restart x-ui" - echo -e "${green}\t2.${plain} Input your own command" - echo -e "${green}\t0.${plain} Keep default reloadcmd" - read -rp "Choose an option: " choice - case "$choice" in - 1) - echo -e "${green}Reloadcmd is: systemctl reload nginx ; systemctl restart x-ui${plain}" - reloadCmd="systemctl reload nginx ; systemctl restart x-ui" - ;; - 2) - echo -e "${yellow}It's recommended to put x-ui restart at the end${plain}" - read -rp "Please enter your custom reloadcmd: " reloadCmd - echo -e "${green}Reloadcmd is: ${reloadCmd}${plain}" - ;; - *) - echo -e "${green}Keeping default reloadcmd${plain}" - ;; - esac - fi - - # install the certificate - local installOutput="" - installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \ - --key-file /root/cert/${domain}/privkey.pem \ - --fullchain-file /root/cert/${domain}/fullchain.pem --reloadcmd "${reloadCmd}" 2>&1) - local installRc=$? - echo "${installOutput}" - - local installWroteFiles=0 - if echo "${installOutput}" | grep -q "Installing key to:" && echo "${installOutput}" | grep -q "Installing full chain to:"; then - installWroteFiles=1 - fi - - if [[ -f "/root/cert/${domain}/privkey.pem" && -f "/root/cert/${domain}/fullchain.pem" && (${installRc} -eq 0 || ${installWroteFiles} -eq 1) ]]; then - echo -e "${green}Installing certificate succeeded, enabling auto renew...${plain}" - else - echo -e "${red}Installing certificate failed, exiting.${plain}" - if [[ ${cert_exists} -eq 0 ]]; then - rm -rf ~/.acme.sh/${domain} - fi - systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null - return 1 - fi - - # enable auto-renew - ~/.acme.sh/acme.sh --upgrade --auto-upgrade - if [ $? -ne 0 ]; then - echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}" - ls -lah /root/cert/${domain}/ - chmod 600 $certPath/privkey.pem - chmod 644 $certPath/fullchain.pem - else - echo -e "${green}Auto renew succeeded, certificate details:${plain}" - ls -lah /root/cert/${domain}/ - chmod 600 $certPath/privkey.pem - chmod 644 $certPath/fullchain.pem - fi - - # Restart panel - systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null - - # Prompt user to set panel paths after successful certificate installation - read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel - if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then - local webCertFile="/root/cert/${domain}/fullchain.pem" - local webKeyFile="/root/cert/${domain}/privkey.pem" - - if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then - ${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" - echo -e "${green}Certificate paths set for the panel${plain}" - echo -e "${green}Certificate File: $webCertFile${plain}" - echo -e "${green}Private Key File: $webKeyFile${plain}" - echo "" - echo -e "${green}Access URL: https://${domain}:${existing_port}/${existing_webBasePath}${plain}" - echo -e "${yellow}Panel will restart to apply SSL certificate...${plain}" - systemctl restart x-ui 2> /dev/null || rc-service x-ui restart 2> /dev/null - else - echo -e "${red}Error: Certificate or private key file not found for domain: $domain.${plain}" - fi - else - echo -e "${yellow}Skipping panel path setting.${plain}" - fi - - return 0 -} -# Unified interactive SSL setup (domain or IP) -# Sets global `SSL_HOST` to the chosen domain/IP -prompt_and_setup_ssl() { - local panel_port="$1" - local web_base_path="$2" # expected without leading slash - local server_ip="$3" - - local ssl_choice="" - - echo -e "${yellow}Choose SSL certificate setup method:${plain}" - echo -e "${green}1.${plain} Let's Encrypt for Domain (90-day validity, auto-renews)" - echo -e "${green}2.${plain} Let's Encrypt for IP Address (6-day validity, auto-renews)" - echo -e "${green}3.${plain} Custom SSL Certificate (Path to existing files)" - echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths." - read -rp "Choose an option (default 2 for IP): " ssl_choice - ssl_choice="${ssl_choice// /}" # Trim whitespace - - # Default to 2 (IP cert) if input is empty or invalid (not 1 or 3) - if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" ]]; then - ssl_choice="2" - fi - - case "$ssl_choice" in - 1) - # User chose Let's Encrypt domain option - echo -e "${green}Using Let's Encrypt for domain certificate...${plain}" - if ssl_cert_issue; then - local cert_domain="${SSL_ISSUED_DOMAIN}" - if [[ -z "${cert_domain}" ]]; then - cert_domain=$(~/.acme.sh/acme.sh --list 2> /dev/null | tail -1 | awk '{print $1}') - fi - - if [[ -n "${cert_domain}" ]]; then - SSL_HOST="${cert_domain}" - echo -e "${green}✓ SSL certificate configured successfully with domain: ${cert_domain}${plain}" - else - echo -e "${yellow}SSL setup may have completed, but domain extraction failed${plain}" - SSL_HOST="${server_ip}" - fi - else - echo -e "${red}SSL certificate setup failed for domain mode.${plain}" - SSL_HOST="${server_ip}" - fi - ;; - 2) - # User chose Let's Encrypt IP certificate option - echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}" - - # Ask for optional IPv6 - local ipv6_addr="" - read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr - ipv6_addr="${ipv6_addr// /}" # Trim whitespace - - # Stop panel if running (port 80 needed) - if [[ $release == "alpine" ]]; then - rc-service x-ui stop > /dev/null 2>&1 - else - systemctl stop x-ui > /dev/null 2>&1 - fi - - setup_ip_certificate "${server_ip}" "${ipv6_addr}" - if [ $? -eq 0 ]; then - SSL_HOST="${server_ip}" - echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}" - else - echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}" - SSL_HOST="${server_ip}" - fi - - # Restart panel after SSL is configured (restart applies new cert settings) - if [[ $release == "alpine" ]]; then - rc-service x-ui restart > /dev/null 2>&1 - else - systemctl restart x-ui > /dev/null 2>&1 - fi - - ;; - 3) - # User chose Custom Paths (User Provided) option - echo -e "${green}Using custom existing certificate...${plain}" - local custom_cert="" - local custom_key="" - local custom_domain="" - - # 3.1 Request Domain to compose Panel URL later - read -rp "Please enter domain name certificate issued for: " custom_domain - custom_domain="${custom_domain// /}" # Remove spaces - - # 3.2 Loop for Certificate Path - while true; do - read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert - # Strip quotes if present - custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'") - - if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then - break - elif [[ ! -f "$custom_cert" ]]; then - echo -e "${red}Error: File does not exist! Try again.${plain}" - elif [[ ! -r "$custom_cert" ]]; then - echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}" - else - echo -e "${red}Error: File is empty!${plain}" - fi - done - - # 3.3 Loop for Private Key Path - while true; do - read -rp "Input private key path (keywords: .key / privatekey): " custom_key - # Strip quotes if present - custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'") - - if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then - break - elif [[ ! -f "$custom_key" ]]; then - echo -e "${red}Error: File does not exist! Try again.${plain}" - elif [[ ! -r "$custom_key" ]]; then - echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}" - else - echo -e "${red}Error: File is empty!${plain}" - fi - done - - # 3.4 Apply Settings via x-ui binary - ${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" > /dev/null 2>&1 - - # Set SSL_HOST for composing Panel URL - if [[ -n "$custom_domain" ]]; then - SSL_HOST="$custom_domain" - else - SSL_HOST="${server_ip}" - fi - - echo -e "${green}✓ Custom certificate paths applied.${plain}" - echo -e "${yellow}Note: You are responsible for renewing these files externally.${plain}" - - systemctl restart x-ui > /dev/null 2>&1 || rc-service x-ui restart > /dev/null 2>&1 - ;; - *) - echo -e "${red}Invalid option. Skipping SSL setup.${plain}" - SSL_HOST="${server_ip}" - ;; - esac -} - -config_after_update() { - echo -e "${yellow}x-ui settings:${plain}" - ${xui_folder}/x-ui setting -show true - ${xui_folder}/x-ui migrate - - # Properly detect empty cert by checking if cert: line exists and has content after it - local existing_cert=$(${xui_folder}/x-ui setting -getCert true 2> /dev/null | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]') - local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') - local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##') - - # Get server IP - local URL_lists=( - "https://api4.ipify.org" - "https://ipv4.icanhazip.com" - "https://v4.api.ipinfo.io/ip" - "https://ipv4.myexternalip.com/raw" - "https://4.ident.me" - "https://check-host.net/ip" - ) - local server_ip="" - for ip_address in "${URL_lists[@]}"; do - local response=$(curl -s -w "\n%{http_code}" --max-time 3 "${ip_address}" 2> /dev/null) - local http_code=$(echo "$response" | tail -n1) - local ip_result=$(echo "$response" | head -n-1 | tr -d '[:space:]"') - if [[ "${http_code}" == "200" && "${ip_result}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - server_ip="${ip_result}" - break - fi - done - - if [[ -z "$server_ip" ]]; then - echo -e "${yellow}Could not auto-detect server IP from any provider.${plain}" - while [[ -z "$server_ip" ]]; do - read -rp "Please enter your server's public IPv4 address: " server_ip - server_ip="${server_ip// /}" - if [[ ! "$server_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo -e "${red}Invalid IPv4 address. Please try again.${plain}" - server_ip="" - fi - done - fi - - # Handle missing/short webBasePath - if [[ ${#existing_webBasePath} -lt 4 ]]; then - echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}" - local config_webBasePath=$(gen_random_string 18) - ${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}" - existing_webBasePath="${config_webBasePath}" - echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}" - fi - - # Check and prompt for SSL if missing - if [[ -z "$existing_cert" ]]; then - echo "" - echo -e "${red}═══════════════════════════════════════════${plain}" - echo -e "${red} ⚠ NO SSL CERTIFICATE DETECTED ⚠ ${plain}" - echo -e "${red}═══════════════════════════════════════════${plain}" - echo -e "${yellow}For security, SSL certificate is MANDATORY for all panels.${plain}" - echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}" - echo "" - - # Prompt and setup SSL (domain or IP) - prompt_and_setup_ssl "${existing_port}" "${existing_webBasePath}" "${server_ip}" - - echo "" - echo -e "${green}═══════════════════════════════════════════${plain}" - echo -e "${green} Panel Access Information ${plain}" - echo -e "${green}═══════════════════════════════════════════${plain}" - echo -e "${green}Access URL: https://${SSL_HOST}:${existing_port}/${existing_webBasePath}${plain}" - echo -e "${green}═══════════════════════════════════════════${plain}" - echo -e "${yellow}⚠ SSL Certificate: Enabled and configured${plain}" - else - echo -e "${green}SSL certificate is already configured${plain}" - # Show access URL with existing certificate - local cert_domain=$(basename "$(dirname "$existing_cert")") - echo "" - echo -e "${green}═══════════════════════════════════════════${plain}" - echo -e "${green} Panel Access Information ${plain}" - echo -e "${green}═══════════════════════════════════════════${plain}" - echo -e "${green}Access URL: https://${cert_domain}:${existing_port}/${existing_webBasePath}${plain}" - echo -e "${green}═══════════════════════════════════════════${plain}" + echo "$mirror" fi } update_x-ui() { cd ${xui_folder%/x-ui}/ - if [ -f "${xui_folder}/x-ui" ]; then - current_xui_version=$(${xui_folder}/x-ui -v) - echo -e "${green}Current x-ui version: ${current_xui_version}${plain}" - else - _fail "ERROR: Current x-ui version: unknown" - fi + local github_raw=$(get_github_raw) - echo -e "${green}Downloading new x-ui version...${plain}" - - tag_version=$(${curl_bin} -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" 2> /dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - if [[ ! -n "$tag_version" ]]; then - echo -e "${yellow}Trying to fetch version with IPv4...${plain}" - tag_version=$(${curl_bin} -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [ $# == 0 ]; then + tag_version=$(curl -4 -Ls "https://api.github.com/repos/mhsanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [[ ! -n "$tag_version" ]]; then - _fail "ERROR: Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later" + echo -e "${yellow}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}" + exit 1 + fi + echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." + curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/mhsanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz + if [[ $? -ne 0 ]]; then + echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" + exit 1 + fi + else + tag_version=$1 + tag_version_numeric=${tag_version#v} + min_version="2.3.5" + + if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then + echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}" + exit 1 + fi + + url="https://github.com/mhsanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" + echo -e "Beginning to install x-ui $1" + curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz ${url} + if [[ $? -ne 0 ]]; then + echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}" + exit 1 fi fi - echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." - ${curl_bin} -fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2> /dev/null + + curl -4fLRo /usr/bin/x-ui-temp ${github_raw}/mhsanaei/3x-ui/main/x-ui.sh if [[ $? -ne 0 ]]; then - echo -e "${yellow}Trying to fetch version with IPv4...${plain}" - ${curl_bin} -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2> /dev/null - if [[ $? -ne 0 ]]; then - _fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub" - fi + echo -e "${red}Failed to download x-ui.sh${plain}" + exit 1 fi if [[ -e ${xui_folder}/ ]]; then - echo -e "${green}Stopping x-ui...${plain}" if [[ $release == "alpine" ]]; then - if [ -f "/etc/init.d/x-ui" ]; then - rc-service x-ui stop > /dev/null 2>&1 - rc-update del x-ui > /dev/null 2>&1 - echo -e "${green}Removing old service unit version...${plain}" - rm -f /etc/init.d/x-ui > /dev/null 2>&1 - else - rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1 - _fail "ERROR: x-ui service unit not installed." - fi + rc-service x-ui stop else - if [ -f "${xui_service}/x-ui.service" ]; then - systemctl stop x-ui > /dev/null 2>&1 - systemctl disable x-ui > /dev/null 2>&1 - echo -e "${green}Removing old systemd unit version...${plain}" - rm ${xui_service}/x-ui.service -f > /dev/null 2>&1 - systemctl daemon-reload > /dev/null 2>&1 - else - rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1 - _fail "ERROR: x-ui systemd unit not installed." - fi + systemctl stop x-ui fi - echo -e "${green}Removing old x-ui version...${plain}" - rm ${xui_folder} -f > /dev/null 2>&1 - rm ${xui_folder}/x-ui.service -f > /dev/null 2>&1 - rm ${xui_folder}/x-ui.service.debian -f > /dev/null 2>&1 - rm ${xui_folder}/x-ui.service.arch -f > /dev/null 2>&1 - rm ${xui_folder}/x-ui.service.rhel -f > /dev/null 2>&1 - rm ${xui_folder}/x-ui -f > /dev/null 2>&1 - rm ${xui_folder}/x-ui.sh -f > /dev/null 2>&1 - echo -e "${green}Removing old xray version...${plain}" - rm ${xui_folder}/bin/xray-linux-amd64 -f > /dev/null 2>&1 - echo -e "${green}Removing old README and LICENSE file...${plain}" - rm ${xui_folder}/bin/README.md -f > /dev/null 2>&1 - rm ${xui_folder}/bin/LICENSE -f > /dev/null 2>&1 - else - rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1 - _fail "ERROR: x-ui not installed." + rm ${xui_folder}/ -rf fi - echo -e "${green}Installing new x-ui version...${plain}" - tar zxvf x-ui-linux-$(arch).tar.gz > /dev/null 2>&1 - rm x-ui-linux-$(arch).tar.gz -f > /dev/null 2>&1 - cd x-ui > /dev/null 2>&1 - chmod +x x-ui > /dev/null 2>&1 + tar zxvf x-ui-linux-$(arch).tar.gz + rm x-ui-linux-$(arch).tar.gz -f + + cd x-ui + chmod +x x-ui + chmod +x x-ui.sh - # Check the system's architecture and rename the file accordingly if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then - mv bin/xray-linux-$(arch) bin/xray-linux-arm > /dev/null 2>&1 - chmod +x bin/xray-linux-arm > /dev/null 2>&1 + mv bin/xray-linux-$(arch) bin/xray-linux-arm + chmod +x bin/xray-linux-arm fi + chmod +x x-ui bin/xray-linux-$(arch) - chmod +x x-ui bin/xray-linux-$(arch) > /dev/null 2>&1 + mv -f /usr/bin/x-ui-temp /usr/bin/x-ui + chmod +x /usr/bin/x-ui - echo -e "${green}Downloading and installing x-ui.sh script...${plain}" - ${curl_bin} -fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh > /dev/null 2>&1 - if [[ $? -ne 0 ]]; then - echo -e "${yellow}Trying to fetch x-ui with IPv4...${plain}" - ${curl_bin} -4fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh > /dev/null 2>&1 - if [[ $? -ne 0 ]]; then - _fail "ERROR: Failed to download x-ui.sh script, please be sure that your server can access GitHub" - fi - fi - - chmod +x ${xui_folder}/x-ui.sh > /dev/null 2>&1 - chmod +x /usr/bin/x-ui > /dev/null 2>&1 - mkdir -p /var/log/x-ui > /dev/null 2>&1 - - echo -e "${green}Changing owner...${plain}" - chown -R root:root ${xui_folder} > /dev/null 2>&1 - - if [ -f "${xui_folder}/bin/config.json" ]; then - echo -e "${green}Changing on config file permissions...${plain}" - chmod 640 ${xui_folder}/bin/config.json > /dev/null 2>&1 - fi + mkdir -p /var/log/x-ui if [[ $release == "alpine" ]]; then - echo -e "${green}Downloading and installing startup unit x-ui.rc...${plain}" - ${curl_bin} -fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc > /dev/null 2>&1 - if [[ $? -ne 0 ]]; then - ${curl_bin} -4fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc > /dev/null 2>&1 - if [[ $? -ne 0 ]]; then - _fail "ERROR: Failed to download startup unit x-ui.rc, please be sure that your server can access GitHub" - fi - fi - chmod +x /etc/init.d/x-ui > /dev/null 2>&1 - chown root:root /etc/init.d/x-ui > /dev/null 2>&1 - rc-update add x-ui > /dev/null 2>&1 - rc-service x-ui start > /dev/null 2>&1 + rc-service x-ui start else - if [ -f "x-ui.service" ]; then - echo -e "${green}Installing systemd unit...${plain}" - cp -f x-ui.service ${xui_service}/ > /dev/null 2>&1 - if [[ $? -ne 0 ]]; then - echo -e "${red}Failed to copy x-ui.service${plain}" - exit 1 - fi - else - service_installed=false - case "${release}" in - ubuntu | debian | armbian) - if [ -f "x-ui.service.debian" ]; then - echo -e "${green}Installing debian-like systemd unit...${plain}" - cp -f x-ui.service.debian ${xui_service}/x-ui.service > /dev/null 2>&1 - if [[ $? -eq 0 ]]; then - service_installed=true - fi - fi - ;; - arch | manjaro | parch) - if [ -f "x-ui.service.arch" ]; then - echo -e "${green}Installing arch-like systemd unit...${plain}" - cp -f x-ui.service.arch ${xui_service}/x-ui.service > /dev/null 2>&1 - if [[ $? -eq 0 ]]; then - service_installed=true - fi - fi - ;; - *) - if [ -f "x-ui.service.rhel" ]; then - echo -e "${green}Installing rhel-like systemd unit...${plain}" - cp -f x-ui.service.rhel ${xui_service}/x-ui.service > /dev/null 2>&1 - if [[ $? -eq 0 ]]; then - service_installed=true - fi - fi - ;; - esac - - # If service file not found in tar.gz, download from GitHub - if [ "$service_installed" = false ]; then - echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}" - case "${release}" in - ubuntu | debian | armbian) - ${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian > /dev/null 2>&1 - ;; - arch | manjaro | parch) - ${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch > /dev/null 2>&1 - ;; - *) - ${curl_bin} -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel > /dev/null 2>&1 - ;; - esac - - if [[ $? -ne 0 ]]; then - echo -e "${red}Failed to install x-ui.service from GitHub${plain}" - exit 1 - fi - fi - fi - chown root:root ${xui_service}/x-ui.service > /dev/null 2>&1 - chmod 644 ${xui_service}/x-ui.service > /dev/null 2>&1 - systemctl daemon-reload > /dev/null 2>&1 - systemctl enable x-ui > /dev/null 2>&1 - systemctl start x-ui > /dev/null 2>&1 + systemctl start x-ui fi - config_after_update - - echo -e "${green}x-ui ${tag_version}${plain} updating finished, it is running now..." + echo -e "${green}x-ui ${tag_version}${plain} update finished, it is running now..." echo -e "" echo -e "┌───────────────────────────────────────────────────────┐ │ ${blue}x-ui control menu usages (subcommands):${plain} │ @@ -986,5 +159,4 @@ update_x-ui() { } echo -e "${green}Running...${plain}" -install_base update_x-ui $1 diff --git a/x-ui.sh b/x-ui.sh index abba9226..5392ba7a 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -6,7 +6,9 @@ blue='\033[0;34m' yellow='\033[0;33m' plain='\033[0m' -#Add some basic function here +GITHUB_MIRROR_DEFAULT="https://gh.kejilion.pro" +GITHUB_RAW_DEFAULT="https://raw.githubusercontent.com" + function LOGD() { echo -e "${yellow}[DEG] $* ${plain}" } @@ -19,7 +21,33 @@ function LOGI() { echo -e "${green}[INF] $* ${plain}" } -# Port helpers: detect listener and owning process (best effort) +get_github_mirror() { + local custom_mirror="${GITHUB_MIRROR:-}" + if [[ -n "$custom_mirror" ]]; then + echo "$custom_mirror" + else + echo "$GITHUB_MIRROR_DEFAULT" + fi +} + +get_github_raw() { + local mirror=$(get_github_mirror) + if [[ "$mirror" == "$GITHUB_MIRROR_DEFAULT" ]]; then + echo "$GITHUB_RAW_DEFAULT" + else + echo "$mirror" + fi +} + +get_github_download_url() { + local mirror=$(get_github_mirror) + if [[ "$mirror" == "$GITHUB_MIRROR_DEFAULT" ]]; then + echo "https://github.com" + else + echo "$mirror" + fi +} + is_port_in_use() { local port="$1" if command -v ss > /dev/null 2>&1; then @@ -36,7 +64,6 @@ is_port_in_use() { return 1 } -# Simple helpers for domain/IP validation is_ipv4() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 } @@ -50,10 +77,8 @@ is_domain() { [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1 } -# check root [[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1 -# Check OS and set release variable if [[ -f /etc/os-release ]]; then source /etc/os-release release=$ID @@ -69,7 +94,6 @@ echo "The OS release is: $release" os_version="" os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.') -# Declare Variables xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}" xui_service="${XUI_SERVICE:=/etc/systemd/system}" log_folder="${XUI_LOG_FOLDER:=/var/log/x-ui}" @@ -77,6 +101,9 @@ mkdir -p "${log_folder}" iplimit_log_path="${log_folder}/3xipl.log" iplimit_banned_log_path="${log_folder}/3xipl-banned.log" +REPO_OWNER="mhsanaei" +REPO_NAME="3x-ui" + confirm() { if [[ $# > 1 ]]; then echo && read -rp "$1 [Default $2]: " temp @@ -108,7 +135,8 @@ before_show_menu() { } install() { - bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh) + local github_raw=$(get_github_raw) + bash <(curl -Ls ${github_raw}/${REPO_OWNER}/${REPO_NAME}/main/install.sh) if [[ $? == 0 ]]; then if [[ $# == 0 ]]; then start @@ -127,7 +155,8 @@ update() { fi return 0 fi - bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/update.sh) + local github_raw=$(get_github_raw) + bash <(curl -Ls ${github_raw}/${REPO_OWNER}/${REPO_NAME}/main/update.sh) if [[ $? == 0 ]]; then LOGI "Update is complete, Panel has automatically restarted " before_show_menu @@ -145,7 +174,8 @@ update_menu() { return 0 fi - curl -fLRo /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh + local github_raw=$(get_github_raw) + curl -fLRo /usr/bin/x-ui ${github_raw}/${REPO_OWNER}/${REPO_NAME}/main/x-ui.sh chmod +x ${xui_folder}/x-ui.sh chmod +x /usr/bin/x-ui @@ -166,16 +196,14 @@ legacy_version() { echo "Panel version cannot be empty. Exiting." exit 1 fi - # Use the entered panel version in the download link - install_command="bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/v$tag_version/install.sh") v$tag_version" + local install_command="bash <(curl -Ls ${github_raw}/${REPO_OWNER}/${REPO_NAME}/v$tag_version/install.sh) v$tag_version" echo "Downloading and installing panel version $tag_version..." eval $install_command } -# Function to handle the deletion of the script file delete_script() { - rm "$0" # Remove the script file itself + rm "$0" exit 1 } @@ -206,9 +234,9 @@ uninstall() { echo "" echo -e "Uninstalled Successfully.\n" echo "If you need to install this panel again, you can use below command:" - echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)${plain}" + local github_raw=$(get_github_raw) + echo -e "${green}bash <(curl -Ls ${github_raw}/${REPO_OWNER}/${REPO_NAME}/main/install.sh)${plain}" echo "" - # Trap the SIGTERM signal trap delete_script SIGTERM delete_script } @@ -259,7 +287,6 @@ reset_webbasepath() { config_webBasePath=$(gen_random_string 18) - # Apply the new web base path setting ${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}" > /dev/null 2>&1 echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}" @@ -340,7 +367,6 @@ check_config() { ssl_cert_issue_for_ip if [[ $? -eq 0 ]]; then echo -e "${green}Access URL: https://${server_ip}:${existing_port}${existing_webBasePath}${plain}" - # ssl_cert_issue_for_ip already restarts the panel, but ensure it's running start 0 > /dev/null 2>&1 else LOGE "IP certificate setup failed." @@ -579,7 +605,6 @@ disable_bbr() { rm /etc/sysctl.d/99-bbr-x-ui.conf sysctl --system else - # Replace BBR with CUBIC configurations if [ -f "/etc/sysctl.conf" ]; then sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf @@ -600,7 +625,6 @@ enable_bbr() { before_show_menu fi - # Enable BBR if [ -d "/etc/sysctl.d/" ]; then { echo "#$(sysctl -n net.core.default_qdisc):$(sysctl -n net.ipv4.tcp_congestion_control)" @@ -608,7 +632,6 @@ enable_bbr() { echo "net.ipv4.tcp_congestion_control = bbr" } > "/etc/sysctl.d/99-bbr-x-ui.conf" if [ -f "/etc/sysctl.conf" ]; then - # Backup old settings from sysctl.conf, if any sed -i 's/^net.core.default_qdisc/# &/' /etc/sysctl.conf sed -i 's/^net.ipv4.tcp_congestion_control/# &/' /etc/sysctl.conf fi @@ -621,7 +644,6 @@ enable_bbr() { sysctl -p fi - # Verify that BBR is enabled if [[ $(sysctl -n net.ipv4.tcp_congestion_control) == "bbr" ]]; then echo -e "${green}BBR has been enabled successfully.${plain}" else @@ -630,7 +652,9 @@ enable_bbr() { } update_shell() { - curl -fLRo /usr/bin/x-ui -z /usr/bin/x-ui https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh + local github_raw=$(get_github_raw) + local github_download=$(get_github_download_url) + curl -fLRo /usr/bin/x-ui -z /usr/bin/x-ui ${github_raw}/${REPO_OWNER}/${REPO_NAME}/main/x-ui.sh if [[ $? != 0 ]]; then echo "" LOGE "Failed to download script, Please check whether the machine can connect Github" @@ -642,7 +666,6 @@ update_shell() { fi } -# 0: running, 1: not running, 2: not installed check_status() { if [[ $release == "alpine" ]]; then if [[ ! -f /etc/init.d/x-ui ]]; then @@ -814,130 +837,103 @@ install_firewall() { echo "ufw firewall is already installed" fi - # Check if the firewall is inactive if ufw status | grep -q "Status: active"; then echo "Firewall is already active" else echo "Activating firewall..." - # Open the necessary ports ufw allow ssh ufw allow http ufw allow https - ufw allow 2053/tcp #webPort - ufw allow 2096/tcp #subport + ufw allow 2053/tcp + ufw allow 2096/tcp - # Enable the firewall ufw --force enable fi } open_ports() { - # Prompt the user to enter the ports they want to open read -rp "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports - # Check if the input is valid if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2 exit 1 fi - # Open the specified ports using ufw IFS=',' read -ra PORT_LIST <<< "$ports" for port in "${PORT_LIST[@]}"; do if [[ $port == *-* ]]; then - # Split the range into start and end ports start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) - # Open the port range ufw allow $start_port:$end_port/tcp ufw allow $start_port:$end_port/udp else - # Open the single port ufw allow "$port" fi done - # Confirm that the ports are opened echo "Opened the specified ports:" for port in "${PORT_LIST[@]}"; do if [[ $port == *-* ]]; then start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) - # Check if the port range has been successfully opened (ufw status | grep -q "$start_port:$end_port") && echo "$start_port-$end_port" else - # Check if the individual port has been successfully opened (ufw status | grep -q "$port") && echo "$port" fi done } delete_ports() { - # Display current rules with numbers echo "Current UFW rules:" ufw status numbered - # Ask the user how they want to delete rules echo "Do you want to delete rules by:" echo "1) Rule numbers" echo "2) Ports" read -rp "Enter your choice (1 or 2): " choice if [[ $choice -eq 1 ]]; then - # Deleting by rule numbers read -rp "Enter the rule numbers you want to delete (1, 2, etc.): " rule_numbers - # Validate the input if ! [[ $rule_numbers =~ ^([0-9]+)(,[0-9]+)*$ ]]; then echo "Error: Invalid input. Please enter a comma-separated list of rule numbers." >&2 exit 1 fi - # Split numbers into an array IFS=',' read -ra RULE_NUMBERS <<< "$rule_numbers" for rule_number in "${RULE_NUMBERS[@]}"; do - # Delete the rule by number ufw delete "$rule_number" || echo "Failed to delete rule number $rule_number" done echo "Selected rules have been deleted." elif [[ $choice -eq 2 ]]; then - # Deleting by ports read -rp "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports - # Validate the input if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2 exit 1 fi - # Split ports into an array IFS=',' read -ra PORT_LIST <<< "$ports" for port in "${PORT_LIST[@]}"; do if [[ $port == *-* ]]; then - # Split the port range start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) - # Delete the port range ufw delete allow $start_port:$end_port/tcp ufw delete allow $start_port:$end_port/udp else - # Delete a single port ufw delete allow "$port" fi done - # Confirmation of deletion echo "Deleted the specified ports:" for port in "${PORT_LIST[@]}"; do if [[ $port == *-* ]]; then start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) - # Check if the port range has been deleted (ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port" else - # Check if the individual port has been deleted (ufw status | grep -q "$port") || echo "$port" fi done @@ -969,7 +965,6 @@ update_geofiles() { ;; esac for dat in "${dat_files[@]}"; do - # Remove suffix for remote filename (e.g., geoip_IR -> geoip) remote_file="${dat%%_*}" curl -fLRo ${xui_folder}/bin/${dat}.dat -z ${xui_folder}/bin/${dat}.dat \ https://github.com/${dat_source}/releases/latest/download/${remote_file}.dat @@ -1018,14 +1013,13 @@ update_geo() { } install_acme() { - # Check if acme.sh is already installed if command -v ~/.acme.sh/acme.sh &> /dev/null; then LOGI "acme.sh is already installed." return 0 fi LOGI "Installing acme.sh..." - cd ~ || return 1 # Ensure you can change to the home directory + cd ~ || return 1 curl -s https://get.acme.sh | sh if [ $? -ne 0 ]; then @@ -1164,7 +1158,6 @@ ssl_cert_issue_for_ip() { local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') - # Get server IP local URL_lists=( "https://api4.ipify.org" "https://ipv4.icanhazip.com" @@ -1198,12 +1191,10 @@ ssl_cert_issue_for_ip() { LOGI "Server IP detected: ${server_ip}" - # Ask for optional IPv6 local ipv6_addr="" read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr - ipv6_addr="${ipv6_addr// /}" # Trim whitespace + ipv6_addr="${ipv6_addr// /}" - # check for acme.sh first if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then LOGI "acme.sh not found, installing..." install_acme @@ -1213,7 +1204,6 @@ ssl_cert_issue_for_ip() { fi fi - # install socat case "${release}" in ubuntu | debian | armbian) apt-get update > /dev/null 2>&1 && apt-get install socat -y > /dev/null 2>&1 @@ -1238,22 +1228,18 @@ ssl_cert_issue_for_ip() { apk add socat curl openssl > /dev/null 2>&1 ;; *) - LOGW "Unsupported OS for automatic socat installation" ;; esac - # Create certificate directory certPath="/root/cert/ip" mkdir -p "$certPath" - # Build domain arguments local domain_args="-d ${server_ip}" if [[ -n "$ipv6_addr" ]] && is_ipv6 "$ipv6_addr"; then domain_args="${domain_args} -d ${ipv6_addr}" LOGI "Including IPv6 address: ${ipv6_addr}" fi - # Choose port for HTTP-01 listener (default 80, allow override) local WebPort="" read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort WebPort="${WebPort:-80}" @@ -1289,10 +1275,8 @@ ssl_cert_issue_for_ip() { fi done - # Reload command - restarts panel after renewal local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null" - # issue the certificate for IP with shortlived profile ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force ~/.acme.sh/acme.sh --issue \ ${domain_args} \ @@ -1306,7 +1290,6 @@ ssl_cert_issue_for_ip() { if [ $? -ne 0 ]; then LOGE "Failed to issue certificate for IP: ${server_ip}" LOGE "Make sure port ${WebPort} is open and the server is accessible from the internet" - # Cleanup acme.sh data for both IPv4 and IPv6 if specified rm -rf ~/.acme.sh/${server_ip} 2> /dev/null [[ -n "$ipv6_addr" ]] && rm -rf ~/.acme.sh/${ipv6_addr} 2> /dev/null rm -rf ${certPath} 2> /dev/null @@ -1315,18 +1298,13 @@ ssl_cert_issue_for_ip() { LOGI "Certificate issued successfully for IP: ${server_ip}" fi - # Install the certificate - # Note: acme.sh may report "Reload error" and exit non-zero if reloadcmd fails, - # but the cert files are still installed. We check for files instead of exit code. ~/.acme.sh/acme.sh --installcert -d ${server_ip} \ --key-file "${certPath}/privkey.pem" \ --fullchain-file "${certPath}/fullchain.pem" \ --reloadcmd "${reloadCmd}" 2>&1 || true - # Verify certificate files exist (don't rely on exit code - reloadcmd failure causes non-zero) if [[ ! -f "${certPath}/fullchain.pem" || ! -f "${certPath}/privkey.pem" ]]; then LOGE "Certificate files not found after installation" - # Cleanup acme.sh data for both IPv4 and IPv6 if specified rm -rf ~/.acme.sh/${server_ip} 2> /dev/null [[ -n "$ipv6_addr" ]] && rm -rf ~/.acme.sh/${ipv6_addr} 2> /dev/null rm -rf ${certPath} 2> /dev/null @@ -1335,12 +1313,10 @@ ssl_cert_issue_for_ip() { LOGI "Certificate files installed successfully" - # enable auto-renew ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 chmod 600 $certPath/privkey.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null - # Set certificate paths for the panel local webCertFile="${certPath}/fullchain.pem" local webKeyFile="${certPath}/privkey.pem" @@ -1363,7 +1339,6 @@ ssl_cert_issue_for_ip() { ssl_cert_issue() { local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') - # check for acme.sh first if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then echo "acme.sh could not be found. we will install it" install_acme @@ -1373,7 +1348,6 @@ ssl_cert_issue() { fi fi - # install socat case "${release}" in ubuntu | debian | armbian) apt-get update > /dev/null 2>&1 && apt-get install socat -y > /dev/null 2>&1 @@ -1398,7 +1372,6 @@ ssl_cert_issue() { apk add socat curl openssl > /dev/null 2>&1 ;; *) - LOGW "Unsupported OS for automatic socat installation" ;; esac if [ $? -ne 0 ]; then @@ -1408,11 +1381,10 @@ ssl_cert_issue() { LOGI "install socat succeed..." fi - # get the domain here, and we need to verify it local domain="" while true; do read -rp "Please enter your domain name: " domain - domain="${domain// /}" # Trim whitespace + domain="${domain// /}" if [[ -z "$domain" ]]; then LOGE "Domain name cannot be empty. Please try again." @@ -1429,7 +1401,6 @@ ssl_cert_issue() { LOGD "Your domain is: ${domain}, checking it..." SSL_ISSUED_DOMAIN="${domain}" - # detect existing certificate and reuse it if present local cert_exists=0 if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then cert_exists=1 @@ -1440,7 +1411,6 @@ ssl_cert_issue() { LOGI "Your domain is ready for issuing certificates now..." fi - # create a directory for the certificate certPath="/root/cert/${domain}" if [ ! -d "$certPath" ]; then mkdir -p "$certPath" @@ -1449,7 +1419,6 @@ ssl_cert_issue() { mkdir -p "$certPath" fi - # get the port number for the standalone server local WebPort=80 read -rp "Please choose which port to use (default is 80): " WebPort if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then @@ -1459,7 +1428,6 @@ ssl_cert_issue() { LOGI "Will use port: ${WebPort} to issue certificates. Please make sure this port is open." if [[ ${cert_exists} -eq 0 ]]; then - # issue the certificate ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force if [ $? -ne 0 ]; then @@ -1499,7 +1467,6 @@ ssl_cert_issue() { esac fi - # install the certificate local installOutput="" installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \ --key-file /root/cert/${domain}/privkey.pem \ @@ -1522,7 +1489,6 @@ ssl_cert_issue() { exit 1 fi - # enable auto-renew ~/.acme.sh/acme.sh --upgrade --auto-upgrade if [ $? -ne 0 ]; then LOGE "Auto renew failed, certificate details:" @@ -1537,7 +1503,6 @@ ssl_cert_issue() { chmod 644 $certPath/fullchain.pem fi - # Prompt user to set panel paths after successful certificate installation read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then local webCertFile="/root/cert/${domain}/fullchain.pem" @@ -1572,7 +1537,6 @@ ssl_cert_issue_CF() { confirm "Do you confirm the information and wish to proceed? [y/n]" "y" if [ $? -eq 0 ]; then - # Check for acme.sh first if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then echo "acme.sh could not be found. We will install it." install_acme @@ -1588,7 +1552,6 @@ ssl_cert_issue_CF() { read -rp "Input your domain here: " CF_Domain LOGD "Your domain name is set to: ${CF_Domain}" - # Set up Cloudflare API details CF_GlobalKey="" CF_AccountEmail="" LOGD "Please set the API key:" @@ -1599,7 +1562,6 @@ ssl_cert_issue_CF() { read -rp "Input your email here: " CF_AccountEmail LOGD "Your registered email address is: ${CF_AccountEmail}" - # Set the default CA to Let's Encrypt ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force if [ $? -ne 0 ]; then LOGE "Default CA, Let'sEncrypt fail, script exiting..." @@ -1609,7 +1571,6 @@ ssl_cert_issue_CF() { export CF_Key="${CF_GlobalKey}" export CF_Email="${CF_AccountEmail}" - # Issue the certificate using Cloudflare DNS ~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log --force if [ $? -ne 0 ]; then LOGE "Certificate issuance failed, script exiting..." @@ -1618,7 +1579,6 @@ ssl_cert_issue_CF() { LOGI "Certificate issued successfully, Installing..." fi - # Install the certificate certPath="/root/cert/${CF_Domain}" if [ -d "$certPath" ]; then rm -rf ${certPath} @@ -1666,7 +1626,6 @@ ssl_cert_issue_CF() { LOGI "Certificate installed successfully, Turning on automatic updates..." fi - # Enable auto-update ~/.acme.sh/acme.sh --upgrade --auto-upgrade if [ $? -ne 0 ]; then LOGE "Auto update setup failed, script exiting..." @@ -1678,7 +1637,6 @@ ssl_cert_issue_CF() { chmod 644 ${certPath}/fullchain.pem fi - # Prompt user to set panel paths after successful certificate installation read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then local webCertFile="${certPath}/fullchain.pem" @@ -1703,15 +1661,11 @@ ssl_cert_issue_CF() { } run_speedtest() { - # Check if Speedtest is already installed if ! command -v speedtest &> /dev/null; then - # If not installed, determine installation method if command -v snap &> /dev/null; then - # Use snap to install Speedtest echo "Installing Speedtest using snap..." snap install speedtest else - # Fallback to using package managers local pkg_manager="" local speedtest_install_script="" @@ -1745,7 +1699,7 @@ run_speedtest() { ip_validation() { ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" - ipv4_regex="^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)$" + ipv4_regex="^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|0)\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|0)$" } iplimit_main() { @@ -1856,14 +1810,6 @@ install_iplimit() { if ! command -v fail2ban-client &> /dev/null; then echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" - # Install fail2ban together with nftables. Recent fail2ban packages - # default to `banaction = nftables-multiport` in /etc/fail2ban/jail.conf, - # but the `nftables` package isn't pulled in as a dependency on most - # minimal server images (Debian 12+, Ubuntu 24+, fresh RHEL-family). - # Without `nft` in PATH the default sshd jail fails to ban with - # stderr: '/bin/sh: 1: nft: not found' - # even though our own 3x-ipl jail uses iptables. Bundling the binary - # at install time prevents that confusing log spam for new installs. case "${release}" in ubuntu) apt-get update @@ -1918,24 +1864,18 @@ install_iplimit() { echo -e "${green}Configuring IP Limit...${plain}\n" - # make sure there's no conflict for jail files iplimit_remove_conflicts - # Check if log file exists if ! test -f "${iplimit_banned_log_path}"; then touch ${iplimit_banned_log_path} fi - # Check if service log file exists so fail2ban won't return error if ! test -f "${iplimit_log_path}"; then touch ${iplimit_log_path} fi - # Create the iplimit jail files - # we didn't pass the bantime here to use the default value create_iplimit_jails - # Launching fail2ban if [[ $release == "alpine" ]]; then if [[ $(rc-service fail2ban status | grep -F 'status: started' -c) == 0 ]]; then rc-service fail2ban start @@ -2063,13 +2003,10 @@ show_banlog() { } create_iplimit_jails() { - # Use default bantime if not passed => 30 minutes local bantime="${1:-30}" - # Uncomment 'allowipv6 = auto' in fail2ban.conf sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf - # On Debian 12+ fail2ban's default backend should be changed to systemd if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf fi @@ -2130,7 +2067,6 @@ iplimit_remove_conflicts() { ) for file in "${jail_files[@]}"; do - # Check for [3x-ipl] config in jail file then remove it if test -f "${file}" && grep -qw '3x-ipl' ${file}; then sed -i "/\[3x-ipl\]/,/^$/d" ${file} echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n" @@ -2270,42 +2206,42 @@ show_usage() { show_menu() { echo -e " -╔────────────────────────────────────────────────╗ -│ ${green}3X-UI Panel Management Script${plain} │ -│ ${green}0.${plain} Exit Script │ -│────────────────────────────────────────────────│ -│ ${green}1.${plain} Install │ -│ ${green}2.${plain} Update │ -│ ${green}3.${plain} Update Menu │ -│ ${green}4.${plain} Legacy Version │ -│ ${green}5.${plain} Uninstall │ -│────────────────────────────────────────────────│ -│ ${green}6.${plain} Reset Username & Password │ -│ ${green}7.${plain} Reset Web Base Path │ -│ ${green}8.${plain} Reset Settings │ -│ ${green}9.${plain} Change Port │ -│ ${green}10.${plain} View Current Settings │ -│────────────────────────────────────────────────│ -│ ${green}11.${plain} Start │ -│ ${green}12.${plain} Stop │ -│ ${green}13.${plain} Restart │ -| ${green}14.${plain} Restart Xray │ -│ ${green}15.${plain} Check Status │ -│ ${green}16.${plain} Logs Management │ -│────────────────────────────────────────────────│ -│ ${green}17.${plain} Enable Autostart │ -│ ${green}18.${plain} Disable Autostart │ -│────────────────────────────────────────────────│ -│ ${green}19.${plain} SSL Certificate Management │ -│ ${green}20.${plain} Cloudflare SSL Certificate │ -│ ${green}21.${plain} IP Limit Management │ -│ ${green}22.${plain} Firewall Management │ -│ ${green}23.${plain} SSH Port Forwarding Management │ -│────────────────────────────────────────────────│ -│ ${green}24.${plain} Enable BBR │ -│ ${green}25.${plain} Update Geo Files │ -│ ${green}26.${plain} Speedtest by Ookla │ -╚────────────────────────────────────────────────╝ +╔════════════════════════════════════════════════════╗ +║ ${green}3X-UI Panel Management Script${plain} ║ +║ ${green}0.${plain} Exit Script ║ +║──────────────────────────────────────────────────║ +║ ${green}1.${plain} Install ║ +║ ${green}2.${plain} Update ║ +║ ${green}3.${plain} Update Menu ║ +║ ${green}4.${plain} Legacy Version ║ +║ ${green}5.${plain} Uninstall ║ +║──────────────────────────────────────────────────║ +║ ${green}6.${plain} Reset Username & Password ║ +║ ${green}7.${plain} Reset Web Base Path ║ +║ ${green}8.${plain} Reset Settings ║ +║ ${green}9.${plain} Change Port ║ +║ ${green}10.${plain} View Current Settings ║ +║──────────────────────────────────────────────────║ +║ ${green}11.${plain} Start ║ +║ ${green}12.${plain} Stop ║ +║ ${green}13.${plain} Restart ║ +║ ${green}14.${plain} Restart Xray ║ +║ ${green}15.${plain} Check Status ║ +║ ${green}16.${plain} Logs Management ║ +║──────────────────────────────────────────────────║ +║ ${green}17.${plain} Enable Autostart ║ +║ ${green}18.${plain} Disable Autostart ║ +║──────────────────────────────────────────────────║ +║ ${green}19.${plain} SSL Certificate Management ║ +║ ${green}20.${plain} Cloudflare SSL Certificate ║ +║ ${green}21.${plain} IP Limit Management ║ +║ ${green}22.${plain} Firewall Management ║ +║ ${green}23.${plain} SSH Port Forwarding Management ║ +║──────────────────────────────────────────────────║ +║ ${green}24.${plain} Enable BBR ║ +║ ${green}25.${plain} Update Geo Files ║ +║ ${green}26.${plain} Speedtest by Ookla ║ +╚════════════════════════════════════════════════════╝ " show_status echo && read -rp "Please enter your selection [0-26]: " num