Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7abb092211 | ||
|
|
937bfb4c78 | ||
|
|
1bcdc54b68 | ||
|
|
eb58314c53 | ||
|
|
c158e6ec73 | ||
|
|
5ae587ee81 | ||
|
|
d40fa46851 | ||
|
|
19a31686da | ||
|
|
13f7e07128 | ||
|
|
0e3691fdbd | ||
|
|
e359b5c75e | ||
|
|
3b3bd3dea4 | ||
|
|
8f36b7ea84 | ||
|
|
569d99512c | ||
|
|
ac84553a68 | ||
|
|
610db7827d | ||
|
|
088b55c9ed | ||
|
|
c800e29900 | ||
|
|
14435db0d8 | ||
|
|
bd6402562e | ||
|
|
d16ad11136 | ||
|
|
6c27e4177d | ||
|
|
bebf83f06c | ||
|
|
07bf741b15 | ||
|
|
5e5851029d |
67
.github/workflows/docker.yml
vendored
67
.github/workflows/docker.yml
vendored
@@ -1,5 +1,4 @@
|
||||
name: Release 3X-UI for Docker
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -7,36 +6,50 @@ on:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
build_and_push:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
hsanaeii/3x-ui
|
||||
ghcr.io/mhsanaei/3x-ui
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=pep440,pattern={{version}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.10.16/"
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.10.31/"
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
|
||||
@@ -27,7 +27,7 @@ case $1 in
|
||||
esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v24.10.16/Xray-linux-${ARCH}.zip"
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v24.10.31/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
|
||||
@@ -30,38 +30,62 @@
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## Instalar una Versión Personalizada (no recomendamos)
|
||||
## Instalar versión antigua (no recomendamos)
|
||||
|
||||
Para instalar la versión deseada, utiliza el siguiente comando de instalación. Por ejemplo, ver `v1.7.9`:
|
||||
|
||||
```
|
||||
VERSION=v1.7.9 bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/refs/tags/$VERSION/install.sh") $VERSION
|
||||
VERSION=v1.7.9 && <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
|
||||
```
|
||||
|
||||
## Certificado SSL
|
||||
|
||||
<details>
|
||||
<summary>Haz clic para el Certificado SSL</summary>
|
||||
<summary>Haga clic para ver los detalles del certificado SSL</summary>
|
||||
|
||||
### Cloudflare
|
||||
### ACME
|
||||
|
||||
El script de gestión tiene una aplicación de certificado SSL incorporada para Cloudflare. Para usar este script para colocar un certificado, necesitas lo siguiente:
|
||||
Para gestionar certificados SSL utilizando ACME:
|
||||
|
||||
- Correo electrónico registrado en Cloudflare
|
||||
- Clave Global de API de Cloudflare
|
||||
- El nombre de dominio se ha resuelto en el servidor actual a través de Cloudflare
|
||||
|
||||
**1:** Ejecuta el comando`x-ui`en la terminal, luego elige `Certificado SSL de Cloudflare`.
|
||||
1. Asegúrate de que tu dominio esté correctamente resuelto al servidor.
|
||||
2. Ejecuta el comando `x-ui` en la terminal y elige `Gestión de Certificados SSL`.
|
||||
3. Se te presentarán las siguientes opciones:
|
||||
|
||||
- **Get SSL:** Obtener certificados SSL.
|
||||
- **Revoke:** Revocar certificados SSL existentes.
|
||||
- **Force Renew:** Forzar la renovación de certificados SSL.
|
||||
- **Show Existing Domains:** Mostrar todos los certificados de dominio disponibles en el servidor.
|
||||
- **Set Certificate Paths for the Panel:** Especificar el certificado para tu dominio que será utilizado por el panel.
|
||||
|
||||
### Certbot
|
||||
```
|
||||
|
||||
Para instalar y usar Certbot:
|
||||
|
||||
```sh
|
||||
apt-get install certbot -y
|
||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
***Consejo:*** *Certbot también está integrado en el script de gestión. Puedes ejecutar el comando `x-ui` , luego elegir `Gestión de Certificados SSL`.*
|
||||
### Cloudflare
|
||||
|
||||
El script de gestión incluye una aplicación de certificado SSL integrada para Cloudflare. Para usar este script para solicitar un certificado, necesitas lo siguiente:
|
||||
|
||||
- Correo electrónico registrado en Cloudflare
|
||||
- Clave API Global de Cloudflare
|
||||
- El nombre de dominio debe estar resuelto al servidor actual a través de Cloudflare
|
||||
|
||||
**Cómo obtener la Clave API Global de Cloudflare:**
|
||||
|
||||
1. Ejecuta el comando `x-ui` en la terminal y elige `Certificado SSL de Cloudflare`.
|
||||
2. Visita el enlace: [Tokens de API de Cloudflare](https://dash.cloudflare.com/profile/api-tokens).
|
||||
3. Haz clic en "Ver Clave API Global" (consulta la captura de pantalla a continuación):
|
||||

|
||||
4. Es posible que necesites volver a autenticar tu cuenta. Después de eso, se mostrará la Clave API (consulta la captura de pantalla a continuación):
|
||||

|
||||
|
||||
Al utilizarlo, simplemente ingresa tu `nombre de dominio`, `correo electrónico` y `CLAVE API`. El diagrama es el siguiente:
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## Install Custom Version (we don't recommend)
|
||||
## Install legacy Version (we don't recommend)
|
||||
|
||||
To install your desired version, use following installation command. e.g., ver `v1.7.9`:
|
||||
|
||||
```
|
||||
VERSION=v1.7.9 bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/refs/tags/$VERSION/install.sh") $VERSION
|
||||
VERSION=v1.7.9 && bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
|
||||
```
|
||||
|
||||
## SSL Certificate
|
||||
@@ -54,6 +54,8 @@ To manage SSL certificates using ACME:
|
||||
- **Get SSL:** Obtain SSL certificates.
|
||||
- **Revoke:** Revoke existing SSL certificates.
|
||||
- **Force Renew:** Force renewal of SSL certificates.
|
||||
- **Show Existing Domains:** Display all domain certificates available on the server.
|
||||
- **Set Certificate Paths for the Panel:** Specify the certificate for your domain to be used by the panel.
|
||||
|
||||
### Certbot
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## Установка определённой версии (мы не рекомендуем)
|
||||
## Установить старую версию (мы не рекомендуем)
|
||||
|
||||
Чтобы установить желаемую версию, используйте следующую команду установки. Например, ver `v1.7.9`:
|
||||
|
||||
```
|
||||
VERSION=v1.7.9 bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/refs/tags/$VERSION/install.sh") $VERSION
|
||||
VERSION=v1.7.9 && <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
|
||||
```
|
||||
|
||||
## SSL Сертификат
|
||||
@@ -54,6 +54,8 @@ VERSION=v1.7.9 bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui
|
||||
- **Get SSL:** Получить SSL сертификаты.
|
||||
- **Revoke:** Отозвать существующие SSL сертификаты.
|
||||
- **Force Renew:** Принудительно перевыпустить SSL сертификаты.
|
||||
- **Show Existing Domains:** Отобразить все сертификаты доменов, доступные на сервере.
|
||||
- **Set Certificate Paths for the Panel:** Укажите сертификат для вашего домена, который будет использоваться панелью.
|
||||
|
||||
### Certbot
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## 安装指定版本 (我们不建议)
|
||||
## 安装旧版本 (我们不建议)
|
||||
|
||||
要安装您想要的版本,请使用以下安装命令。例如,ver `v1.7.9`:
|
||||
|
||||
```
|
||||
VERSION=v1.7.9 bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/refs/tags/$VERSION/install.sh") $VERSION
|
||||
VERSION=v1.7.9 && <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
|
||||
```
|
||||
|
||||
### SSL证书
|
||||
@@ -51,9 +51,11 @@ VERSION=v1.7.9 bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui
|
||||
2. 在终端中运行 `x-ui` 命令,然后选择 `SSL证书管理`。
|
||||
3. 您将看到以下选项:
|
||||
|
||||
- **获取SSL证书:** 获取SSL证书。
|
||||
- **吊销:** 吊销现有的SSL证书。
|
||||
- **强制更新:** 强制更新SSL证书。
|
||||
- **Get SSL:** 获取SSL证书。
|
||||
- **Revoke:** 吊销现有的SSL证书。
|
||||
- **Force Renew:** 强制更新SSL证书。
|
||||
- **Show Existing Domains:** 显示服务器上所有可用的域证书。
|
||||
- **Set Certificate Paths for the Panel:** 指定用于面板的域证书。
|
||||
|
||||
### Certbot
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.4.5
|
||||
2.4.6
|
||||
16
go.mod
16
go.mod
@@ -13,8 +13,8 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.24.9
|
||||
github.com/valyala/fasthttp v1.56.0
|
||||
github.com/xtls/xray-core v1.8.25-0.20241005021528-c30f5d47964b
|
||||
github.com/valyala/fasthttp v1.57.0
|
||||
github.com/xtls/xray-core v1.8.25-0.20241031075831-4ec5c78c3453
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.19.0
|
||||
google.golang.org/grpc v1.67.1
|
||||
@@ -25,12 +25,12 @@ require (
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/ebitengine/purego v0.8.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/fasthttp/router v1.5.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
@@ -40,7 +40,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect
|
||||
github.com/google/pprof v0.0.0-20241023014458-598669927662 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
@@ -62,7 +62,7 @@ require (
|
||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.48.0 // indirect
|
||||
github.com/quic-go/quic-go v0.48.1 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
@@ -81,7 +81,7 @@ require (
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
@@ -94,7 +94,7 @@ require (
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
|
||||
|
||||
32
go.sum
32
go.sum
@@ -7,8 +7,8 @@ github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOL
|
||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
@@ -22,8 +22,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
|
||||
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/fasthttp/router v1.5.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
|
||||
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
@@ -64,8 +64,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241009165004-a3522334989c h1:NDovD0SMpBYXlE1zJmS1q55vWB/fUQBcPAqAboZSccA=
|
||||
github.com/google/pprof v0.0.0-20241009165004-a3522334989c/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA=
|
||||
github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
@@ -129,8 +129,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.48.0 h1:2TCyvBrMu1Z25rvIAlnp2dPT4lgh/uTqLqiXVpp5AeU=
|
||||
github.com/quic-go/quic-go v0.48.0/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
|
||||
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
@@ -172,8 +172,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
|
||||
github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
|
||||
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
|
||||
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
@@ -182,16 +182,16 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
||||
github.com/xtls/xray-core v1.8.25-0.20241005021528-c30f5d47964b h1:bWuePNnzV4ptnSYJkY96dAg3WgjYbfVgGnasLe3++9w=
|
||||
github.com/xtls/xray-core v1.8.25-0.20241005021528-c30f5d47964b/go.mod h1:YSvBScSqyzAocGDvzHBbEeoHNrFy8nV6gityRVDvHaM=
|
||||
github.com/xtls/xray-core v1.8.25-0.20241031075831-4ec5c78c3453 h1:2piT7IYX0SKhYjD+XiJMDZAbY01MkP1HYO54njmisaQ=
|
||||
github.com/xtls/xray-core v1.8.25-0.20241031075831-4ec5c78c3453/go.mod h1:OlJhs59caMUabGbOamwTc2khBSOfd34qtVJVXFhpfWM=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||
@@ -225,8 +225,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
|
||||
90
install.sh
90
install.sh
@@ -49,6 +49,8 @@ elif [[ "${release}" == "manjaro" ]]; then
|
||||
echo "Your OS is Manjaro"
|
||||
elif [[ "${release}" == "armbian" ]]; then
|
||||
echo "Your OS is Armbian"
|
||||
elif [[ "${release}" == "alpine" ]]; then
|
||||
echo "Your OS is Alpine Linux"
|
||||
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
|
||||
echo "Your OS is OpenSUSE Tumbleweed"
|
||||
elif [[ "${release}" == "openEuler" ]]; then
|
||||
@@ -137,69 +139,59 @@ gen_random_string() {
|
||||
}
|
||||
|
||||
config_after_install() {
|
||||
|
||||
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}')
|
||||
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
|
||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||
local server_ip=$(curl -s https://api.ipify.org)
|
||||
|
||||
# Check if username and password exist
|
||||
if [[ -n "$existing_username" && -n "$existing_password" ]]; then
|
||||
# If webBasePath is missing, generate a new one
|
||||
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
||||
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
||||
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||
local config_webBasePath=$(gen_random_string 15)
|
||||
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
||||
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
|
||||
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
|
||||
else
|
||||
echo -e "${green}Username, Password, and WebBasePath are already set. Exiting...${plain}"
|
||||
fi
|
||||
/usr/local/x-ui/x-ui migrate
|
||||
return 0
|
||||
fi
|
||||
local config_username=$(gen_random_string 10)
|
||||
local config_password=$(gen_random_string 10)
|
||||
|
||||
read -p "Would you like to customize the Panel Port settings? (If not, random settings will be applied) [y/n]: " config_confirm
|
||||
read -p "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
read -p "Please set up the panel port: " config_port
|
||||
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
||||
else
|
||||
local config_port=$(shuf -i 1024-62000 -n 1)
|
||||
echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
||||
fi
|
||||
|
||||
local config_webBasePath=$(gen_random_string 15)
|
||||
local config_username=$(gen_random_string 10)
|
||||
local config_password=$(gen_random_string 10)
|
||||
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
read -p "Please set up the panel port: " config_port
|
||||
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
||||
|
||||
echo -e "${yellow}Your Username will be generated randomly: ${config_username}${plain}"
|
||||
echo -e "${yellow}Your Password will be generated randomly: ${config_password}${plain}"
|
||||
echo -e "${yellow}Your Web Base Path will be generated randomly: ${config_webBasePath}${plain}"
|
||||
|
||||
echo -e "${yellow}Initializing, please wait...${plain}"
|
||||
|
||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
|
||||
echo -e "${green}Settings applied successfully!${plain}"
|
||||
|
||||
echo -e "###############################################"
|
||||
echo -e "${green}Username: ${config_username}${plain}"
|
||||
echo -e "${green}Password: ${config_password}${plain}"
|
||||
echo -e "${green}Port: ${config_port}${plain}"
|
||||
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
|
||||
echo -e "###############################################"
|
||||
else
|
||||
echo -e "${red}Cancel...${plain}"
|
||||
|
||||
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
|
||||
local portTemp=$(shuf -i 1024-62000 -n 1)
|
||||
|
||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${portTemp}" -webBasePath "${config_webBasePath}"
|
||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
|
||||
echo -e "This is a fresh installation, generating random login info for security concerns:"
|
||||
echo -e "###############################################"
|
||||
echo -e "${green}Username: ${config_username}${plain}"
|
||||
echo -e "${green}Password: ${config_password}${plain}"
|
||||
echo -e "${green}Port: ${portTemp}${plain}"
|
||||
echo -e "${green}Port: ${config_port}${plain}"
|
||||
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
|
||||
echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}"
|
||||
echo -e "###############################################"
|
||||
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check after installation${plain}"
|
||||
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
||||
else
|
||||
echo -e "${yellow}This is your upgrade, keeping old settings. If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
||||
local config_webBasePath=$(gen_random_string 15)
|
||||
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
||||
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
|
||||
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
|
||||
echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
|
||||
fi
|
||||
else
|
||||
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||
local config_username=$(gen_random_string 10)
|
||||
local config_password=$(gen_random_string 10)
|
||||
|
||||
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
|
||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}"
|
||||
echo -e "Generated new random login credentials:"
|
||||
echo -e "###############################################"
|
||||
echo -e "${green}Username: ${config_username}${plain}"
|
||||
echo -e "${green}Password: ${config_password}${plain}"
|
||||
echo -e "###############################################"
|
||||
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
||||
else
|
||||
echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -282,7 +274,7 @@ install_x-ui() {
|
||||
echo -e "x-ui log - Check logs"
|
||||
echo -e "x-ui banlog - Check Fail2ban ban logs"
|
||||
echo -e "x-ui update - Update"
|
||||
echo -e "x-ui custom - custom version"
|
||||
echo -e "x-ui legacy - legacy version"
|
||||
echo -e "x-ui install - Install"
|
||||
echo -e "x-ui uninstall - Uninstall"
|
||||
echo -e "----------------------------------------------"
|
||||
|
||||
77
main.go
77
main.go
@@ -136,6 +136,15 @@ func showSetting(show bool) {
|
||||
fmt.Println("get webBasePath failed, error info:", err)
|
||||
}
|
||||
|
||||
certFile, err := settingService.GetCertFile()
|
||||
if err != nil {
|
||||
fmt.Println("get cert file failed, error info:", err)
|
||||
}
|
||||
keyFile, err := settingService.GetKeyFile()
|
||||
if err != nil {
|
||||
fmt.Println("get key file failed, error info:", err)
|
||||
}
|
||||
|
||||
userService := service.UserService{}
|
||||
userModel, err := userService.GetFirstUser()
|
||||
if err != nil {
|
||||
@@ -149,14 +158,15 @@ func showSetting(show bool) {
|
||||
}
|
||||
|
||||
fmt.Println("current panel settings as follows:")
|
||||
if certFile == "" || keyFile == "" {
|
||||
fmt.Println("Warning: Panel is not secure with SSL")
|
||||
} else {
|
||||
fmt.Println("Panel is secure with SSL")
|
||||
}
|
||||
fmt.Println("username:", username)
|
||||
fmt.Println("password:", userpasswd)
|
||||
fmt.Println("port:", port)
|
||||
if webBasePath != "" {
|
||||
fmt.Println("webBasePath:", webBasePath)
|
||||
} else {
|
||||
fmt.Println("webBasePath is not set")
|
||||
}
|
||||
fmt.Println("webBasePath:", webBasePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +226,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||
}
|
||||
}
|
||||
|
||||
func updateSetting(port int, username string, password string, webBasePath string) {
|
||||
func updateSetting(port int, username string, password string, webBasePath string, listenIP string) {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println("Database initialization failed:", err)
|
||||
@@ -252,6 +262,15 @@ func updateSetting(port int, username string, password string, webBasePath strin
|
||||
fmt.Println("Base URI path set successfully")
|
||||
}
|
||||
}
|
||||
|
||||
if listenIP != "" {
|
||||
err := settingService.SetListen(listenIP)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to set listen IP:", err)
|
||||
} else {
|
||||
fmt.Printf("listen %v set successfully", listenIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateCert(publicKey string, privateKey string) {
|
||||
@@ -281,6 +300,37 @@ func updateCert(publicKey string, privateKey string) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetCertificate(getCert bool) {
|
||||
if getCert {
|
||||
settingService := service.SettingService{}
|
||||
certFile, err := settingService.GetCertFile()
|
||||
if err != nil {
|
||||
fmt.Println("get cert file failed, error info:", err)
|
||||
}
|
||||
keyFile, err := settingService.GetKeyFile()
|
||||
if err != nil {
|
||||
fmt.Println("get key file failed, error info:", err)
|
||||
}
|
||||
|
||||
fmt.Println("cert:", certFile)
|
||||
fmt.Println("key:", keyFile)
|
||||
}
|
||||
}
|
||||
|
||||
func GetListenIP(getListen bool) {
|
||||
if getListen {
|
||||
|
||||
settingService := service.SettingService{}
|
||||
ListenIP, err := settingService.GetListen()
|
||||
if err != nil {
|
||||
log.Printf("Failed to retrieve listen IP: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("listenIP:", ListenIP)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateDb() {
|
||||
inboundService := service.InboundService{}
|
||||
|
||||
@@ -339,6 +389,8 @@ func main() {
|
||||
var username string
|
||||
var password string
|
||||
var webBasePath string
|
||||
var listenIP string
|
||||
var getListen bool
|
||||
var webCertFile string
|
||||
var webKeyFile string
|
||||
var tgbottoken string
|
||||
@@ -347,6 +399,7 @@ func main() {
|
||||
var tgbotRuntime string
|
||||
var reset bool
|
||||
var show bool
|
||||
var getCert bool
|
||||
var remove_secret bool
|
||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
||||
@@ -355,6 +408,9 @@ func main() {
|
||||
settingCmd.StringVar(&username, "username", "", "Set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "Set login password")
|
||||
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
||||
settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
|
||||
settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
|
||||
settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
|
||||
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
||||
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
|
||||
@@ -397,11 +453,17 @@ func main() {
|
||||
if reset {
|
||||
resetSetting()
|
||||
} else {
|
||||
updateSetting(port, username, password, webBasePath)
|
||||
updateSetting(port, username, password, webBasePath, listenIP)
|
||||
}
|
||||
if show {
|
||||
showSetting(show)
|
||||
}
|
||||
if getListen {
|
||||
GetListenIP(getListen)
|
||||
}
|
||||
if getCert {
|
||||
GetCertificate(getCert)
|
||||
}
|
||||
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||
}
|
||||
@@ -422,7 +484,6 @@ func main() {
|
||||
} else {
|
||||
updateCert(webCertFile, webKeyFile)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Println("Invalid subcommands")
|
||||
fmt.Println()
|
||||
|
||||
@@ -452,38 +452,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
}
|
||||
}
|
||||
|
||||
if security == "xtls" {
|
||||
params["security"] = "xtls"
|
||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
}
|
||||
if len(alpn) > 0 {
|
||||
params["alpn"] = strings.Join(alpn, ",")
|
||||
}
|
||||
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
|
||||
params["sni"], _ = sniValue.(string)
|
||||
}
|
||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||
if xtlsSetting != nil {
|
||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||
params["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||
if insecure.(bool) {
|
||||
params["allowInsecure"] = "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
}
|
||||
}
|
||||
|
||||
if security != "tls" && security != "reality" && security != "xtls" {
|
||||
if security != "tls" && security != "reality" {
|
||||
params["security"] = "none"
|
||||
}
|
||||
|
||||
@@ -676,39 +645,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
}
|
||||
}
|
||||
|
||||
if security == "xtls" {
|
||||
params["security"] = "xtls"
|
||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
}
|
||||
if len(alpn) > 0 {
|
||||
params["alpn"] = strings.Join(alpn, ",")
|
||||
}
|
||||
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
|
||||
params["sni"], _ = sniValue.(string)
|
||||
}
|
||||
|
||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||
if xtlsSetting != nil {
|
||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||
params["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||
if insecure.(bool) {
|
||||
params["allowInsecure"] = "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
}
|
||||
}
|
||||
|
||||
if security != "tls" && security != "reality" && security != "xtls" {
|
||||
if security != "tls" && security != "reality" {
|
||||
params["security"] = "none"
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const supportLangs = [
|
||||
icon: "🇮🇷",
|
||||
},
|
||||
{
|
||||
name: "簡體中文",
|
||||
name: "简体中文",
|
||||
value: "zh-CN",
|
||||
icon: "🇨🇳",
|
||||
},
|
||||
|
||||
@@ -21,11 +21,6 @@ const SSMethods = {
|
||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||
};
|
||||
|
||||
const XTLS_FLOW_CONTROL = {
|
||||
ORIGIN: "xtls-rprx-origin",
|
||||
DIRECT: "xtls-rprx-direct",
|
||||
};
|
||||
|
||||
const TLS_FLOW_CONTROL = {
|
||||
VISION: "xtls-rprx-vision",
|
||||
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||
@@ -120,7 +115,6 @@ const USERS_SECURITY = {
|
||||
|
||||
Object.freeze(Protocols);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(XTLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_VERSION_OPTION);
|
||||
Object.freeze(TLS_CIPHER_OPTION);
|
||||
@@ -755,137 +749,6 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||
}
|
||||
};
|
||||
|
||||
class XtlsStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
serverName = '',
|
||||
certificates = [new XtlsStreamSettings.Cert()],
|
||||
alpn = [ALPN_OPTION.H3, ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
||||
settings = new XtlsStreamSettings.Settings()
|
||||
) {
|
||||
super();
|
||||
this.sni = serverName;
|
||||
this.certs = certificates;
|
||||
this.alpn = alpn;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
addCert() {
|
||||
this.certs.push(new XtlsStreamSettings.Cert());
|
||||
}
|
||||
|
||||
removeCert(index) {
|
||||
this.certs.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
let certs;
|
||||
let settings;
|
||||
if (!ObjectUtil.isEmpty(json.certificates)) {
|
||||
certs = json.certificates.map(cert => XtlsStreamSettings.Cert.fromJson(cert));
|
||||
}
|
||||
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
settings = new XtlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.serverName);
|
||||
}
|
||||
return new XtlsStreamSettings(
|
||||
json.serverName,
|
||||
certs,
|
||||
json.alpn,
|
||||
settings,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
serverName: this.sni,
|
||||
certificates: XtlsStreamSettings.toJsonArray(this.certs),
|
||||
alpn: this.alpn,
|
||||
settings: this.settings,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
XtlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||
constructor(
|
||||
useFile = true,
|
||||
certificateFile = '',
|
||||
keyFile = '',
|
||||
certificate = '',
|
||||
key = '',
|
||||
ocspStapling = 3600,
|
||||
oneTimeLoading = false,
|
||||
usage = USAGE_OPTION.ENCIPHERMENT
|
||||
) {
|
||||
super();
|
||||
this.useFile = useFile;
|
||||
this.certFile = certificateFile;
|
||||
this.keyFile = keyFile;
|
||||
this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate;
|
||||
this.key = Array.isArray(key) ? key.join('\n') : key;
|
||||
this.ocspStapling = ocspStapling;
|
||||
this.oneTimeLoading = oneTimeLoading;
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
if ('certificateFile' in json && 'keyFile' in json) {
|
||||
return new XtlsStreamSettings.Cert(
|
||||
true,
|
||||
json.certificateFile,
|
||||
json.keyFile, '', '',
|
||||
json.ocspStapling,
|
||||
json.oneTimeLoading,
|
||||
json.usage,
|
||||
);
|
||||
} else {
|
||||
return new XtlsStreamSettings.Cert(
|
||||
false, '', '',
|
||||
json.certificate.join('\n'),
|
||||
json.key.join('\n'),
|
||||
json.ocspStapling,
|
||||
json.oneTimeLoading,
|
||||
json.usage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
toJson() {
|
||||
if (this.useFile) {
|
||||
return {
|
||||
certificateFile: this.certFile,
|
||||
keyFile: this.keyFile,
|
||||
ocspStapling: this.ocspStapling,
|
||||
oneTimeLoading: this.oneTimeLoading,
|
||||
usage: this.usage,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
certificate: this.cert.split('\n'),
|
||||
key: this.key.split('\n'),
|
||||
ocspStapling: this.ocspStapling,
|
||||
oneTimeLoading: this.oneTimeLoading,
|
||||
usage: this.usage,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XtlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||
constructor(allowInsecure = false) {
|
||||
super();
|
||||
this.allowInsecure = allowInsecure;
|
||||
}
|
||||
static fromJson(json = {}) {
|
||||
return new XtlsStreamSettings.Settings(
|
||||
json.allowInsecure,
|
||||
);
|
||||
}
|
||||
toJson() {
|
||||
return {
|
||||
allowInsecure: this.allowInsecure,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class RealityStreamSettings extends XrayCommonClass {
|
||||
constructor(
|
||||
@@ -1071,7 +934,6 @@ class StreamSettings extends XrayCommonClass {
|
||||
security = 'none',
|
||||
externalProxy = [],
|
||||
tlsSettings = new TlsStreamSettings(),
|
||||
xtlsSettings = new XtlsStreamSettings(),
|
||||
realitySettings = new RealityStreamSettings(),
|
||||
tcpSettings = new TcpStreamSettings(),
|
||||
kcpSettings = new KcpStreamSettings(),
|
||||
@@ -1087,7 +949,6 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.security = security;
|
||||
this.externalProxy = externalProxy;
|
||||
this.tls = tlsSettings;
|
||||
this.xtls = xtlsSettings;
|
||||
this.reality = realitySettings;
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
@@ -1111,18 +972,6 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get isXtls() {
|
||||
return this.security === "xtls";
|
||||
}
|
||||
|
||||
set isXtls(isXtls) {
|
||||
if (isXtls) {
|
||||
this.security = 'xtls';
|
||||
} else {
|
||||
this.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
//for Reality
|
||||
get isReality() {
|
||||
return this.security === "reality";
|
||||
@@ -1150,7 +999,6 @@ class StreamSettings extends XrayCommonClass {
|
||||
json.security,
|
||||
json.externalProxy,
|
||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
||||
RealityStreamSettings.fromJson(json.realitySettings),
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
@@ -1170,7 +1018,6 @@ class StreamSettings extends XrayCommonClass {
|
||||
security: this.security,
|
||||
externalProxy: this.externalProxy,
|
||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
@@ -1283,18 +1130,6 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get xtls() {
|
||||
return this.stream.security === 'xtls';
|
||||
}
|
||||
|
||||
set xtls(isXtls) {
|
||||
if (isXtls) {
|
||||
this.stream.security = 'xtls';
|
||||
} else {
|
||||
this.stream.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
get network() {
|
||||
return this.stream.network;
|
||||
}
|
||||
@@ -1349,7 +1184,6 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
get serverName() {
|
||||
if (this.stream.isTls) return this.stream.tls.sni;
|
||||
if (this.stream.isXtls) return this.stream.xtls.sni;
|
||||
if (this.stream.isReality) return this.stream.reality.serverNames;
|
||||
return "";
|
||||
}
|
||||
@@ -1425,12 +1259,7 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
canEnableReality() {
|
||||
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||
return ["tcp", "http", "grpc"].includes(this.network);
|
||||
}
|
||||
|
||||
canEnableXtls() {
|
||||
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||
return this.network === "tcp";
|
||||
return ["tcp", "http", "grpc", "splithttp"].includes(this.network);
|
||||
}
|
||||
|
||||
canEnableStream() {
|
||||
@@ -1592,18 +1421,6 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
else if (security === 'xtls') {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.xtls.alpn);
|
||||
if (this.stream.xtls.settings.allowInsecure) {
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)) {
|
||||
params.set("sni", this.stream.xtls.sni);
|
||||
}
|
||||
params.set("flow", flow);
|
||||
}
|
||||
|
||||
else if (security === 'reality') {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
@@ -1801,18 +1618,6 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
else if (security === 'xtls') {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.xtls.alpn);
|
||||
if (this.stream.xtls.settings.allowInsecure) {
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)) {
|
||||
params.set("sni", this.stream.xtls.sni);
|
||||
}
|
||||
params.set("flow", flow);
|
||||
}
|
||||
|
||||
else {
|
||||
params.set("security", "none");
|
||||
}
|
||||
@@ -2273,7 +2078,6 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
constructor(
|
||||
password = RandomUtil.randomSeq(10),
|
||||
flow = '',
|
||||
email = RandomUtil.randomLowerAndNum(8),
|
||||
limitIp = 0,
|
||||
totalGB = 0,
|
||||
@@ -2285,7 +2089,6 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
) {
|
||||
super();
|
||||
this.password = password;
|
||||
this.flow = flow;
|
||||
this.email = email;
|
||||
this.limitIp = limitIp;
|
||||
this.totalGB = totalGB;
|
||||
@@ -2299,7 +2102,6 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
toJson() {
|
||||
return {
|
||||
password: this.password,
|
||||
flow: this.flow,
|
||||
email: this.email,
|
||||
limitIp: this.limitIp,
|
||||
totalGB: this.totalGB,
|
||||
@@ -2314,7 +2116,6 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.TrojanSettings.Trojan(
|
||||
json.password,
|
||||
json.flow,
|
||||
json.email,
|
||||
json.limitIp,
|
||||
json.totalGB,
|
||||
@@ -2651,16 +2452,14 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||
mtu = 1420,
|
||||
secretKey = Wireguard.generateKeypair().privateKey,
|
||||
peers = [new Inbound.WireguardSettings.Peer()],
|
||||
kernelMode = false,
|
||||
kernelTun = false,
|
||||
noKernelTun = false
|
||||
) {
|
||||
super(protocol);
|
||||
this.mtu = mtu;
|
||||
this.secretKey = secretKey;
|
||||
this.pubKey = secretKey.length > 0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
|
||||
this.peers = peers;
|
||||
this.kernelMode = kernelMode;
|
||||
this.kernelTun = kernelTun;
|
||||
this.noKernelTun = noKernelTun;
|
||||
}
|
||||
|
||||
addPeer() {
|
||||
@@ -2677,8 +2476,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||
json.mtu,
|
||||
json.secretKey,
|
||||
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
|
||||
json.kernelMode,
|
||||
json.kernelTun,
|
||||
json.noKernelTun,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2687,8 +2485,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||
mtu: this.mtu ?? undefined,
|
||||
secretKey: this.secretKey,
|
||||
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||
kernelMode: this.kernelMode,
|
||||
kernelTun: this.kernelTun,
|
||||
noKernelTun: this.noKernelTun,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -602,7 +602,7 @@ class Outbound extends CommonClass {
|
||||
|
||||
canEnableReality() {
|
||||
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||
return ["tcp", "http", "grpc"].includes(this.stream.network);
|
||||
return ["tcp", "http", "grpc", "splithttp"].includes(this.stream.network);
|
||||
}
|
||||
|
||||
canEnableStream() {
|
||||
@@ -875,16 +875,16 @@ Outbound.FreedomSettings = class extends CommonClass {
|
||||
json.domainStrategy,
|
||||
json.redirect,
|
||||
json.fragment ? Outbound.FreedomSettings.Fragment.fromJson(json.fragment) : undefined,
|
||||
json.noises ? json.noises.map(noise => Outbound.FreedomSettings.Noise.fromJson(noise)) : [new Outbound.FreedomSettings.Noise()],
|
||||
json.noises ? json.noises.map(noise => Outbound.FreedomSettings.Noise.fromJson(noise)) : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
domainStrategy: ObjectUtil.isEmpty(this.domainStrategy) ? undefined : this.domainStrategy,
|
||||
redirect: this.redirect,
|
||||
redirect: ObjectUtil.isEmpty(this.redirect) ? undefined: this.redirect,
|
||||
fragment: Object.keys(this.fragment).length === 0 ? undefined : this.fragment,
|
||||
noises: Outbound.FreedomSettings.Noise.toJsonArray(this.noises),
|
||||
noises: this.noises.length === 0 ? undefined : Outbound.FreedomSettings.Noise.toJsonArray(this.noises),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -937,10 +937,6 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
||||
delay: this.delay,
|
||||
};
|
||||
}
|
||||
|
||||
static toJsonArray(noises) {
|
||||
return noises.map(noise => noise.toJson());
|
||||
}
|
||||
};
|
||||
|
||||
Outbound.BlackholeSettings = class extends CommonClass {
|
||||
@@ -1182,8 +1178,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
domainStrategy = '',
|
||||
reserved = '',
|
||||
peers = [new Outbound.WireguardSettings.Peer()],
|
||||
kernelMode = false,
|
||||
kernelTun = false
|
||||
noKernelTun = false,
|
||||
) {
|
||||
super();
|
||||
this.mtu = mtu;
|
||||
@@ -1194,8 +1189,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
this.domainStrategy = domainStrategy;
|
||||
this.reserved = Array.isArray(reserved) ? reserved.join(',') : reserved;
|
||||
this.peers = peers;
|
||||
this.kernelMode = kernelMode;
|
||||
this.kernelTun = kernelTun;
|
||||
this.noKernelTun = noKernelTun;
|
||||
}
|
||||
|
||||
addPeer() {
|
||||
@@ -1215,8 +1209,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
json.domainStrategy,
|
||||
json.reserved,
|
||||
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
|
||||
json.kernelMode,
|
||||
json.kernelTun,
|
||||
json.noKernelTun,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1229,8 +1222,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
||||
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||
kernelMode: this.kernelMode,
|
||||
kernelTun: this.kernelTun,
|
||||
noKernelTun: this.noKernelTun,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,12 +39,6 @@
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.xtls">
|
||||
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.subSettings.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
@@ -181,9 +175,6 @@
|
||||
if (clientsBulkModal.inbound.canEnableTlsFlow()) {
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
if (clientsBulkModal.inbound.xtls) {
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
newClient.reset = clientsBulkModal.reset;
|
||||
clients.push(newClient);
|
||||
}
|
||||
|
||||
@@ -104,12 +104,6 @@
|
||||
</a-textarea>
|
||||
</a-form>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.stream.isXtls" label='Flow'>
|
||||
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
|
||||
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
|
||||
@@ -147,11 +147,8 @@
|
||||
<a-form-item label='Workers'>
|
||||
<a-input-number v-model.number="outbound.settings.workers" min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Mode'>
|
||||
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Tun'>
|
||||
<a-switch v-model="outbound.settings.kernelTun"></a-switch>
|
||||
<a-form-item label='No Kernel Tun'>
|
||||
<a-switch v-model="outbound.settings.noKernelTun"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
|
||||
@@ -18,11 +18,8 @@
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Mode'>
|
||||
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Tun'>
|
||||
<a-switch v-model="inbound.settings.kernelTun"></a-switch>
|
||||
<a-form-item label='No Kernel Tun'>
|
||||
<a-switch v-model="inbound.settings.noKernelTun"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Peers">
|
||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
|
||||
|
||||
@@ -5,18 +5,7 @@
|
||||
<a-form-item label='{{ i18n "security" }}'>
|
||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
||||
</template>
|
||||
<a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
||||
</template>
|
||||
<a-radio-button v-if="inbound.canEnableReality()" value="reality">REALITY</a-radio-button>
|
||||
</a-tooltip>
|
||||
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||
<a-radio-button value="tls">TLS</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
@@ -116,11 +105,6 @@
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- xtls settings -->
|
||||
<template v-else-if="inbound.stream.isXtls">
|
||||
{{template "form/xtlsSettings"}}
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
<template v-if="inbound.stream.isReality">
|
||||
{{template "form/realitySettings"}}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
{{define "form/xtlsSettings"}}
|
||||
<template>
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="ALPN">
|
||||
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.xtls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow Insecure">
|
||||
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-for="cert,index in inbound.stream.xtls.certs">
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()"
|
||||
style="margin-left: 10px"></a-button>
|
||||
<a-button icon="minus" v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
|
||||
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px"></a-button>
|
||||
</a-form-item>
|
||||
<template v-if="cert.useFile">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||
<a-input v-model.trim="cert.certFile"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||
<a-input v-model.trim="cert.keyFile"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">
|
||||
{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label='OCSP stapling'>
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="One Time Loading">
|
||||
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Usage Option'>
|
||||
<a-select v-model="cert.usage" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
{{end}}
|
||||
@@ -154,15 +154,6 @@
|
||||
<a-tag color="orange">{{ i18n "none" }}</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.inbound.xtls">
|
||||
<td>Flow</td>
|
||||
<td v-if="infoModal.clientSettings.flow">
|
||||
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
|
||||
</td>
|
||||
<td v-else>
|
||||
<a-tag color="orange">{{ i18n "none" }}</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.clientSettings.password">
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
@@ -370,12 +361,8 @@
|
||||
<td>[[ inbound.settings.mtu ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kernel Mode</td>
|
||||
<td>[[ inbound.settings.kernelMode ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kernel Tun</td>
|
||||
<td>[[ inbound.settings.kernelTun ]]</td>
|
||||
<td>No Kernel Tun</td>
|
||||
<td>[[ inbound.settings.noKernelTun ]]</td>
|
||||
</tr>
|
||||
<template v-for="(peer, index) in inbound.settings.peers">
|
||||
<tr>
|
||||
|
||||
@@ -102,11 +102,6 @@
|
||||
client.flow = "";
|
||||
});
|
||||
}
|
||||
if ((this.inModal.inbound.protocol == Protocols.VLESS || this.inModal.inbound.protocol == Protocols.TROJAN) && !inModal.inbound.xtls) {
|
||||
this.inModal.inbound.settings.vlesses.forEach(client => {
|
||||
client.flow = "";
|
||||
});
|
||||
}
|
||||
},
|
||||
SSMethodChange() {
|
||||
if (this.inModal.inbound.isSSMultiUser) {
|
||||
@@ -132,10 +127,6 @@
|
||||
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
|
||||
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
|
||||
},
|
||||
setDefaultCertXtls(index) {
|
||||
inModal.inbound.stream.xtls.certs[index].certFile = app.defaultCert;
|
||||
inModal.inbound.stream.xtls.certs[index].keyFile = app.defaultKey;
|
||||
},
|
||||
async getNewX25519Cert() {
|
||||
inModal.loading(true);
|
||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||
|
||||
@@ -338,7 +338,6 @@
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="blue">XTLS</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
@@ -549,7 +548,7 @@
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/persianDatepicker" .}}
|
||||
|
||||
@@ -147,8 +147,7 @@
|
||||
publicKey: peer.public_key,
|
||||
endpoint: peer.endpoint.host,
|
||||
}],
|
||||
kernelMode: false,
|
||||
kernelTun: false,
|
||||
noKernelTun: false,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
@@ -37,11 +36,17 @@ func (j *CheckClientIpJob) Run() {
|
||||
|
||||
shouldClearAccessLog := false
|
||||
iplimitActive := j.hasLimitIp()
|
||||
f2bInstalled := j.checkFail2BanInstalled(iplimitActive)
|
||||
f2bInstalled := j.checkFail2BanInstalled()
|
||||
isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive)
|
||||
|
||||
if iplimitActive && f2bInstalled && isAccessLogAvailable {
|
||||
shouldClearAccessLog = j.processLogFile()
|
||||
if iplimitActive {
|
||||
if f2bInstalled && isAccessLogAvailable {
|
||||
shouldClearAccessLog = j.processLogFile()
|
||||
} else {
|
||||
if !f2bInstalled {
|
||||
logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shouldClearAccessLog || (isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600) {
|
||||
@@ -53,23 +58,18 @@ func (j *CheckClientIpJob) clearAccessLog() {
|
||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
j.checkError(err)
|
||||
|
||||
// get access log path to open it
|
||||
accessLogPath, err := xray.GetAccessLogPath()
|
||||
j.checkError(err)
|
||||
|
||||
// reopen the access log file for reading
|
||||
file, err := os.Open(accessLogPath)
|
||||
j.checkError(err)
|
||||
|
||||
// copy access log content to persistent file
|
||||
_, err = io.Copy(logAccessP, file)
|
||||
j.checkError(err)
|
||||
|
||||
// close the file after copying content
|
||||
logAccessP.Close()
|
||||
file.Close()
|
||||
|
||||
// clean access log
|
||||
err = os.Truncate(accessLogPath, 0)
|
||||
j.checkError(err)
|
||||
j.lastClear = time.Now().Unix()
|
||||
@@ -105,74 +105,69 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) processLogFile() bool {
|
||||
accessLogPath, err := xray.GetAccessLogPath()
|
||||
j.checkError(err)
|
||||
|
||||
file, err := os.Open(accessLogPath)
|
||||
j.checkError(err)
|
||||
ipRegex := regexp.MustCompile(`from \[?([0-9a-fA-F:.]+)\]?:\d+ accepted`)
|
||||
emailRegex := regexp.MustCompile(`email: (.+)$`)
|
||||
|
||||
InboundClientIps := make(map[string][]string)
|
||||
accessLogPath, _ := xray.GetAccessLogPath()
|
||||
file, _ := os.Open(accessLogPath)
|
||||
defer file.Close()
|
||||
|
||||
inboundClientIps := make(map[string]map[string]struct{}, 100)
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
ipRegx, _ := regexp.Compile(`from \[?([0-9a-fA-F:.]+)\]?:\d+ accepted`)
|
||||
emailRegx, _ := regexp.Compile(`email: (\S+)$`)
|
||||
|
||||
matches := ipRegx.FindStringSubmatch(line)
|
||||
if len(matches) > 1 {
|
||||
ip := matches[1]
|
||||
if ip == "127.0.0.1" || ip == "::1" {
|
||||
continue
|
||||
}
|
||||
|
||||
matchesEmail := emailRegx.FindString(line)
|
||||
if matchesEmail == "" {
|
||||
continue
|
||||
}
|
||||
matchesEmail = strings.Split(matchesEmail, "email: ")[1]
|
||||
|
||||
if InboundClientIps[matchesEmail] != nil {
|
||||
if j.contains(InboundClientIps[matchesEmail], ip) {
|
||||
continue
|
||||
}
|
||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||
} else {
|
||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||
}
|
||||
ipMatches := ipRegex.FindStringSubmatch(line)
|
||||
if len(ipMatches) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := ipMatches[1]
|
||||
|
||||
if ip == "127.0.0.1" || ip == "::1" {
|
||||
continue
|
||||
}
|
||||
|
||||
emailMatches := emailRegex.FindStringSubmatch(line)
|
||||
if len(emailMatches) < 2 {
|
||||
continue
|
||||
}
|
||||
email := emailMatches[1]
|
||||
|
||||
if _, exists := inboundClientIps[email]; !exists {
|
||||
inboundClientIps[email] = make(map[string]struct{})
|
||||
}
|
||||
inboundClientIps[email][ip] = struct{}{}
|
||||
}
|
||||
|
||||
j.checkError(scanner.Err())
|
||||
file.Close()
|
||||
|
||||
shouldCleanLog := false
|
||||
for email, uniqueIps := range inboundClientIps {
|
||||
|
||||
for clientEmail, ips := range InboundClientIps {
|
||||
inboundClientIps, err := j.getInboundClientIps(clientEmail)
|
||||
sort.Strings(ips)
|
||||
if err != nil {
|
||||
j.addInboundClientIps(clientEmail, ips)
|
||||
} else {
|
||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||
ips := make([]string, 0, len(uniqueIps))
|
||||
for ip := range uniqueIps {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
sort.Strings(ips)
|
||||
|
||||
inboundClientIps, err := j.getInboundClientIps(email)
|
||||
if err != nil {
|
||||
j.addInboundClientIps(email, ips)
|
||||
continue
|
||||
}
|
||||
|
||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog
|
||||
}
|
||||
|
||||
return shouldCleanLog
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) checkFail2BanInstalled(iplimitActive bool) bool {
|
||||
func (j *CheckClientIpJob) checkFail2BanInstalled() bool {
|
||||
cmd := "fail2ban-client"
|
||||
args := []string{"-h"}
|
||||
err := exec.Command(cmd, args...).Run()
|
||||
|
||||
if iplimitActive && err != nil {
|
||||
logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) checkAccessLogAvailable(iplimitActive bool) bool {
|
||||
@@ -253,7 +248,6 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||
inboundClientIps.ClientEmail = clientEmail
|
||||
inboundClientIps.Ips = string(jsonIps)
|
||||
|
||||
// Fetch inbound settings by client email
|
||||
inbound, err := j.getInboundByEmail(clientEmail)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err)
|
||||
@@ -265,14 +259,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||
return false
|
||||
}
|
||||
|
||||
// Unmarshal settings to get client limits
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
shouldCleanLog := false
|
||||
j.disAllowedIps = []string{}
|
||||
|
||||
// Open log file for IP limits
|
||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to open IP limit log file: %s", err)
|
||||
@@ -282,7 +274,6 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||
log.SetOutput(logIpFile)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
|
||||
// Check client IP limits
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
limitIp := client.LimitIP
|
||||
|
||||
@@ -248,28 +248,46 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
}
|
||||
|
||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
url := "https://api.github.com/repos/XTLS/Xray-core/releases"
|
||||
resp, err := http.Get(url)
|
||||
const (
|
||||
XrayURL = "https://api.github.com/repos/XTLS/Xray-core/releases"
|
||||
bufferSize = 8192
|
||||
)
|
||||
|
||||
resp, err := http.Get(XrayURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
|
||||
buffer := bytes.NewBuffer(make([]byte, bufferSize))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
if _, err := buffer.ReadFrom(resp.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
releases := make([]Release, 0)
|
||||
err = json.Unmarshal(buffer.Bytes(), &releases)
|
||||
if err != nil {
|
||||
var releases []Release
|
||||
if err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var versions []string
|
||||
for _, release := range releases {
|
||||
if release.TagName >= "v1.7.5" {
|
||||
tagVersion := strings.TrimPrefix(release.TagName, "v")
|
||||
tagParts := strings.Split(tagVersion, ".")
|
||||
if len(tagParts) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
major, err1 := strconv.Atoi(tagParts[0])
|
||||
minor, err2 := strconv.Atoi(tagParts[1])
|
||||
patch, err3 := strconv.Atoi(tagParts[2])
|
||||
if err1 != nil || err2 != nil || err3 != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if (major == 1 && minor == 8 && patch == 24) ||
|
||||
(major == 24 && ((minor > 10) || (minor == 10 && patch >= 16))) ||
|
||||
(major > 24) {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +243,10 @@ func (s *SettingService) GetListen() (string, error) {
|
||||
return s.getString("webListen")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetListen(ip string) error {
|
||||
return s.setString("webListen", ip)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetWebDomain() (string, error) {
|
||||
return s.getString("webDomain")
|
||||
}
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "The IPs history log. (to enable inbound after disabling, clear the log)"
|
||||
"IPLimitlogclear" = "Clear The Log"
|
||||
"setDefaultCert" = "Set Cert from Panel"
|
||||
"xtlsDesc" = "Xray must be v1.7.5"
|
||||
"realityDesc" = "Xray must be v1.8.0+"
|
||||
"telegramDesc" = "Please provide Telegram Chat ID. (use '/id' command in the bot) or (@userinfobot)"
|
||||
"subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients."
|
||||
"info" = "Info"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "Registro de historial de IPs (antes de habilitar la entrada después de que haya sido desactivada por el límite de IP, debes borrar el registro)."
|
||||
"IPLimitlogclear" = "Limpiar el Registro"
|
||||
"setDefaultCert" = "Establecer certificado desde el panel"
|
||||
"xtlsDesc" = "La versión del núcleo de Xray debe ser 1.7.5"
|
||||
"realityDesc" = "La versión del núcleo de Xray debe ser 1.8.0 o superior."
|
||||
"telegramDesc" = "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o (@userinfobot)"
|
||||
"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones."
|
||||
"info" = "Info"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "گزارش تاریخچه آیپی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید"
|
||||
"IPLimitlogclear" = "پاک کردن گزارشها"
|
||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||
"xtlsDesc" = "ایکسری باید 1.7.5 باشد"
|
||||
"realityDesc" = "ایکسری باید +1.8.0 باشد"
|
||||
"telegramDesc" = "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا (@userinfobot)"
|
||||
"subscriptionDesc" = "شما میتوانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین میتوانید از همین نام برای چندین کاربر استفادهکنید"
|
||||
"info" = "اطلاعات"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
|
||||
"IPLimitlogclear" = "Hapus Log"
|
||||
"setDefaultCert" = "Atur Sertifikat dari Panel"
|
||||
"xtlsDesc" = "Xray harus versi 1.7.5"
|
||||
"realityDesc" = "Xray harus versi 1.8.0+"
|
||||
"telegramDesc" = "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau (@userinfobot)"
|
||||
"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
|
||||
"info" = "Info"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "O histórico de IPs. (para ativar o inbound após a desativação, limpe o log)"
|
||||
"IPLimitlogclear" = "Limpar o Log"
|
||||
"setDefaultCert" = "Definir Certificado pelo Painel"
|
||||
"xtlsDesc" = "O Xray deve ser v1.7.5"
|
||||
"realityDesc" = "O Xray deve ser v1.8.0+"
|
||||
"telegramDesc" = "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou (@userinfobot)"
|
||||
"subscriptionDesc" = "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes."
|
||||
"info" = "Informações"
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"meansNoLimit" = "= Без ограничений (значение: ГБ)"
|
||||
"totalFlow" = "Общий расход"
|
||||
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало"
|
||||
"noRecommendKeepDefault" = "Рекомендуется оставить настройки по умолчанию"
|
||||
"noRecommendKeepDefault" = "Не рекомендуется оставлять настройки по умолчанию"
|
||||
"certificatePath" = "Путь к файлу"
|
||||
"certificateContent" = "Содержимое файла"
|
||||
"publicKey" = "Публичный ключ"
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
|
||||
"IPLimitlogclear" = "Очистить лог"
|
||||
"setDefaultCert" = "Установить сертификат с панели"
|
||||
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
|
||||
"realityDesc" = "Версия Xray должна быть не ниже 1.8.0"
|
||||
"telegramDesc" = "Пожалуйста, укажите ID чата Telegram. (используйте команду '/id' в боте) или (@userinfobot)"
|
||||
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций"
|
||||
"info" = "Информация"
|
||||
@@ -187,7 +185,7 @@
|
||||
"inboundData" = "Входящие данные"
|
||||
"exportInbound" = "Экспорт входящих"
|
||||
"import" = "Импортировать"
|
||||
"importInbound" = "Импортировать входящее сообщение"
|
||||
"importInbound" = "Импортировать подключение"
|
||||
|
||||
[pages.client]
|
||||
"add" = "Добавить пользователя"
|
||||
@@ -214,7 +212,7 @@
|
||||
"request" = "Запрос"
|
||||
"response" = "Ответ"
|
||||
"name" = "Имя"
|
||||
"value" = "Ценить"
|
||||
"value" = "Значение"
|
||||
|
||||
[pages.inbounds.stream.tcp]
|
||||
"version" = "Версия"
|
||||
@@ -230,7 +228,7 @@
|
||||
"save" = "Сохранить"
|
||||
"infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу"
|
||||
"restartPanel" = "Перезапуск панели"
|
||||
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель?Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере"
|
||||
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере"
|
||||
"actions" = "Действия"
|
||||
"resetDefaultConfig" = "Сбросить на конфигурацию по умолчанию"
|
||||
"panelSettings" = "Настройки панели"
|
||||
@@ -402,7 +400,7 @@
|
||||
"portal" = "Портал"
|
||||
"intercon" = "Соединение"
|
||||
"settings" = "Настройки"
|
||||
"accountInfo" = "Информация Об учетной записи"
|
||||
"accountInfo" = "Информация об учетной записи"
|
||||
"outboundStatus" = "Исходящий статус"
|
||||
"sendThrough" = "Отправить через"
|
||||
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "IP geçmiş günlüğü. (devre dışı bırakıldıktan sonra gelini etkinleştirmek için günlüğü temizleyin)"
|
||||
"IPLimitlogclear" = "Günlüğü Temizle"
|
||||
"setDefaultCert" = "Panelden Sertifikayı Ayarla"
|
||||
"xtlsDesc" = "Xray v1.7.5 olmalıdır"
|
||||
"realityDesc" = "Xray v1.8.0+ olmalıdır"
|
||||
"telegramDesc" = "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya (@userinfobot)"
|
||||
"subscriptionDesc" = "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz."
|
||||
"info" = "Bilgi"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)"
|
||||
"IPLimitlogclear" = "Очистити журнал"
|
||||
"setDefaultCert" = "Установити сертифікат з панелі"
|
||||
"xtlsDesc" = "Xray має бути v1.7.5"
|
||||
"realityDesc" = "Xray має бути v1.8.0+"
|
||||
"telegramDesc" = "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або (@userinfobot)"
|
||||
"subscriptionDesc" = "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів."
|
||||
"info" = "Інформація"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "Lịch sử đăng nhập IP (trước khi kích hoạt điểm vào sau khi bị vô hiệu hóa bởi giới hạn IP, bạn nên xóa lịch sử)."
|
||||
"IPLimitlogclear" = "Xóa Lịch sử"
|
||||
"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển"
|
||||
"xtlsDesc" = "Xray core cần phiên bản 1.7.5"
|
||||
"realityDesc" = "Xray core cần phiên bản 1.8.0 hoặc cao hơn."
|
||||
"telegramDesc" = "Vui lòng cung cấp ID Trò chuyện Telegram. (sử dụng lệnh '/id' trong bot) hoặc (@userinfobot)"
|
||||
"subscriptionDesc" = "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau"
|
||||
"info" = "Thông tin"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)"
|
||||
"IPLimitlogclear" = "清除日志"
|
||||
"setDefaultCert" = "从面板设置证书"
|
||||
"xtlsDesc" = "Xray 核心需要 1.7.5"
|
||||
"realityDesc" = "Xray 核心需要 1.8.0 及以上版本"
|
||||
"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot"
|
||||
"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。"
|
||||
"info" = "信息"
|
||||
|
||||
@@ -178,8 +178,6 @@
|
||||
"IPLimitlogDesc" = "IP 歷史日誌(要啟用被禁用的入站流量,請清除日誌)"
|
||||
"IPLimitlogclear" = "清除日誌"
|
||||
"setDefaultCert" = "從面板設定證書"
|
||||
"xtlsDesc" = "Xray 核心需要 1.7.5"
|
||||
"realityDesc" = "Xray 核心需要 1.8.0 及以上版本"
|
||||
"telegramDesc" = "請提供Telegram聊天ID。(在機器人中使用'/id'命令)或(@userinfobot"
|
||||
"subscriptionDesc" = "要找到你的訂閱 URL,請導航到“詳細資訊”。此外,你可以為多個客戶端使用相同的名稱。"
|
||||
"info" = "資訊"
|
||||
|
||||
157
x-ui.sh
157
x-ui.sh
@@ -46,6 +46,8 @@ elif [[ "${release}" == "manjaro" ]]; then
|
||||
echo "Your OS is Manjaro"
|
||||
elif [[ "${release}" == "armbian" ]]; then
|
||||
echo "Your OS is Armbian"
|
||||
elif [[ "${release}" == "alpine" ]]; then
|
||||
echo "Your OS is Alpine Linux"
|
||||
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
|
||||
echo "Your OS is OpenSUSE Tumbleweed"
|
||||
elif [[ "${release}" == "openEuler" ]]; then
|
||||
@@ -190,7 +192,7 @@ update_menu() {
|
||||
fi
|
||||
}
|
||||
|
||||
custom_version() {
|
||||
legacy_version() {
|
||||
echo "Enter the panel version (like 2.4.0):"
|
||||
read tag_version
|
||||
|
||||
@@ -198,17 +200,8 @@ custom_version() {
|
||||
echo "Panel version cannot be empty. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
min_version="2.3.5"
|
||||
if [[ "$(printf '%s\n' "$tag_version" "$min_version" | sort -V | head -n1)" == "$tag_version" && "$tag_version" != "$min_version" ]]; then
|
||||
echo "Please use a newer version (at least 2.3.5). Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
download_link="https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh"
|
||||
|
||||
# Use the entered panel version in the download link
|
||||
install_command="bash <(curl -Ls $download_link) 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..."
|
||||
eval $install_command
|
||||
@@ -306,12 +299,19 @@ reset_config() {
|
||||
}
|
||||
|
||||
check_config() {
|
||||
info=$(/usr/local/x-ui/x-ui setting -show true)
|
||||
local info=$(/usr/local/x-ui/x-ui setting -show true)
|
||||
if [[ $? != 0 ]]; then
|
||||
LOGE "get current settings error, please check logs"
|
||||
show_menu
|
||||
return
|
||||
fi
|
||||
LOGI "${info}"
|
||||
|
||||
local existing_webBasePath=$(echo "$info" | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||
local existing_port=$(echo "$info" | grep -Eo 'port: .+' | awk '{print $2}')
|
||||
local server_ip=$(curl -s https://api.ipify.org)
|
||||
|
||||
echo -e "${green}Access URL: http://${server_ip}:${existing_port}${existing_webBasePath}${plain}"
|
||||
}
|
||||
|
||||
set_port() {
|
||||
@@ -1168,6 +1168,7 @@ run_speedtest() {
|
||||
# Run Speedtest
|
||||
speedtest
|
||||
}
|
||||
|
||||
create_iplimit_jails() {
|
||||
# Use default bantime if not passed => 15 minutes
|
||||
local bantime="${1:-15}"
|
||||
@@ -1175,7 +1176,7 @@ create_iplimit_jails() {
|
||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||
|
||||
#On Debian 12+ fail2ban's default backend should be changed to systemd
|
||||
# On Debian 12+ fail2ban's default backend should be changed to systemd
|
||||
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
|
||||
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
|
||||
fi
|
||||
@@ -1185,7 +1186,7 @@ create_iplimit_jails() {
|
||||
enabled=true
|
||||
backend=auto
|
||||
filter=3x-ipl
|
||||
action=3x-ipl
|
||||
action = %(known/action)s[name=%(__name__)s, protocol="%(protocol)s", chain="%(chain)s"]
|
||||
logpath=${iplimit_log_path}
|
||||
maxretry=2
|
||||
findtime=32
|
||||
@@ -1201,7 +1202,7 @@ EOF
|
||||
|
||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||
[INCLUDES]
|
||||
before = iptables-allports.conf
|
||||
before = iptables-common.conf
|
||||
|
||||
[Definition]
|
||||
actionstart = <iptables> -N f2b-<name>
|
||||
@@ -1221,6 +1222,11 @@ actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
||||
|
||||
[Init]
|
||||
# Use default settings from iptables-common.conf
|
||||
# This will automatically handle both IPv4 and IPv6
|
||||
name = default
|
||||
protocol = tcp
|
||||
chain = INPUT
|
||||
EOF
|
||||
|
||||
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
|
||||
@@ -1245,10 +1251,11 @@ iplimit_main() {
|
||||
echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
|
||||
echo -e "${green}\t2.${plain} Change Ban Duration"
|
||||
echo -e "${green}\t3.${plain} Unban Everyone"
|
||||
echo -e "${green}\t4.${plain} Check Logs"
|
||||
echo -e "${green}\t5.${plain} Fail2ban Status"
|
||||
echo -e "${green}\t6.${plain} Restart Fail2ban"
|
||||
echo -e "${green}\t7.${plain} Uninstall Fail2ban"
|
||||
echo -e "${green}\t4.${plain} Ban Logs"
|
||||
echo -e "${green}\t5.${plain} Real-Time Logs"
|
||||
echo -e "${green}\t6.${plain} Service Status"
|
||||
echo -e "${green}\t7.${plain} Service Restart"
|
||||
echo -e "${green}\t8.${plain} Uninstall Fail2ban and IP Limit"
|
||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
@@ -1289,12 +1296,15 @@ iplimit_main() {
|
||||
show_banlog
|
||||
;;
|
||||
5)
|
||||
service fail2ban status
|
||||
tail -f /var/log/fail2ban.log
|
||||
;;
|
||||
6)
|
||||
systemctl restart fail2ban
|
||||
service fail2ban status
|
||||
;;
|
||||
7)
|
||||
systemctl restart fail2ban
|
||||
;;
|
||||
8)
|
||||
remove_iplimit
|
||||
;;
|
||||
*) echo "Invalid choice" ;;
|
||||
@@ -1428,6 +1438,85 @@ remove_iplimit() {
|
||||
esac
|
||||
}
|
||||
|
||||
SSH_port_forwarding() {
|
||||
local server_ip=$(curl -s https://api.ipify.org)
|
||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
|
||||
local existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true | grep -Eo 'cert: .+' | awk '{print $2}')
|
||||
local existing_key=$(/usr/local/x-ui/x-ui setting -getCert true | grep -Eo 'key: .+' | awk '{print $2}')
|
||||
|
||||
local config_listenIP=""
|
||||
local listen_choice=""
|
||||
|
||||
if [[ -n "$existing_cert" && -n "$existing_key" ]]; then
|
||||
echo -e "${green}Panel is secure with SSL.${plain}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -z "$existing_cert" && -z "$existing_key" && -z "$existing_listenIP" ]]; then
|
||||
echo -e "\n${red}Warning: No Cert and Key found! The panel is not secure.${plain}"
|
||||
echo "Please obtain a certificate or set up SSH port forwarding."
|
||||
fi
|
||||
|
||||
if [[ -n "$existing_listenIP" && (-z "$existing_cert" && -z "$existing_key") ]]; then
|
||||
echo -e "\n${green}Current SSH Port Forwarding Configuration:${plain}"
|
||||
echo -e "Standard SSH command:"
|
||||
echo -e "${yellow}ssh -L 2222:${existing_listenIP}:${existing_port} root@${server_ip}${plain}"
|
||||
echo -e "\nIf using SSH key:"
|
||||
echo -e "${yellow}ssh -i <sshkeypath> -L 2222:${existing_listenIP}:${existing_port} root@${server_ip}${plain}"
|
||||
echo -e "\nAfter connecting, access the panel at:"
|
||||
echo -e "${yellow}http://localhost:2222${existing_webBasePath}${plain}"
|
||||
fi
|
||||
|
||||
echo -e "\nChoose an option:"
|
||||
echo -e "${green}1.${plain} Set listen IP"
|
||||
echo -e "${green}2.${plain} Clear listen IP"
|
||||
echo -e "${green}0.${plain} Abort"
|
||||
read -p "Choose an option: " num
|
||||
|
||||
case "$num" in
|
||||
1)
|
||||
if [[ -z "$existing_listenIP" ]]; then
|
||||
echo -e "\nNo listenIP configured. Choose an option:"
|
||||
echo -e "1. Use default IP (127.0.0.1)"
|
||||
echo -e "2. Set a custom IP"
|
||||
read -p "Select an option (1 or 2): " listen_choice
|
||||
|
||||
config_listenIP="127.0.0.1"
|
||||
[[ "$listen_choice" == "2" ]] && read -p "Enter custom IP to listen on: " config_listenIP
|
||||
|
||||
/usr/local/x-ui/x-ui setting -listenIP "${config_listenIP}" >/dev/null 2>&1
|
||||
echo -e "${green}listen IP has been set to ${config_listenIP}.${plain}"
|
||||
restart
|
||||
else
|
||||
config_listenIP="${existing_listenIP}"
|
||||
echo -e "${green}Current listen IP is already set to ${config_listenIP}.${plain}"
|
||||
fi
|
||||
|
||||
if [[ -n "${config_listenIP}" ]]; then
|
||||
echo -e "\n${green}SSH Port Forwarding Configuration:${plain}"
|
||||
echo -e "Standard SSH command:"
|
||||
echo -e "${yellow}ssh -L 2222:${config_listenIP}:${existing_port} root@${server_ip}${plain}"
|
||||
echo -e "\nIf using SSH key:"
|
||||
echo -e "${yellow}ssh -i <sshkeypath> -L 2222:${config_listenIP}:${existing_port} root@${server_ip}${plain}"
|
||||
echo -e "\nAfter connecting, access the panel at:"
|
||||
echo -e "${yellow}http://localhost:2222${existing_webBasePath}${plain}"
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
/usr/local/x-ui/x-ui setting -listenIP ' ' >/dev/null 2>&1
|
||||
echo -e "${green}Listen IP has been cleared.${plain}"
|
||||
restart
|
||||
;;
|
||||
0)
|
||||
echo "Operation aborted."
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option. Exiting."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
echo "x-ui control menu usages: "
|
||||
echo "------------------------------------------"
|
||||
@@ -1457,7 +1546,7 @@ show_menu() {
|
||||
${green}1.${plain} Install
|
||||
${green}2.${plain} Update
|
||||
${green}3.${plain} Update Menu
|
||||
${green}4.${plain} Custom Version
|
||||
${green}4.${plain} Legacy Version
|
||||
${green}5.${plain} Uninstall
|
||||
————————————————
|
||||
${green}6.${plain} Reset Username & Password & Secret Token
|
||||
@@ -1479,13 +1568,14 @@ show_menu() {
|
||||
${green}19.${plain} Cloudflare SSL Certificate
|
||||
${green}20.${plain} IP Limit Management
|
||||
${green}21.${plain} Firewall Management
|
||||
${green}22.${plain} SSH Port Forwarding Management
|
||||
————————————————
|
||||
${green}22.${plain} Enable BBR
|
||||
${green}23.${plain} Update Geo Files
|
||||
${green}24.${plain} Speedtest by Ookla
|
||||
${green}23.${plain} Enable BBR
|
||||
${green}24.${plain} Update Geo Files
|
||||
${green}25.${plain} Speedtest by Ookla
|
||||
"
|
||||
show_status
|
||||
echo && read -p "Please enter your selection [0-24]: " num
|
||||
echo && read -p "Please enter your selection [0-25]: " num
|
||||
|
||||
case "${num}" in
|
||||
0)
|
||||
@@ -1501,7 +1591,7 @@ show_menu() {
|
||||
check_install && update_menu
|
||||
;;
|
||||
4)
|
||||
check_install && custom_version
|
||||
check_install && legacy_version
|
||||
;;
|
||||
5)
|
||||
check_install && uninstall
|
||||
@@ -1555,16 +1645,19 @@ show_menu() {
|
||||
firewall_menu
|
||||
;;
|
||||
22)
|
||||
bbr_menu
|
||||
SSH_port_forwarding
|
||||
;;
|
||||
23)
|
||||
update_geo
|
||||
bbr_menu
|
||||
;;
|
||||
24)
|
||||
update_geo
|
||||
;;
|
||||
25)
|
||||
run_speedtest
|
||||
;;
|
||||
*)
|
||||
LOGE "Please enter the correct number [0-24]"
|
||||
LOGE "Please enter the correct number [0-25]"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -1601,8 +1694,8 @@ if [[ $# > 0 ]]; then
|
||||
"update")
|
||||
check_install 0 && update 0
|
||||
;;
|
||||
"custom")
|
||||
check_install 0 && custom_version 0
|
||||
"legacy")
|
||||
check_install 0 && legacy_version 0
|
||||
;;
|
||||
"install")
|
||||
check_uninstall 0 && install 0
|
||||
|
||||
Reference in New Issue
Block a user