feat: 优化项目并添加多语言和GitHub加速

Co-authored-by: traeagent <traeagent@users.noreply.github.com>
This commit is contained in:
ruyawwj 2026-05-11 04:20:49 +00:00
parent 8834e5fbbe
commit 6fd3e9553f
13 changed files with 339 additions and 1315 deletions

View file

@ -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)
<p align="center"> <p align="center">
<picture> <picture>
@ -7,50 +7,45 @@
</picture> </picture>
</p> </p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) **3X-UI** — لوحة تحكم متقدمة مفتوحة المصدر لإدارة خوادم Xray-core. توفر واجهة سهلة الاستخدام لتكوين ومراقبة بروتوكولات VPN والخوادم الوكيلة المختلفة.
[![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] > [!IMPORTANT]
> هذا المشروع مخصص للاستخدام الشخصي والاتصال فقط، يرجى عدم استخدامه لأغراض غير قانونية، يرجى عدم استخدامه في بيئة الإنتاج. > هذا المشروع مخصص للاستخدام الشخصي والبحث في الاتصالات فقط. يرجى عدم استخدامه لأغراض غير قانونية أو في بيئة الإنتاج.
كمشروع محسن من مشروع X-UI الأصلي، يوفر 3X-UI استقرارًا محسنًا ودعمًا أوسع للبروتوكولات وميزات إضافية. كمحاكاة محسنة من مشروع X-UI الأصلي، يوفر 3X-UI استقرارًا أفضل ودعمًا أوسع للبروتوكولات وميزات إضافية.
## البدء السريع ## البدء السريع
``` ### سكريبت التثبيت بنقرة واحدة
**المصدر الرسمي:**
```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) 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/) - [alireza0](https://github.com/alireza0/)
## الاعتراف ## الإشادة
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (الترخيص: **GPL-3.0**): _قواعد توجيه v2ray/xray و v2ray/xray-clients المحسنة مع النطاقات الإيرانية المدمجة وتركيز على الأمان وحظر الإعلانات._ - [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 محدثة تلقائيًا بناءً على بيانات النطاقات والعناوين المحظورة في روسيا._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (الترخيص: **GPL-3.0**): _قواعد V2Ray المحدثة تلقائيًا بناءً على النطاقات المحظورة في روسيا._
## دعم المشروع ## دعم المشروع
**إذا كان هذا المشروع مفيدًا لك، فقد ترغب في إعطائه**:star2: **إذا كان هذا المشروع مفيدًا لك، يمكنك إعطائه**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## النجوم عبر الزمن
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View file

@ -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)
<p align="center"> <p align="center">
<picture> <picture>
@ -7,28 +7,35 @@
</picture> </picture>
</p> </p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) **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.
[![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.
> [!IMPORTANT] > [!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. 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 ## 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) 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 ## Un Agradecimiento Especial a
@ -36,22 +43,9 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M
## Reconocimientos ## 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._ - [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**): _Este repositorio contiene reglas de enrutamiento V2Ray actualizadas automáticamente basadas en datos de dominios y direcciones bloqueadas en Rusia._ - [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 ## Apoyar el Proyecto
**Si este proyecto te es útil, puedes darle una**:star2: **Si este proyecto te es útil, puedes darle una**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Estrellas a lo Largo del Tiempo
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View file

@ -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)
<p align="center"> <p align="center">
<picture> <picture>
@ -7,51 +7,45 @@
</picture> </picture>
</p> </p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) **3X-UI** — پنل مدیریت وب پیشرفته متن‌باز برای مدیریت سرورهای Xray-core. این پنل یک رابط کاربری آسان برای پیکربندی و نظارت بر پروتکل‌های مختلف VPN و پروکسی فراهم می‌کند.
[![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] > [!IMPORTANT]
> این پروژه فقط برای استفاده شخصی و ارتباطات است، لطفاً از آن برای اهداف غیرقانونی استفاده نکنید، لطفاً از آن در محیط تولید استفاده نکنید. > این پروژه فقط برای استفاده شخصی و تحقیقات ارتباطی است. لطفاً از آن برای اهداف غیرقانونی استفاده نکنید و در محیط تولید به کار نبرید.
به عنوان یک نسخه بهبود یافته از پروژه اصلی X-UI، 3X-UI پایداری بهتر، پشتیبانی گسترده‌تر از پروتکل‌ها و ویژگی‌های اضافی را ارائه می‌دهد. به عنوان یک نسخه بهبود یافته از پروژه اصلی X-UI، 3X-UI پایداری بهتر، پشتیبانی گسترده‌تر از پروتکل‌ها و ویژگی‌های اضافی را ارائه می‌دهد.
## شروع سریع ## شروع سریع
``` ### اسکریپت نصب با یک کلیک
**منبع رسمی:**
```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) 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) مراجعه کنید. برای مستندات کامل، لطفاً به [ویکی پروژه](https://github.com/MHSanaei/3x-ui/wiki) مراجعه کنید.
## تشکر ویژه از ## تقدیر ویژه
- [alireza0](https://github.com/alireza0/) - [alireza0](https://github.com/alireza0/)
## قدردانی ## تشکر و قدردانی
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**): _قوانین مسیریابی بهبود یافته v2ray/xray و v2ray/xray-clients با دامنه‌های ایرانی داخلی و تمرکز بر امنیت و مسدود کردن تبلیغات._ - [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 به‌روزرسانی شده خودکار بر اساس داده‌های دامنه‌ها و آدرس‌های مسدود شده در روسیه است._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**): _قوانین V2Ray که بر اساس دامنه‌های مسدود شده در روسیه به‌روزرسانی می‌شوند._
## پشتیبانی از پروژه ## حمایت از پروژه
**اگر این پروژه برای شما مفید است، می‌توانید به آن یک**:star2: بدهید **اگر این پروژه برای شما مفید است، می‌توانید به آن**:star2: **بدهید**
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## ستاره‌ها در طول زمان
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View file

@ -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)
<p align="center"> <p align="center">
<picture> <picture>
@ -7,27 +7,34 @@
</picture> </picture>
</p> </p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) **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.
[![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.
> [!IMPORTANT] > [!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 ## Quick Start
### One-Click Installation Script
**Official Source:**
```bash ```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) 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). For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki).
## A Special Thanks to ## 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._ - [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._ - [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: **If this project is helpful to you, you may wish to give it a**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Stargazers over Time
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View file

@ -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)
<p align="center"> <p align="center">
<picture> <picture>
@ -7,51 +7,45 @@
</picture> </picture>
</p> </p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) **3X-UI** — Панель управления веб-интерфейса Xray-core. Она предлагает удобный интерфейс для настройки и мониторинга различных VPN и прокси-протоколов.
[![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] > [!IMPORTANT]
> Этот проект предназначен только для личного использования, пожалуйста, не используйте его в незаконных целях и в производственной среде. > Этот проект предназначен только для личного использования и исследований связи. Пожалуйста, не используйте его в незаконных целях и не используйте в производственной среде.
Как улучшенная версия оригинального проекта X-UI, 3X-UI обеспечивает повышенную стабильность, более широкую поддержку протоколов и дополнительные функции. В качестве улучшенной версии оригинального проекта X-UI, 3X-UI обеспечивает лучшую стабильность, более широкую поддержку протоколов и дополнительные функции.
## Быстрый старт ## Быстрый старт
``` ### Скрипт быстрой установки
**Официальный источник:**
```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) 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/) - [alireza0](https://github.com/alireza0/)
## Благодарности ## Признание
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Лицензия: **GPL-3.0**): _Улучшенные правила маршрутизации для v2ray/xray и v2ray/xray-clients со встроенными иранскими доменами и фокусом на безопасность и блокировку рекламы._ - [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 на основе данных о заблокированных доменах и адресах в России._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Лицензия: **GPL-3.0**): _Автоматически обновляемые правила V2Ray на основе заблокированных в России доменов._
## Поддержка проекта ## Поддержать проект
**Если этот проект полезен для вас, вы можете поставить ему**:star2: **Если этот проект полезен для вас, вы можете поставить**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Звезды с течением времени
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View file

@ -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)
<p align="center"> <p align="center">
<picture> <picture>
@ -7,27 +7,34 @@
</picture> </picture>
</p> </p>
[![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] > [!IMPORTANT]
> 本项目仅用于个人使用和通信,请勿将其用于非法目的,请勿在生产环境中使用。 > 本项目仅用于个人学习和通信研究,请勿将其用于非法目的,请勿在生产环境中使用。
作为原始 X-UI 项目的增强版本3X-UI 提供了更好的稳定性、更广泛的协议支持和额外的功能。 作为原始 X-UI 项目的增强版本3X-UI 提供了更好的稳定性、更广泛的协议支持和额外的功能。
## 快速开始 ## 快速开始
``` ### 一键安装脚本
**官方源安装:**
```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) 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)。 完整文档请参阅 [项目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: **如果这个项目对您有帮助,您可以给它一个**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## 随时间变化的星标数
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View file

@ -1 +1 @@
x-ui 3x-ui

View file

@ -1,5 +1,5 @@
{ {
"name": "3x-ui-frontend", "name": "3x-ui",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",

View file

@ -29,6 +29,7 @@ import { LanguageManager } from '@/utils';
// "lazy" here effectively means "load only what this page needs for // "lazy" here effectively means "load only what this page needs for
// its lifetime." // its lifetime."
const FALLBACK = 'en-US'; const FALLBACK = 'en-US';
const DEFAULT_LOCALE = 'zh-CN';
const lazyModules = import.meta.glob('../../../web/translation/*.json'); const lazyModules = import.meta.glob('../../../web/translation/*.json');
const eagerModules = import.meta.glob('../../../web/translation/*.json', { eager: true }); 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 // Resolve the active locale via LanguageManager so the cookie set on
// the legacy panel keeps working after a user upgrades. Falls back // 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(); let active = LanguageManager.getLanguage();
if (!Object.prototype.hasOwnProperty.call(lazyModules, moduleKeyFor(active))) { if (!Object.prototype.hasOwnProperty.call(lazyModules, moduleKeyFor(active))) {
active = FALLBACK; active = DEFAULT_LOCALE;
} }
const messages = {}; const messages = {};

View file

@ -840,11 +840,11 @@ export class LanguageManager {
if (LanguageManager.isSupportLanguage(lang)) { if (LanguageManager.isSupportLanguage(lang)) {
CookieManager.setCookie("lang", lang); CookieManager.setCookie("lang", lang);
} else { } else {
CookieManager.setCookie("lang", "en-US"); CookieManager.setCookie("lang", "zh-CN");
window.location.reload(); window.location.reload();
} }
} else { } else {
CookieManager.setCookie("lang", "en-US"); CookieManager.setCookie("lang", "zh-CN");
window.location.reload(); window.location.reload();
} }
} }
@ -854,7 +854,7 @@ export class LanguageManager {
static setLanguage(language) { static setLanguage(language) {
if (!LanguageManager.isSupportLanguage(language)) { if (!LanguageManager.isSupportLanguage(language)) {
language = "en-US"; language = "zh-CN";
} }
CookieManager.setCookie("lang", language); CookieManager.setCookie("lang", language);

View file

@ -11,10 +11,12 @@ cur_dir=$(pwd)
xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}" xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}"
xui_service="${XUI_SERVICE:=/etc/systemd/system}" 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 [[ $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 if [[ -f /etc/os-release ]]; then
source /etc/os-release source /etc/os-release
release=$ID release=$ID
@ -42,7 +44,6 @@ arch() {
echo "Arch: $(arch)" echo "Arch: $(arch)"
# Simple helpers
is_ipv4() { is_ipv4() {
[[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 [[ "$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 [[ "$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() { is_port_in_use() {
local port="$1" local port="$1"
if command -v ss > /dev/null 2>&1; then if command -v ss > /dev/null 2>&1; then
@ -73,6 +73,24 @@ is_port_in_use() {
return 1 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() { install_base() {
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
@ -131,7 +149,6 @@ setup_ssl_certificate() {
echo -e "${green}Setting up SSL certificate...${plain}" echo -e "${green}Setting up SSL certificate...${plain}"
# Check if acme.sh is installed
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
install_acme install_acme
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -140,11 +157,9 @@ setup_ssl_certificate() {
fi fi
fi fi
# Create certificate directory
local certPath="/root/cert/${domain}" local certPath="/root/cert/${domain}"
mkdir -p "$certPath" mkdir -p "$certPath"
# Issue certificate
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}" echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${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 return 1
fi fi
# Install certificate
~/.acme.sh/acme.sh --installcert -d ${domain} \ ~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \ --key-file /root/cert/${domain}/privkey.pem \
--fullchain-file /root/cert/${domain}/fullchain.pem \ --fullchain-file /root/cert/${domain}/fullchain.pem \
@ -170,13 +184,10 @@ setup_ssl_certificate() {
return 1 return 1
fi fi
# Enable auto-renew
~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 ~/.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 600 $certPath/privkey.pem 2> /dev/null
chmod 644 $certPath/fullchain.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null
# Set certificate for panel
local webCertFile="/root/cert/${domain}/fullchain.pem" local webCertFile="/root/cert/${domain}/fullchain.pem"
local webKeyFile="/root/cert/${domain}/privkey.pem" local webKeyFile="/root/cert/${domain}/privkey.pem"
@ -190,17 +201,14 @@ setup_ssl_certificate() {
fi 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() { setup_ip_certificate() {
local ipv4="$1" 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 "${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}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}" 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 if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
install_acme install_acme
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -209,7 +217,6 @@ setup_ip_certificate() {
fi fi
fi fi
# Validate IP address
if [[ -z "$ipv4" ]]; then if [[ -z "$ipv4" ]]; then
echo -e "${red}IPv4 address is required${plain}" echo -e "${red}IPv4 address is required${plain}"
return 1 return 1
@ -220,21 +227,17 @@ setup_ip_certificate() {
return 1 return 1
fi fi
# Create certificate directory
local certDir="/root/cert/ip" local certDir="/root/cert/ip"
mkdir -p "$certDir" mkdir -p "$certDir"
# Build domain arguments
local domain_args="-d ${ipv4}" local domain_args="-d ${ipv4}"
if [[ -n "$ipv6" ]] && is_ipv6 "$ipv6"; then if [[ -n "$ipv6" ]] && is_ipv6 "$ipv6"; then
domain_args="${domain_args} -d ${ipv6}" domain_args="${domain_args} -d ${ipv6}"
echo -e "${green}Including IPv6 address: ${ipv6}${plain}" echo -e "${green}Including IPv6 address: ${ipv6}${plain}"
fi 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" 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="" local WebPort=""
read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
WebPort="${WebPort:-80}" 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}" echo -e "${yellow}Reminder: Let's Encrypt still connects on port 80; forward external port 80 to ${WebPort}.${plain}"
fi fi
# Ensure chosen port is available
while true; do while true; do
if is_port_in_use "${WebPort}"; then if is_port_in_use "${WebPort}"; then
echo -e "${yellow}Port ${WebPort} is in use.${plain}" echo -e "${yellow}Port ${WebPort} is in use.${plain}"
@ -271,7 +273,6 @@ setup_ip_certificate() {
fi fi
done done
# Issue certificate with shortlived profile
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" 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 --set-default-ca --server letsencrypt --force > /dev/null 2>&1
@ -287,7 +288,6 @@ setup_ip_certificate() {
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo -e "${red}Failed to issue IP certificate${plain}" 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}" 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 rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
rm -rf ${certDir} 2> /dev/null rm -rf ${certDir} 2> /dev/null
@ -296,18 +296,13 @@ setup_ip_certificate() {
echo -e "${green}Certificate issued successfully, installing...${plain}" 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} \ ~/.acme.sh/acme.sh --installcert -d ${ipv4} \
--key-file "${certDir}/privkey.pem" \ --key-file "${certDir}/privkey.pem" \
--fullchain-file "${certDir}/fullchain.pem" \ --fullchain-file "${certDir}/fullchain.pem" \
--reloadcmd "${reloadCmd}" 2>&1 || true --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 if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
echo -e "${red}Certificate files not found after installation${plain}" 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 rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
rm -rf ${certDir} 2> /dev/null rm -rf ${certDir} 2> /dev/null
@ -316,14 +311,11 @@ setup_ip_certificate() {
echo -e "${green}Certificate files installed successfully${plain}" 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 ~/.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 600 ${certDir}/privkey.pem 2> /dev/null
chmod 644 ${certDir}/fullchain.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}" echo -e "${green}Setting certificate paths for the panel...${plain}"
${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" ${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem"
@ -342,12 +334,10 @@ setup_ip_certificate() {
return 0 return 0
} }
# Comprehensive manual SSL certificate issuance via acme.sh
ssl_cert_issue() { 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_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:]') 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 if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
echo "acme.sh could not be found. Installing now..." echo "acme.sh could not be found. Installing now..."
cd ~ || return 1 cd ~ || return 1
@ -360,11 +350,10 @@ ssl_cert_issue() {
fi fi
fi fi
# get the domain here, and we need to verify it
local domain="" local domain=""
while true; do while true; do
read -rp "Please enter your domain name: " domain read -rp "Please enter your domain name: " domain
domain="${domain// /}" # Trim whitespace domain="${domain// /}"
if [[ -z "$domain" ]]; then if [[ -z "$domain" ]]; then
echo -e "${red}Domain name cannot be empty. Please try again.${plain}" 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}" echo -e "${green}Your domain is: ${domain}, checking it...${plain}"
SSL_ISSUED_DOMAIN="${domain}" SSL_ISSUED_DOMAIN="${domain}"
# detect existing certificate and reuse it if present
local cert_exists=0 local cert_exists=0
if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
cert_exists=1 cert_exists=1
@ -392,7 +380,6 @@ ssl_cert_issue() {
echo -e "${green}Your domain is ready for issuing certificates now...${plain}" echo -e "${green}Your domain is ready for issuing certificates now...${plain}"
fi fi
# create a directory for the certificate
certPath="/root/cert/${domain}" certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then if [ ! -d "$certPath" ]; then
mkdir -p "$certPath" mkdir -p "$certPath"
@ -401,7 +388,6 @@ ssl_cert_issue() {
mkdir -p "$certPath" mkdir -p "$certPath"
fi fi
# get the port number for the standalone server
local WebPort=80 local WebPort=80
read -rp "Please choose which port to use (default is 80): " WebPort read -rp "Please choose which port to use (default is 80): " WebPort
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
@ -410,12 +396,10 @@ ssl_cert_issue() {
fi fi
echo -e "${green}Will use port: ${WebPort} to issue certificates. Please make sure this port is open.${plain}" 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}" echo -e "${yellow}Stopping panel temporarily...${plain}"
systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null
if [[ ${cert_exists} -eq 0 ]]; then if [[ ${cert_exists} -eq 0 ]]; then
# issue the certificate
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -430,7 +414,6 @@ ssl_cert_issue() {
echo -e "${green}Using existing certificate, installing certificates...${plain}" echo -e "${green}Using existing certificate, installing certificates...${plain}"
fi fi
# Setup reload command
reloadCmd="systemctl restart x-ui || rc-service x-ui restart" 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}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}" echo -e "${green}This command will run on every certificate issue and renew.${plain}"
@ -456,7 +439,6 @@ ssl_cert_issue() {
esac esac
fi fi
# install the certificate
local installOutput="" local installOutput=""
installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \ installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \ --key-file /root/cert/${domain}/privkey.pem \
@ -480,26 +462,21 @@ ssl_cert_issue() {
return 1 return 1
fi fi
# enable auto-renew
~/.acme.sh/acme.sh --upgrade --auto-upgrade ~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}" echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}"
ls -lah /root/cert/${domain}/ ls -lah /root/cert/${domain}/
# Secure permissions: private key readable only by owner
chmod 600 $certPath/privkey.pem 2> /dev/null chmod 600 $certPath/privkey.pem 2> /dev/null
chmod 644 $certPath/fullchain.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null
else else
echo -e "${green}Auto renew succeeded, certificate details:${plain}" echo -e "${green}Auto renew succeeded, certificate details:${plain}"
ls -lah /root/cert/${domain}/ ls -lah /root/cert/${domain}/
# Secure permissions: private key readable only by owner
chmod 600 $certPath/privkey.pem 2> /dev/null chmod 600 $certPath/privkey.pem 2> /dev/null
chmod 644 $certPath/fullchain.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null
fi fi
# start panel
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null 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 read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
local webCertFile="/root/cert/${domain}/fullchain.pem" local webCertFile="/root/cert/${domain}/fullchain.pem"
@ -524,8 +501,6 @@ ssl_cert_issue() {
return 0 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() { prompt_and_setup_ssl() {
local panel_port="$1" local panel_port="$1"
local web_base_path="$2" 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} 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." 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 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 if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" && "$ssl_choice" != "4" ]]; then
ssl_choice="2" ssl_choice="2"
fi fi
case "$ssl_choice" in case "$ssl_choice" in
1) 1)
# User chose Let's Encrypt domain option
echo -e "${green}Using Let's Encrypt for domain certificate...${plain}" echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
if ssl_cert_issue; then if ssl_cert_issue; then
local cert_domain="${SSL_ISSUED_DOMAIN}" local cert_domain="${SSL_ISSUED_DOMAIN}"
@ -572,15 +545,12 @@ prompt_and_setup_ssl() {
fi fi
;; ;;
2) 2)
# User chose Let's Encrypt IP certificate option
echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}" echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"
# Ask for optional IPv6
local ipv6_addr="" local ipv6_addr=""
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " 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 if [[ $release == "alpine" ]]; then
rc-service x-ui stop > /dev/null 2>&1 rc-service x-ui stop > /dev/null 2>&1
else else
@ -597,20 +567,16 @@ prompt_and_setup_ssl() {
fi fi
;; ;;
3) 3)
# User chose Custom Paths (User Provided) option
echo -e "${green}Using custom existing certificate...${plain}" echo -e "${green}Using custom existing certificate...${plain}"
local custom_cert="" local custom_cert=""
local custom_key="" local custom_key=""
local custom_domain="" local custom_domain=""
# 3.1 Request Domain to compose Panel URL later
read -rp "Please enter domain name certificate issued for: " custom_domain 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 while true; do
read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert
# Strip quotes if present
custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'") custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'")
if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then
@ -624,10 +590,8 @@ prompt_and_setup_ssl() {
fi fi
done done
# 3.3 Loop for Private Key Path
while true; do while true; do
read -rp "Input private key path (keywords: .key / privatekey): " custom_key read -rp "Input private key path (keywords: .key / privatekey): " custom_key
# Strip quotes if present
custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'") custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'")
if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then
@ -641,10 +605,8 @@ prompt_and_setup_ssl() {
fi fi
done done
# 3.4 Apply Settings via x-ui binary
${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" > /dev/null 2>&1 ${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 if [[ -n "$custom_domain" ]]; then
SSL_HOST="$custom_domain" SSL_HOST="$custom_domain"
else 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_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_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}') 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 existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local URL_lists=( local URL_lists=(
"https://api4.ipify.org" "https://api4.ipify.org"
@ -763,7 +724,6 @@ config_after_install() {
prompt_and_setup_ssl "${config_port}" "${config_webBasePath}" "${server_ip}" prompt_and_setup_ssl "${config_port}" "${config_webBasePath}" "${server_ip}"
# Display final credentials and access information
echo "" echo ""
echo -e "${green}═══════════════════════════════════════════${plain}" echo -e "${green}═══════════════════════════════════════════${plain}"
echo -e "${green} Panel Installation Complete! ${plain}" echo -e "${green} Panel Installation Complete! ${plain}"
@ -786,7 +746,6 @@ config_after_install() {
${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}" ${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}"
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}" 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 if [[ -z "${existing_cert}" ]]; then
echo "" echo ""
echo -e "${green}═══════════════════════════════════════════${plain}" echo -e "${green}═══════════════════════════════════════════${plain}"
@ -797,7 +756,6 @@ config_after_install() {
prompt_and_setup_ssl "${existing_port}" "${config_webBasePath}" "${server_ip}" prompt_and_setup_ssl "${existing_port}" "${config_webBasePath}" "${server_ip}"
echo -e "${green}Access URL: ${SSL_SCHEME}://${SSL_HOST}:${existing_port}/${config_webBasePath}${plain}" echo -e "${green}Access URL: ${SSL_SCHEME}://${SSL_HOST}:${existing_port}/${config_webBasePath}${plain}"
else else
# If a cert already exists, just show the access URL
echo -e "${green}Access URL: https://${server_ip}:${existing_port}/${config_webBasePath}${plain}" echo -e "${green}Access URL: https://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
fi fi
fi fi
@ -817,8 +775,6 @@ config_after_install() {
echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}" echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}"
fi 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:]') existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [[ -z "$existing_cert" ]]; then if [[ -z "$existing_cert" ]]; then
echo "" echo ""
@ -840,19 +796,20 @@ config_after_install() {
install_x-ui() { install_x-ui() {
cd ${xui_folder%/x-ui}/ cd ${xui_folder%/x-ui}/
# Download resources local github_raw=$(get_github_raw)
if [ $# == 0 ]; then 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 if [[ ! -n "$tag_version" ]]; then
echo -e "${yellow}Trying to fetch version with IPv4...${plain}" 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 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}" echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}"
exit 1 exit 1
fi fi
fi fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." 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 if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
exit 1 exit 1
@ -867,7 +824,7 @@ install_x-ui() {
exit 1 exit 1
fi 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" echo -e "Beginning to install x-ui $1"
curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz ${url} curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
@ -875,13 +832,12 @@ install_x-ui() {
exit 1 exit 1
fi fi
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 if [[ $? -ne 0 ]]; then
echo -e "${red}Failed to download x-ui.sh${plain}" echo -e "${red}Failed to download x-ui.sh${plain}"
exit 1 exit 1
fi fi
# Stop x-ui service and remove old resources
if [[ -e ${xui_folder}/ ]]; then if [[ -e ${xui_folder}/ ]]; then
if [[ $release == "alpine" ]]; then if [[ $release == "alpine" ]]; then
rc-service x-ui stop rc-service x-ui stop
@ -891,7 +847,6 @@ install_x-ui() {
rm ${xui_folder}/ -rf rm ${xui_folder}/ -rf
fi fi
# Extract resources and set permissions
tar zxvf x-ui-linux-$(arch).tar.gz tar zxvf x-ui-linux-$(arch).tar.gz
rm x-ui-linux-$(arch).tar.gz -f rm x-ui-linux-$(arch).tar.gz -f
@ -899,20 +854,17 @@ install_x-ui() {
chmod +x x-ui chmod +x x-ui
chmod +x x-ui.sh chmod +x x-ui.sh
# Check the system's architecture and rename the file accordingly
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
mv bin/xray-linux-$(arch) bin/xray-linux-arm mv bin/xray-linux-$(arch) bin/xray-linux-arm
chmod +x bin/xray-linux-arm chmod +x bin/xray-linux-arm
fi fi
chmod +x x-ui bin/xray-linux-$(arch) 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 mv -f /usr/bin/x-ui-temp /usr/bin/x-ui
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
mkdir -p /var/log/x-ui mkdir -p /var/log/x-ui
config_after_install config_after_install
# Etckeeper compatibility
if [ -d "/etc/.git" ]; then if [ -d "/etc/.git" ]; then
if [ -f "/etc/.gitignore" ]; then if [ -f "/etc/.gitignore" ]; then
if ! grep -q "x-ui/x-ui.db" "/etc/.gitignore"; then if ! grep -q "x-ui/x-ui.db" "/etc/.gitignore"; then
@ -927,7 +879,7 @@ install_x-ui() {
fi fi
if [[ $release == "alpine" ]]; then 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 if [[ $? -ne 0 ]]; then
echo -e "${red}Failed to download x-ui.rc${plain}" echo -e "${red}Failed to download x-ui.rc${plain}"
exit 1 exit 1
@ -936,7 +888,6 @@ install_x-ui() {
rc-update add x-ui rc-update add x-ui
rc-service x-ui start rc-service x-ui start
else else
# Install systemd service file
service_installed=false service_installed=false
if [ -f "x-ui.service" ]; then if [ -f "x-ui.service" ]; then
@ -979,18 +930,17 @@ install_x-ui() {
esac esac
fi fi
# If service file not found in tar.gz, download from GitHub
if [ "$service_installed" = false ]; then if [ "$service_installed" = false ]; then
echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}" echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}"
case "${release}" in case "${release}" in
ubuntu | debian | armbian) 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) 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 esac

964
update.sh

File diff suppressed because it is too large Load diff

240
x-ui.sh
View file

@ -6,7 +6,9 @@ blue='\033[0;34m'
yellow='\033[0;33m' yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
#Add some basic function here GITHUB_MIRROR_DEFAULT="https://gh.kejilion.pro"
GITHUB_RAW_DEFAULT="https://raw.githubusercontent.com"
function LOGD() { function LOGD() {
echo -e "${yellow}[DEG] $* ${plain}" echo -e "${yellow}[DEG] $* ${plain}"
} }
@ -19,7 +21,33 @@ function LOGI() {
echo -e "${green}[INF] $* ${plain}" 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() { is_port_in_use() {
local port="$1" local port="$1"
if command -v ss > /dev/null 2>&1; then if command -v ss > /dev/null 2>&1; then
@ -36,7 +64,6 @@ is_port_in_use() {
return 1 return 1
} }
# Simple helpers for domain/IP validation
is_ipv4() { is_ipv4() {
[[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 [[ "$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 [[ "$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 [[ $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 if [[ -f /etc/os-release ]]; then
source /etc/os-release source /etc/os-release
release=$ID release=$ID
@ -69,7 +94,6 @@ echo "The OS release is: $release"
os_version="" os_version=""
os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.') 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_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}"
xui_service="${XUI_SERVICE:=/etc/systemd/system}" xui_service="${XUI_SERVICE:=/etc/systemd/system}"
log_folder="${XUI_LOG_FOLDER:=/var/log/x-ui}" 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_log_path="${log_folder}/3xipl.log"
iplimit_banned_log_path="${log_folder}/3xipl-banned.log" iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
REPO_OWNER="mhsanaei"
REPO_NAME="3x-ui"
confirm() { confirm() {
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
echo && read -rp "$1 [Default $2]: " temp echo && read -rp "$1 [Default $2]: " temp
@ -108,7 +135,8 @@ before_show_menu() {
} }
install() { 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
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
start start
@ -127,7 +155,8 @@ update() {
fi fi
return 0 return 0
fi 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 if [[ $? == 0 ]]; then
LOGI "Update is complete, Panel has automatically restarted " LOGI "Update is complete, Panel has automatically restarted "
before_show_menu before_show_menu
@ -145,7 +174,8 @@ update_menu() {
return 0 return 0
fi 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 ${xui_folder}/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
@ -166,16 +196,14 @@ legacy_version() {
echo "Panel version cannot be empty. Exiting." echo "Panel version cannot be empty. Exiting."
exit 1 exit 1
fi fi
# Use the entered panel version in the download link local install_command="bash <(curl -Ls ${github_raw}/${REPO_OWNER}/${REPO_NAME}/v$tag_version/install.sh) v$tag_version"
install_command="bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/v$tag_version/install.sh") v$tag_version"
echo "Downloading and installing panel version $tag_version..." echo "Downloading and installing panel version $tag_version..."
eval $install_command eval $install_command
} }
# Function to handle the deletion of the script file
delete_script() { delete_script() {
rm "$0" # Remove the script file itself rm "$0"
exit 1 exit 1
} }
@ -206,9 +234,9 @@ uninstall() {
echo "" echo ""
echo -e "Uninstalled Successfully.\n" echo -e "Uninstalled Successfully.\n"
echo "If you need to install this panel again, you can use below command:" 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 "" echo ""
# Trap the SIGTERM signal
trap delete_script SIGTERM trap delete_script SIGTERM
delete_script delete_script
} }
@ -259,7 +287,6 @@ reset_webbasepath() {
config_webBasePath=$(gen_random_string 18) 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 ${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}" echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}"
@ -340,7 +367,6 @@ check_config() {
ssl_cert_issue_for_ip ssl_cert_issue_for_ip
if [[ $? -eq 0 ]]; then if [[ $? -eq 0 ]]; then
echo -e "${green}Access URL: https://${server_ip}:${existing_port}${existing_webBasePath}${plain}" 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 start 0 > /dev/null 2>&1
else else
LOGE "IP certificate setup failed." LOGE "IP certificate setup failed."
@ -579,7 +605,6 @@ disable_bbr() {
rm /etc/sysctl.d/99-bbr-x-ui.conf rm /etc/sysctl.d/99-bbr-x-ui.conf
sysctl --system sysctl --system
else else
# Replace BBR with CUBIC configurations
if [ -f "/etc/sysctl.conf" ]; then 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.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 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 before_show_menu
fi fi
# Enable BBR
if [ -d "/etc/sysctl.d/" ]; then if [ -d "/etc/sysctl.d/" ]; then
{ {
echo "#$(sysctl -n net.core.default_qdisc):$(sysctl -n net.ipv4.tcp_congestion_control)" 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" echo "net.ipv4.tcp_congestion_control = bbr"
} > "/etc/sysctl.d/99-bbr-x-ui.conf" } > "/etc/sysctl.d/99-bbr-x-ui.conf"
if [ -f "/etc/sysctl.conf" ]; then 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.core.default_qdisc/# &/' /etc/sysctl.conf
sed -i 's/^net.ipv4.tcp_congestion_control/# &/' /etc/sysctl.conf sed -i 's/^net.ipv4.tcp_congestion_control/# &/' /etc/sysctl.conf
fi fi
@ -621,7 +644,6 @@ enable_bbr() {
sysctl -p sysctl -p
fi fi
# Verify that BBR is enabled
if [[ $(sysctl -n net.ipv4.tcp_congestion_control) == "bbr" ]]; then if [[ $(sysctl -n net.ipv4.tcp_congestion_control) == "bbr" ]]; then
echo -e "${green}BBR has been enabled successfully.${plain}" echo -e "${green}BBR has been enabled successfully.${plain}"
else else
@ -630,7 +652,9 @@ enable_bbr() {
} }
update_shell() { 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 if [[ $? != 0 ]]; then
echo "" echo ""
LOGE "Failed to download script, Please check whether the machine can connect Github" LOGE "Failed to download script, Please check whether the machine can connect Github"
@ -642,7 +666,6 @@ update_shell() {
fi fi
} }
# 0: running, 1: not running, 2: not installed
check_status() { check_status() {
if [[ $release == "alpine" ]]; then if [[ $release == "alpine" ]]; then
if [[ ! -f /etc/init.d/x-ui ]]; then if [[ ! -f /etc/init.d/x-ui ]]; then
@ -814,130 +837,103 @@ install_firewall() {
echo "ufw firewall is already installed" echo "ufw firewall is already installed"
fi fi
# Check if the firewall is inactive
if ufw status | grep -q "Status: active"; then if ufw status | grep -q "Status: active"; then
echo "Firewall is already active" echo "Firewall is already active"
else else
echo "Activating firewall..." echo "Activating firewall..."
# Open the necessary ports
ufw allow ssh ufw allow ssh
ufw allow http ufw allow http
ufw allow https ufw allow https
ufw allow 2053/tcp #webPort ufw allow 2053/tcp
ufw allow 2096/tcp #subport ufw allow 2096/tcp
# Enable the firewall
ufw --force enable ufw --force enable
fi fi
} }
open_ports() { 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 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 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 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 exit 1
fi fi
# Open the specified ports using ufw
IFS=',' read -ra PORT_LIST <<< "$ports" IFS=',' read -ra PORT_LIST <<< "$ports"
for port in "${PORT_LIST[@]}"; do for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then if [[ $port == *-* ]]; then
# Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) end_port=$(echo $port | cut -d'-' -f2)
# Open the port range
ufw allow $start_port:$end_port/tcp ufw allow $start_port:$end_port/tcp
ufw allow $start_port:$end_port/udp ufw allow $start_port:$end_port/udp
else else
# Open the single port
ufw allow "$port" ufw allow "$port"
fi fi
done done
# Confirm that the ports are opened
echo "Opened the specified ports:" echo "Opened the specified ports:"
for port in "${PORT_LIST[@]}"; do for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then if [[ $port == *-* ]]; then
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) 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" (ufw status | grep -q "$start_port:$end_port") && echo "$start_port-$end_port"
else else
# Check if the individual port has been successfully opened
(ufw status | grep -q "$port") && echo "$port" (ufw status | grep -q "$port") && echo "$port"
fi fi
done done
} }
delete_ports() { delete_ports() {
# Display current rules with numbers
echo "Current UFW rules:" echo "Current UFW rules:"
ufw status numbered ufw status numbered
# Ask the user how they want to delete rules
echo "Do you want to delete rules by:" echo "Do you want to delete rules by:"
echo "1) Rule numbers" echo "1) Rule numbers"
echo "2) Ports" echo "2) Ports"
read -rp "Enter your choice (1 or 2): " choice read -rp "Enter your choice (1 or 2): " choice
if [[ $choice -eq 1 ]]; then if [[ $choice -eq 1 ]]; then
# Deleting by rule numbers
read -rp "Enter the rule numbers you want to delete (1, 2, etc.): " 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 if ! [[ $rule_numbers =~ ^([0-9]+)(,[0-9]+)*$ ]]; then
echo "Error: Invalid input. Please enter a comma-separated list of rule numbers." >&2 echo "Error: Invalid input. Please enter a comma-separated list of rule numbers." >&2
exit 1 exit 1
fi fi
# Split numbers into an array
IFS=',' read -ra RULE_NUMBERS <<< "$rule_numbers" IFS=',' read -ra RULE_NUMBERS <<< "$rule_numbers"
for rule_number in "${RULE_NUMBERS[@]}"; do for rule_number in "${RULE_NUMBERS[@]}"; do
# Delete the rule by number
ufw delete "$rule_number" || echo "Failed to delete rule number $rule_number" ufw delete "$rule_number" || echo "Failed to delete rule number $rule_number"
done done
echo "Selected rules have been deleted." echo "Selected rules have been deleted."
elif [[ $choice -eq 2 ]]; then 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 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 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 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 exit 1
fi fi
# Split ports into an array
IFS=',' read -ra PORT_LIST <<< "$ports" IFS=',' read -ra PORT_LIST <<< "$ports"
for port in "${PORT_LIST[@]}"; do for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then if [[ $port == *-* ]]; then
# Split the port range
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) 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/tcp
ufw delete allow $start_port:$end_port/udp ufw delete allow $start_port:$end_port/udp
else else
# Delete a single port
ufw delete allow "$port" ufw delete allow "$port"
fi fi
done done
# Confirmation of deletion
echo "Deleted the specified ports:" echo "Deleted the specified ports:"
for port in "${PORT_LIST[@]}"; do for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then if [[ $port == *-* ]]; then
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) 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" (ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port"
else else
# Check if the individual port has been deleted
(ufw status | grep -q "$port") || echo "$port" (ufw status | grep -q "$port") || echo "$port"
fi fi
done done
@ -969,7 +965,6 @@ update_geofiles() {
;; ;;
esac esac
for dat in "${dat_files[@]}"; do for dat in "${dat_files[@]}"; do
# Remove suffix for remote filename (e.g., geoip_IR -> geoip)
remote_file="${dat%%_*}" remote_file="${dat%%_*}"
curl -fLRo ${xui_folder}/bin/${dat}.dat -z ${xui_folder}/bin/${dat}.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 https://github.com/${dat_source}/releases/latest/download/${remote_file}.dat
@ -1018,14 +1013,13 @@ update_geo() {
} }
install_acme() { install_acme() {
# Check if acme.sh is already installed
if command -v ~/.acme.sh/acme.sh &> /dev/null; then if command -v ~/.acme.sh/acme.sh &> /dev/null; then
LOGI "acme.sh is already installed." LOGI "acme.sh is already installed."
return 0 return 0
fi fi
LOGI "Installing acme.sh..." 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 curl -s https://get.acme.sh | sh
if [ $? -ne 0 ]; then 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_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}') local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
# Get server IP
local URL_lists=( local URL_lists=(
"https://api4.ipify.org" "https://api4.ipify.org"
"https://ipv4.icanhazip.com" "https://ipv4.icanhazip.com"
@ -1198,12 +1191,10 @@ ssl_cert_issue_for_ip() {
LOGI "Server IP detected: ${server_ip}" LOGI "Server IP detected: ${server_ip}"
# Ask for optional IPv6
local ipv6_addr="" local ipv6_addr=""
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " 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 if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
LOGI "acme.sh not found, installing..." LOGI "acme.sh not found, installing..."
install_acme install_acme
@ -1213,7 +1204,6 @@ ssl_cert_issue_for_ip() {
fi fi
fi fi
# install socat
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update > /dev/null 2>&1 && apt-get install socat -y > /dev/null 2>&1 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 apk add socat curl openssl > /dev/null 2>&1
;; ;;
*) *)
LOGW "Unsupported OS for automatic socat installation"
;; ;;
esac esac
# Create certificate directory
certPath="/root/cert/ip" certPath="/root/cert/ip"
mkdir -p "$certPath" mkdir -p "$certPath"
# Build domain arguments
local domain_args="-d ${server_ip}" local domain_args="-d ${server_ip}"
if [[ -n "$ipv6_addr" ]] && is_ipv6 "$ipv6_addr"; then if [[ -n "$ipv6_addr" ]] && is_ipv6 "$ipv6_addr"; then
domain_args="${domain_args} -d ${ipv6_addr}" domain_args="${domain_args} -d ${ipv6_addr}"
LOGI "Including IPv6 address: ${ipv6_addr}" LOGI "Including IPv6 address: ${ipv6_addr}"
fi fi
# Choose port for HTTP-01 listener (default 80, allow override)
local WebPort="" local WebPort=""
read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
WebPort="${WebPort:-80}" WebPort="${WebPort:-80}"
@ -1289,10 +1275,8 @@ ssl_cert_issue_for_ip() {
fi fi
done done
# Reload command - restarts panel after renewal
local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null" 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 --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue \ ~/.acme.sh/acme.sh --issue \
${domain_args} \ ${domain_args} \
@ -1306,7 +1290,6 @@ ssl_cert_issue_for_ip() {
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Failed to issue certificate for IP: ${server_ip}" LOGE "Failed to issue certificate for IP: ${server_ip}"
LOGE "Make sure port ${WebPort} is open and the server is accessible from the internet" 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 rm -rf ~/.acme.sh/${server_ip} 2> /dev/null
[[ -n "$ipv6_addr" ]] && rm -rf ~/.acme.sh/${ipv6_addr} 2> /dev/null [[ -n "$ipv6_addr" ]] && rm -rf ~/.acme.sh/${ipv6_addr} 2> /dev/null
rm -rf ${certPath} 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}" LOGI "Certificate issued successfully for IP: ${server_ip}"
fi 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} \ ~/.acme.sh/acme.sh --installcert -d ${server_ip} \
--key-file "${certPath}/privkey.pem" \ --key-file "${certPath}/privkey.pem" \
--fullchain-file "${certPath}/fullchain.pem" \ --fullchain-file "${certPath}/fullchain.pem" \
--reloadcmd "${reloadCmd}" 2>&1 || true --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 if [[ ! -f "${certPath}/fullchain.pem" || ! -f "${certPath}/privkey.pem" ]]; then
LOGE "Certificate files not found after installation" 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 rm -rf ~/.acme.sh/${server_ip} 2> /dev/null
[[ -n "$ipv6_addr" ]] && rm -rf ~/.acme.sh/${ipv6_addr} 2> /dev/null [[ -n "$ipv6_addr" ]] && rm -rf ~/.acme.sh/${ipv6_addr} 2> /dev/null
rm -rf ${certPath} 2> /dev/null rm -rf ${certPath} 2> /dev/null
@ -1335,12 +1313,10 @@ ssl_cert_issue_for_ip() {
LOGI "Certificate files installed successfully" LOGI "Certificate files installed successfully"
# enable auto-renew
~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1 ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
chmod 600 $certPath/privkey.pem 2> /dev/null chmod 600 $certPath/privkey.pem 2> /dev/null
chmod 644 $certPath/fullchain.pem 2> /dev/null chmod 644 $certPath/fullchain.pem 2> /dev/null
# Set certificate paths for the panel
local webCertFile="${certPath}/fullchain.pem" local webCertFile="${certPath}/fullchain.pem"
local webKeyFile="${certPath}/privkey.pem" local webKeyFile="${certPath}/privkey.pem"
@ -1363,7 +1339,6 @@ ssl_cert_issue_for_ip() {
ssl_cert_issue() { ssl_cert_issue() {
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') 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}') 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 if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
echo "acme.sh could not be found. we will install it" echo "acme.sh could not be found. we will install it"
install_acme install_acme
@ -1373,7 +1348,6 @@ ssl_cert_issue() {
fi fi
fi fi
# install socat
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update > /dev/null 2>&1 && apt-get install socat -y > /dev/null 2>&1 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 apk add socat curl openssl > /dev/null 2>&1
;; ;;
*) *)
LOGW "Unsupported OS for automatic socat installation"
;; ;;
esac esac
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -1408,11 +1381,10 @@ ssl_cert_issue() {
LOGI "install socat succeed..." LOGI "install socat succeed..."
fi fi
# get the domain here, and we need to verify it
local domain="" local domain=""
while true; do while true; do
read -rp "Please enter your domain name: " domain read -rp "Please enter your domain name: " domain
domain="${domain// /}" # Trim whitespace domain="${domain// /}"
if [[ -z "$domain" ]]; then if [[ -z "$domain" ]]; then
LOGE "Domain name cannot be empty. Please try again." LOGE "Domain name cannot be empty. Please try again."
@ -1429,7 +1401,6 @@ ssl_cert_issue() {
LOGD "Your domain is: ${domain}, checking it..." LOGD "Your domain is: ${domain}, checking it..."
SSL_ISSUED_DOMAIN="${domain}" SSL_ISSUED_DOMAIN="${domain}"
# detect existing certificate and reuse it if present
local cert_exists=0 local cert_exists=0
if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
cert_exists=1 cert_exists=1
@ -1440,7 +1411,6 @@ ssl_cert_issue() {
LOGI "Your domain is ready for issuing certificates now..." LOGI "Your domain is ready for issuing certificates now..."
fi fi
# create a directory for the certificate
certPath="/root/cert/${domain}" certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then if [ ! -d "$certPath" ]; then
mkdir -p "$certPath" mkdir -p "$certPath"
@ -1449,7 +1419,6 @@ ssl_cert_issue() {
mkdir -p "$certPath" mkdir -p "$certPath"
fi fi
# get the port number for the standalone server
local WebPort=80 local WebPort=80
read -rp "Please choose which port to use (default is 80): " WebPort read -rp "Please choose which port to use (default is 80): " WebPort
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then 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." LOGI "Will use port: ${WebPort} to issue certificates. Please make sure this port is open."
if [[ ${cert_exists} -eq 0 ]]; then if [[ ${cert_exists} -eq 0 ]]; then
# issue the certificate
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -1499,7 +1467,6 @@ ssl_cert_issue() {
esac esac
fi fi
# install the certificate
local installOutput="" local installOutput=""
installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \ installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \ --key-file /root/cert/${domain}/privkey.pem \
@ -1522,7 +1489,6 @@ ssl_cert_issue() {
exit 1 exit 1
fi fi
# enable auto-renew
~/.acme.sh/acme.sh --upgrade --auto-upgrade ~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Auto renew failed, certificate details:" LOGE "Auto renew failed, certificate details:"
@ -1537,7 +1503,6 @@ ssl_cert_issue() {
chmod 644 $certPath/fullchain.pem chmod 644 $certPath/fullchain.pem
fi 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 read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
local webCertFile="/root/cert/${domain}/fullchain.pem" 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" confirm "Do you confirm the information and wish to proceed? [y/n]" "y"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# Check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
echo "acme.sh could not be found. We will install it." echo "acme.sh could not be found. We will install it."
install_acme install_acme
@ -1588,7 +1552,6 @@ ssl_cert_issue_CF() {
read -rp "Input your domain here: " CF_Domain read -rp "Input your domain here: " CF_Domain
LOGD "Your domain name is set to: ${CF_Domain}" LOGD "Your domain name is set to: ${CF_Domain}"
# Set up Cloudflare API details
CF_GlobalKey="" CF_GlobalKey=""
CF_AccountEmail="" CF_AccountEmail=""
LOGD "Please set the API key:" LOGD "Please set the API key:"
@ -1599,7 +1562,6 @@ ssl_cert_issue_CF() {
read -rp "Input your email here: " CF_AccountEmail read -rp "Input your email here: " CF_AccountEmail
LOGD "Your registered email address is: ${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 ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Default CA, Let'sEncrypt fail, script exiting..." LOGE "Default CA, Let'sEncrypt fail, script exiting..."
@ -1609,7 +1571,6 @@ ssl_cert_issue_CF() {
export CF_Key="${CF_GlobalKey}" export CF_Key="${CF_GlobalKey}"
export CF_Email="${CF_AccountEmail}" 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 ~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log --force
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Certificate issuance failed, script exiting..." LOGE "Certificate issuance failed, script exiting..."
@ -1618,7 +1579,6 @@ ssl_cert_issue_CF() {
LOGI "Certificate issued successfully, Installing..." LOGI "Certificate issued successfully, Installing..."
fi fi
# Install the certificate
certPath="/root/cert/${CF_Domain}" certPath="/root/cert/${CF_Domain}"
if [ -d "$certPath" ]; then if [ -d "$certPath" ]; then
rm -rf ${certPath} rm -rf ${certPath}
@ -1666,7 +1626,6 @@ ssl_cert_issue_CF() {
LOGI "Certificate installed successfully, Turning on automatic updates..." LOGI "Certificate installed successfully, Turning on automatic updates..."
fi fi
# Enable auto-update
~/.acme.sh/acme.sh --upgrade --auto-upgrade ~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Auto update setup failed, script exiting..." LOGE "Auto update setup failed, script exiting..."
@ -1678,7 +1637,6 @@ ssl_cert_issue_CF() {
chmod 644 ${certPath}/fullchain.pem chmod 644 ${certPath}/fullchain.pem
fi 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 read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
local webCertFile="${certPath}/fullchain.pem" local webCertFile="${certPath}/fullchain.pem"
@ -1703,15 +1661,11 @@ ssl_cert_issue_CF() {
} }
run_speedtest() { run_speedtest() {
# Check if Speedtest is already installed
if ! command -v speedtest &> /dev/null; then if ! command -v speedtest &> /dev/null; then
# If not installed, determine installation method
if command -v snap &> /dev/null; then if command -v snap &> /dev/null; then
# Use snap to install Speedtest
echo "Installing Speedtest using snap..." echo "Installing Speedtest using snap..."
snap install speedtest snap install speedtest
else else
# Fallback to using package managers
local pkg_manager="" local pkg_manager=""
local speedtest_install_script="" local speedtest_install_script=""
@ -1745,7 +1699,7 @@ run_speedtest() {
ip_validation() { 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]))$" 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() { iplimit_main() {
@ -1856,14 +1810,6 @@ install_iplimit() {
if ! command -v fail2ban-client &> /dev/null; then if ! command -v fail2ban-client &> /dev/null; then
echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" 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 case "${release}" in
ubuntu) ubuntu)
apt-get update apt-get update
@ -1918,24 +1864,18 @@ install_iplimit() {
echo -e "${green}Configuring IP Limit...${plain}\n" echo -e "${green}Configuring IP Limit...${plain}\n"
# make sure there's no conflict for jail files
iplimit_remove_conflicts iplimit_remove_conflicts
# Check if log file exists
if ! test -f "${iplimit_banned_log_path}"; then if ! test -f "${iplimit_banned_log_path}"; then
touch ${iplimit_banned_log_path} touch ${iplimit_banned_log_path}
fi fi
# Check if service log file exists so fail2ban won't return error
if ! test -f "${iplimit_log_path}"; then if ! test -f "${iplimit_log_path}"; then
touch ${iplimit_log_path} touch ${iplimit_log_path}
fi fi
# Create the iplimit jail files
# we didn't pass the bantime here to use the default value
create_iplimit_jails create_iplimit_jails
# Launching fail2ban
if [[ $release == "alpine" ]]; then if [[ $release == "alpine" ]]; then
if [[ $(rc-service fail2ban status | grep -F 'status: started' -c) == 0 ]]; then if [[ $(rc-service fail2ban status | grep -F 'status: started' -c) == 0 ]]; then
rc-service fail2ban start rc-service fail2ban start
@ -2063,13 +2003,10 @@ show_banlog() {
} }
create_iplimit_jails() { create_iplimit_jails() {
# Use default bantime if not passed => 30 minutes
local bantime="${1:-30}" local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/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 if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
fi fi
@ -2130,7 +2067,6 @@ iplimit_remove_conflicts() {
) )
for file in "${jail_files[@]}"; do 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 if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
sed -i "/\[3x-ipl\]/,/^$/d" ${file} sed -i "/\[3x-ipl\]/,/^$/d" ${file}
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n" echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
@ -2270,42 +2206,42 @@ show_usage() {
show_menu() { show_menu() {
echo -e " echo -e "
──────────────────────────────────────────────── ════════════════════════════════════════════════════
${green}3X-UI Panel Management Script${plain} ${green}3X-UI Panel Management Script${plain}
${green}0.${plain} Exit Script ${green}0.${plain} Exit Script
──────────────────────────────────────────────── ║──────────────────────────────────────────────────
${green}1.${plain} Install ${green}1.${plain} Install
${green}2.${plain} Update ${green}2.${plain} Update
${green}3.${plain} Update Menu ${green}3.${plain} Update Menu
${green}4.${plain} Legacy Version ${green}4.${plain} Legacy Version
${green}5.${plain} Uninstall ${green}5.${plain} Uninstall
──────────────────────────────────────────────── ║──────────────────────────────────────────────────
${green}6.${plain} Reset Username & Password ${green}6.${plain} Reset Username & Password
${green}7.${plain} Reset Web Base Path ${green}7.${plain} Reset Web Base Path
${green}8.${plain} Reset Settings ${green}8.${plain} Reset Settings
${green}9.${plain} Change Port ${green}9.${plain} Change Port
${green}10.${plain} View Current Settings ${green}10.${plain} View Current Settings
──────────────────────────────────────────────── ║──────────────────────────────────────────────────
${green}11.${plain} Start ${green}11.${plain} Start
${green}12.${plain} Stop ${green}12.${plain} Stop
${green}13.${plain} Restart ${green}13.${plain} Restart
| ${green}14.${plain} Restart Xray ${green}14.${plain} Restart Xray
${green}15.${plain} Check Status ${green}15.${plain} Check Status
${green}16.${plain} Logs Management ${green}16.${plain} Logs Management
──────────────────────────────────────────────── ║──────────────────────────────────────────────────
${green}17.${plain} Enable Autostart ${green}17.${plain} Enable Autostart
${green}18.${plain} Disable Autostart ${green}18.${plain} Disable Autostart
──────────────────────────────────────────────── ║──────────────────────────────────────────────────
${green}19.${plain} SSL Certificate Management ${green}19.${plain} SSL Certificate Management
${green}20.${plain} Cloudflare SSL Certificate ${green}20.${plain} Cloudflare SSL Certificate
${green}21.${plain} IP Limit Management ${green}21.${plain} IP Limit Management
${green}22.${plain} Firewall Management ${green}22.${plain} Firewall Management
${green}23.${plain} SSH Port Forwarding Management ${green}23.${plain} SSH Port Forwarding Management
──────────────────────────────────────────────── ║──────────────────────────────────────────────────
${green}24.${plain} Enable BBR ${green}24.${plain} Enable BBR
${green}25.${plain} Update Geo Files ${green}25.${plain} Update Geo Files
${green}26.${plain} Speedtest by Ookla ${green}26.${plain} Speedtest by Ookla
──────────────────────────────────────────────── ════════════════════════════════════════════════════
" "
show_status show_status
echo && read -rp "Please enter your selection [0-26]: " num echo && read -rp "Please enter your selection [0-26]: " num