Compare commits

...

62 Commits

Author SHA1 Message Date
mhsanaei
f497a8dbcc v2.4.7 2024-11-14 19:36:10 +03:30
mhsanaei
4290081486 readme: postman
idk why the old link has been removed
2024-11-14 19:21:23 +03:30
mhsanaei
ccda652e69 SplitHTTP - Mode 2024-11-14 13:09:51 +03:30
mhsanaei
2982d809ab update - CF SSL Certificate 2024-11-14 12:00:24 +03:30
mhsanaei
7ad4a3dffc Xray-core v24.11.11 2024-11-13 14:23:53 +03:30
mhsanaei
c0ef53e542 Access URL - SSL exist 2024-11-12 15:39:23 +03:30
mhsanaei
b7d1c84cd0 README OS - Windows x64 2024-11-12 14:26:35 +03:30
mhsanaei
111bfe5d2e ShadowSocks - ivCheck 2024-11-12 14:01:42 +03:30
mhsanaei
6e59aa14b0 Xray core buggy version removed
accept >= v24.11.11 or v1.8.24
2024-11-12 12:13:10 +03:30
mhsanaei
a4cf77422f update dependencies 2024-11-12 01:29:03 +03:30
DecorativeFamily
35df2a0505 [CodeFactor] Apply fixes (#2595)
Co-authored-by: codefactor-io <support@codefactor.io>
2024-11-12 01:28:00 +03:30
dependabot[bot]
9f445686a4 Bump google.golang.org/grpc from 1.67.1 to 1.68.0 (#2597)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.1 to 1.68.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.67.1...v1.68.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 01:10:21 +03:30
mhsanaei
0fc935e996 fix update geo + log details fail2ban 2024-11-04 13:16:04 +01:00
mhsanaei
adb08a60cf rename - splithttp to xhttp 2024-11-03 10:51:53 +01:00
dependabot[bot]
e3576e8a85 Bump github.com/shirou/gopsutil/v4 from 4.24.9 to 4.24.10 (#2593)
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.9 to 4.24.10.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.9...v4.24.10)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 17:40:14 +01:00
mhsanaei
9c065aed4e Access URL - after you get SSL 2024-10-31 15:02:01 +01:00
mhsanaei
7abb092211 v2.4.6 2024-10-31 09:54:53 +01:00
Sanaei
937bfb4c78 SSH Port Forwarding (#2590)
* getListen & getCert

* SSH Port Forwarding

* fix
2024-10-31 09:53:47 +01:00
mhsanaei
1bcdc54b68 Xray Core v24.10.31 2024-10-31 09:47:39 +01:00
mhsanaei
eb58314c53 bash - remove version limit 2024-10-31 01:18:37 +01:00
mhsanaei
c158e6ec73 getListen & getCert 2024-10-31 01:10:17 +01:00
mhsanaei
5ae587ee81 bash - set or reset listenIP
we need this if we want to add SSH port forwarding
2024-10-30 16:35:36 +01:00
mhsanaei
d40fa46851 fix access url 2024-10-30 15:24:18 +01:00
mhsanaei
19a31686da REALITY: SplitHTTP transport 2024-10-30 14:46:27 +01:00
mhsanaei
13f7e07128 bash - Access URL
I will add https with domain later
2024-10-29 15:20:38 +01:00
mhsanaei
0e3691fdbd Xray core buggy version removed
only v24.10.16 or newer and v1.8.24
2024-10-29 14:56:30 +01:00
mhsanaei
e359b5c75e removed - XTLS Security
because its too old and no one use it anymore
2024-10-29 12:50:25 +01:00
pr3ci0u5
3b3bd3dea4 Update translate.ru_RU.toml (#2588) 2024-10-29 11:03:15 +01:00
mhsanaei
8f36b7ea84 update dependencies 2024-10-29 11:00:07 +01:00
mhsanaei
569d99512c iplimit - accept all email format 2024-10-28 20:13:42 +01:00
mhsanaei
ac84553a68 Fail2ban - Real-Time logs 2024-10-28 19:24:44 +01:00
mhsanaei
610db7827d Update install.sh 2024-10-25 11:30:44 +02:00
mhsanaei
088b55c9ed readme 2024-10-24 21:23:21 +02:00
mhsanaei
c800e29900 Update docker.yml 2024-10-24 21:22:40 +02:00
mhsanaei
14435db0d8 bash - Default credentials detected. Security update required 2024-10-24 21:17:00 +02:00
mhsanaei
bd6402562e OS - Alpine Linux
fix bash menu for docker
2024-10-24 20:36:12 +02:00
mhsanaei
d16ad11136 fix outbound noises
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-10-21 10:40:29 +02:00
MHSanaei
6c27e4177d readme: install old version 2024-10-20 14:18:50 +02:00
MHSanaei
bebf83f06c wireguard - noKernelTun 2024-10-20 14:07:21 +02:00
Amazing Watermelon
07bf741b15 Fixed the traditional Chinese name 2024-10-20 11:33:50 +02:00
mhsanaei
5e5851029d update README: SSL Certificate Management 2024-10-17 12:58:18 +02:00
mhsanaei
e6020850fc v2.4.5 2024-10-17 12:27:13 +02:00
mhsanaei
80183f61e8 update README: using old versions
i don't care anymore do whatever you want

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-10-17 11:45:22 +02:00
mhsanaei
1b9432ff37 move security domains into protection shield 2024-10-17 11:27:36 +02:00
MadrinX
f1f813269c feat(tgbot): Add the option to change the telegram API server (#2584) 2024-10-17 10:59:42 +02:00
mhsanaei
a23f390402 splithttp - xmux (change default value) 2024-10-17 10:40:56 +02:00
mhsanaei
2950ce0c17 freedom - default settings 2024-10-17 10:36:05 +02:00
mhsanaei
514c4909a4 revert changes 2024-10-17 10:34:30 +02:00
mhsanaei
99cadf7652 Update install.sh 2024-10-16 16:29:20 +02:00
mhsanaei
4ca36d64a8 update dependencies 2024-10-16 16:26:07 +02:00
mhsanaei
863009dcaa Refactor size formatting for readability 2024-10-16 16:03:00 +02:00
Ahmad Thoriq Najahi
2ef5ccc2fd feat(tgbot): Allow restart core via telegram bot (#2581) 2024-10-16 14:39:25 +02:00
mhsanaei
744583b4e7 install.sh - check existing settings 2024-10-16 13:49:56 +02:00
mhsanaei
b36032e22c REALITY: target as an alias of dest
I don't want to add it separately because they are the same, just the name is different.
2024-10-16 12:22:07 +02:00
mhsanaei
ac7901abba Wireguard - kernel Tun 2024-10-16 12:08:01 +02:00
mhsanaei
dff2496d73 Xray Core v24.10.16 2024-10-16 12:08:01 +02:00
MHSanaei
d97d36bb9e 500 rows for log page 2024-10-15 21:49:36 +02:00
MHSanaei
b0d2cb93e1 bash menu - debug log, clear all logs 2024-10-15 21:37:14 +02:00
laperuz92
f98d78c356 Update Russian translation. (#2583)
* Update Russian translations.

* Some more fixes
2024-10-15 20:56:09 +02:00
dependabot[bot]
d85226dc79 Bump github.com/nicksnyder/go-i18n/v2 from 2.4.0 to 2.4.1 (#2582)
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.4.0...v2.4.1)

---
updated-dependencies:
- dependency-name: github.com/nicksnyder/go-i18n/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 20:55:16 +02:00
MHSanaei
1c2b6095c9 fix core restart on traffic reset of disabled client
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-10-15 20:54:23 +02:00
MHSanaei
5f8c8f4525 v2.4.4 2024-10-11 10:19:53 +02:00
61 changed files with 1099 additions and 955 deletions

View File

@@ -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 }}

View File

@@ -83,7 +83,7 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.9.30/"
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.11.11/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip

View File

@@ -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.9.30/Xray-linux-${ARCH}.zip"
wget "https://github.com/XTLS/Xray-core/releases/download/v24.11.11/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}"

View File

@@ -30,38 +30,62 @@
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## Instalar una Versión Personalizada
## Instalar versión antigua (no recomendamos)
Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.4.3`:
Para instalar la versión deseada, utiliza el siguiente comando de instalación. Por ejemplo, ver `v1.7.9`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.4.3
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):
![](media/APIKey1.PNG)
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):
![](media/APIKey2.png)
Al utilizarlo, simplemente ingresa tu `nombre de dominio`, `correo electrónico` y `CLAVE API`. El diagrama es el siguiente:
![](media/DetailEnter.png)
</details>
@@ -229,6 +253,7 @@ location /sub {
- Oracle Linux 8+
- OpenSUSE Tubleweed
- Amazon Linux 2023
- Windows x64
## Arquitecturas y Dispositivos Compatibles
@@ -452,7 +477,7 @@ Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas d
#### Uso
- [Documentación de API](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
- [Documentación de API](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
- `/login` con `POST` datos de usuario: `{username: '', password: ''}` para iniciar sesión
- `/panel/api/inbounds` base para las siguientes acciones:
@@ -482,7 +507,7 @@ Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas d
- `client.password` para TROJAN
- `client.email` para Shadowsocks
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
</details>
## Variables de Entorno

View File

@@ -30,12 +30,12 @@
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## Install Custom Version
## Install legacy Version (we don't recommend)
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.4.3`:
To install your desired version, use following installation command. e.g., ver `v1.7.9`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.4.3
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
@@ -256,6 +258,7 @@ location /sub {
- Oracle Linux 8+
- OpenSUSE Tubleweed
- Amazon Linux 2023
- Windows x64
## Supported Architectures and Devices
@@ -283,7 +286,8 @@ Our platform offers compatibility with a diverse range of architectures and devi
- English
- Farsi
- Chinese
- Traditional Chinese
- Simplified Chinese
- Russian
- Vietnamese
- Spanish
@@ -484,7 +488,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
#### Usage
- [API Documentation](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
- [API Documentation](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
- `/login` with `POST` user data: `{username: '', password: ''}` for login
- `/panel/api/inbounds` base for following actions:
@@ -515,7 +519,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
- `client.password` for TROJAN
- `client.email` for Shadowsocks
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
</details>
## Environment Variables

View File

@@ -30,12 +30,12 @@
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## Установка определённой версии
## Установить старую версию (мы не рекомендуем)
Чтобы установить нужную вам версию, добавьте номер версии в конец команды установки. Например, `v2.4.3`:
Чтобы установить желаемую версию, используйте следующую команду установки. Например, ver `v1.7.9`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.4.3
VERSION=v1.7.9 && <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
```
## SSL Сертификат
@@ -54,6 +54,8 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
- **Get SSL:** Получить SSL сертификаты.
- **Revoke:** Отозвать существующие SSL сертификаты.
- **Force Renew:** Принудительно перевыпустить SSL сертификаты.
- **Show Existing Domains:** Отобразить все сертификаты доменов, доступные на сервере.
- **Set Certificate Paths for the Panel:** Укажите сертификат для вашего домена, который будет использоваться панелью.
### Certbot
@@ -255,6 +257,7 @@ location /sub {
- Oracle Linux 8+
- OpenSUSE Tubleweed
- Amazon Linux 2023
- Windows x64
## Поддерживаемые архитектуры и устройства
@@ -479,7 +482,7 @@ WARP встроен, и дополнительная установка не т
#### Использование
- [API документация](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
- [API документация](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
- `/login` с `POST`-данными: `{username: '', password: ''}` для входа
- `/panel/api/inbounds` это базовый путь для следующих действий:
@@ -513,7 +516,7 @@ WARP встроен, и дополнительная установка не т
</details>
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
</details>
## Переменные среды

View File

@@ -30,12 +30,12 @@
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## 安装指定版本
## 安装旧版本 (我们不建议)
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.4.3`:
要安装您想要的版本,请使用以下安装命令。例如,ver `v1.7.9`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.4.3
VERSION=v1.7.9 && <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
```
### SSL证书
@@ -51,9 +51,11 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
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
@@ -252,6 +254,7 @@ location /sub {
- Oracle Linux 8+
- OpenSUSE Tubleweed
- Amazon Linux 2023
- Windows x64
## 支持的架构和设备
<details>
@@ -474,7 +477,7 @@ Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备
#### 使用
- [API 文档](https://documenter.getpostman.com/view/5146551/2sAXxP8Y12)
- [API 文档](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
- `/panel/api/inbounds` 以下操作的基础:
@@ -504,7 +507,7 @@ Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备
- `client.password` TROJAN
- `client.email` Shadowsocks
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://god.gw.postman.com/run-collection/5146551-e6aac565-e0e2-46df-acff-2607a51bbd04?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-e6aac565-e0e2-46df-acff-2607a51bbd04%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
</details>
## 环境变量

View File

@@ -1 +1 @@
2.4.3
2.4.7

60
go.mod
View File

@@ -1,38 +1,38 @@
module x-ui
go 1.23.2
go 1.23.3
require (
github.com/gin-contrib/gzip v1.0.1
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.3
github.com/mymmrac/telego v0.31.3
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/mymmrac/telego v0.31.4
github.com/nicksnyder/go-i18n/v2 v2.4.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
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/shirou/gopsutil/v4 v4.24.10
github.com/valyala/fasthttp v1.57.0
github.com/xtls/xray-core v1.8.25-0.20241111042233-0df2446f824d
go.uber.org/atomic v1.11.0
golang.org/x/text v0.19.0
google.golang.org/grpc v1.67.1
golang.org/x/text v0.20.0
google.golang.org/grpc v1.68.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.12
)
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 v1.12.4 // 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.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // 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-20241101162523-b92577c0c142 // 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
@@ -49,8 +49,8 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.10 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
@@ -58,15 +58,15 @@ require (
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
github.com/onsi/ginkgo/v2 v2.21.0 // indirect
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.47.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
github.com/sagernet/sing v0.4.3 // indirect
github.com/sagernet/sing v0.5.0 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
@@ -78,23 +78,23 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.3.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vishvananda/netns v0.0.5 // 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
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.27.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-20241007155032-5fefd90f89a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // 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

126
go.sum
View File

@@ -1,14 +1,14 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
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 v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
github.com/bytedance/sonic v1.12.4/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,12 +22,12 @@ 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.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
@@ -57,6 +57,8 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -64,8 +66,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-20241101162523-b92577c0c142 h1:sAGdeJj0bnMgUNVeUpp6AYlVdCt3/GdI3pGRqsNSQLs=
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/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=
@@ -82,11 +84,11 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -107,14 +109,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.31.3 h1:yZlD+dm+1W6p3OmCG8K+MbS02Y6paUgwPnqfZN3RWQQ=
github.com/mymmrac/telego v0.31.3/go.mod h1:coOoqXVmjFnwBlzusjfEezbQ7RH9wQnDowJdMm+bnEo=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/mymmrac/telego v0.31.4 h1:NpiNl0P/8eydknka/k6XaaaWVj5BKMlM3Ibba63QTBU=
github.com/mymmrac/telego v0.31.4/go.mod h1:T12js1PgbYDYznvoN05MSMuPMfWTYo7D9LKl5cPFWiI=
github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g=
github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@@ -129,8 +131,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.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
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=
@@ -139,16 +141,16 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=
github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing v0.5.0 h1:soo2wVwLcieKWWKIksFNK6CCAojUgAppqQVwyRYGkEM=
github.com/sagernet/sing v0.5.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/shirou/gopsutil/v4 v4.24.9 h1:KIV+/HaHD5ka5f570RZq+2SaeFsb/pq+fp2DGNWYoOI=
github.com/shirou/gopsutil/v4 v4.24.9/go.mod h1:3fkaHNeYsUFCGZ8+9vZVWtbyM1k2eRnlL+bWO8Bxa/Q=
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -172,63 +174,63 @@ 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=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/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.20241111042233-0df2446f824d h1:00+ceMqRs5YdBMm4NLdsupYnxkP5Ghka9Lzw2ciVhgk=
github.com/xtls/xray-core v1.8.25-0.20241111042233-0df2446f824d/go.mod h1:n8wJBW828YcDQ+9+BnAtbofXVt31VMvz4TwbZK0BaEE=
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=
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
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-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/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/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -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,62 +139,59 @@ gen_random_string() {
}
config_after_install() {
echo -e "${yellow}Install/update finished! For security, it's recommended to modify panel settings ${plain}"
read -p "Would you like to customize the Panel Port settings? (If not, random settings will be applied) [y/n]: " config_confirm
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)
local config_webBasePath=$(gen_random_string 15)
local config_account=$(gen_random_string 10)
local config_password=$(gen_random_string 10)
if [[ ${#existing_webBasePath} -lt 4 ]]; then
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
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 "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
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_account}${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_account}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
echo -e "${green}Settings applied successfully!${plain}"
echo -e "###############################################"
echo -e "${green}Username: ${config_account}${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_account}" -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_account}${plain}"
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)
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
if [[ ${#existing_webBasePath} -lt 4 ]]; then
echo -e "${yellow}WebBasePath is empty, generating a random one...${plain}"
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
fi
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
@@ -275,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
View File

@@ -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()

View File

@@ -47,7 +47,9 @@
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
"domainStrategy": "UseIP",
"redirect": "",
"noises": []
}
},
{

View File

@@ -238,6 +238,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
headers, _ := splithttp["headers"].(map[string]interface{})
obj["host"] = searchHost(headers)
}
obj["mode"] = splithttp["mode"].(string)
}
security, _ := stream["security"].(string)
obj["tls"] = security
@@ -389,6 +390,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
headers, _ := splithttp["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
params["mode"] = splithttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
@@ -452,38 +454,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"
}
@@ -617,6 +588,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
headers, _ := splithttp["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
params["mode"] = splithttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
@@ -676,39 +648,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"
}
@@ -846,6 +786,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
headers, _ := splithttp["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
params["mode"] = splithttp["mode"].(string)
}
security, _ := stream["security"].(string)

View File

@@ -4,18 +4,14 @@ import (
"fmt"
)
func FormatTraffic(trafficBytes int64) (size string) {
if trafficBytes < 1024 {
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
} else if trafficBytes < (1024 * 1024) {
return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024))
} else if trafficBytes < (1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024))
} else if trafficBytes < (1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024))
} else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024))
} else {
return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024))
func FormatTraffic(trafficBytes int64) string {
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
unitIndex := 0
size := float64(trafficBytes)
for size >= 1024 && unitIndex < len(units)-1 {
size /= 1024
unitIndex++
}
return fmt.Sprintf("%.2f%s", size, units[unitIndex])
}

View File

@@ -63,7 +63,7 @@
return scriptHint(editor, javascriptKeywords,
function (e, cur) {return e.getTokenAt(cur);},
options);
};
}
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
function getCoffeeScriptToken(editor, cur) {

View File

@@ -362,7 +362,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
}
return exp;
}

View File

@@ -6,7 +6,7 @@
.CodeMirror-lint-tooltip {
background-color: #ffd;
border: 1px solid black;
border-radius: 4px 4px 4px 4px;
border-radius: 4px;
color: black;
font-family: monospace;
font-size: 10pt;

View File

@@ -10,7 +10,7 @@ const supportLangs = [
icon: "🇮🇷",
},
{
name: "簡體中文",
name: "简体中文",
value: "zh-CN",
icon: "🇨🇳",
},

View File

@@ -140,9 +140,9 @@ class DBInbound {
return false;
}
}
genInboundLinks(remarkModel) {
genInboundLinks(remarkModel) {
const inbound = this.toInbound();
return inbound.genInboundLinks(this.remark,remarkModel);
return inbound.genInboundLinks(this.remark, remarkModel);
}
}

View File

@@ -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",
@@ -118,9 +113,14 @@ const USERS_SECURITY = {
ZERO: "zero",
};
const MODE_OPTION = {
AUTO: "auto",
PACKET_UP: "packet-up",
STREAM_UP: "stream-up",
};
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);
@@ -131,6 +131,7 @@ Object.freeze(USAGE_OPTION);
Object.freeze(DOMAIN_STRATEGY_OPTION);
Object.freeze(TCP_CONGESTION_OPTION);
Object.freeze(USERS_SECURITY);
Object.freeze(MODE_OPTION);
class XrayCommonClass {
@@ -529,12 +530,13 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
scMinPostsIntervalMs = "10-50",
noSSEHeader = false,
xPaddingBytes = "100-1000",
xmux = {
maxConcurrency: 0,
xmux = {
maxConcurrency: "16-32",
maxConnections: 0,
cMaxReuseTimes: 0,
cMaxReuseTimes: "64-128",
cMaxLifetimeMs: 0
}
},
mode = MODE_OPTION.AUTO,
) {
super();
this.path = path;
@@ -545,7 +547,8 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
this.noSSEHeader = noSSEHeader;
this.xPaddingBytes = xPaddingBytes;
this.xmux = xmux;
this.xmux = xmux;
this.mode = mode;
}
addHeader(name, value) {
@@ -567,6 +570,7 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
json.noSSEHeader,
json.xPaddingBytes,
json.xmux,
json.mode,
);
}
@@ -585,7 +589,8 @@ class SplitHTTPStreamSettings extends XrayCommonClass {
maxConnections: this.xmux.maxConnections,
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
cMaxLifetimeMs: this.xmux.cMaxLifetimeMs
}
},
mode: this.mode,
};
}
}
@@ -755,137 +760,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(
@@ -909,7 +783,7 @@ class RealityStreamSettings extends XrayCommonClass {
this.minClient = minClient;
this.maxClient = maxClient;
this.maxTimediff = maxTimediff;
this.shortIds = Array.isArray(shortIds) ? shortIds.join(",") : shortIds;
this.shortIds = Array.isArray(shortIds) ? shortIds.join(",") : shortIds;
this.settings = settings;
}
@@ -920,7 +794,9 @@ class RealityStreamSettings extends XrayCommonClass {
json.settings.publicKey,
json.settings.fingerprint,
json.settings.serverName,
json.settings.spiderX);}
json.settings.spiderX
);
}
return new RealityStreamSettings(
json.show,
json.xver,
@@ -1069,7 +945,6 @@ class StreamSettings extends XrayCommonClass {
security = 'none',
externalProxy = [],
tlsSettings = new TlsStreamSettings(),
xtlsSettings = new XtlsStreamSettings(),
realitySettings = new RealityStreamSettings(),
tcpSettings = new TcpStreamSettings(),
kcpSettings = new KcpStreamSettings(),
@@ -1085,7 +960,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;
@@ -1109,18 +983,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";
@@ -1148,7 +1010,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),
@@ -1168,7 +1029,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,
@@ -1281,18 +1141,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;
}
@@ -1347,7 +1195,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 "";
}
@@ -1423,12 +1270,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() {
@@ -1498,6 +1340,7 @@ class Inbound extends XrayCommonClass {
const splithttp = this.stream.splithttp;
obj.path = splithttp.path;
obj.host = splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host');
obj.mode = splithttp.mode;
}
if (security === 'tls') {
@@ -1570,6 +1413,7 @@ class Inbound extends XrayCommonClass {
const splithttp = this.stream.splithttp;
params.set("path", splithttp.path);
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
params.set("mode", splithttp.mode);
break;
}
@@ -1590,18 +1434,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);
@@ -1685,6 +1517,7 @@ class Inbound extends XrayCommonClass {
const splithttp = this.stream.splithttp;
params.set("path", splithttp.path);
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
params.set("mode", splithttp.mode);
break;
}
@@ -1767,6 +1600,7 @@ class Inbound extends XrayCommonClass {
const splithttp = this.stream.splithttp;
params.set("path", splithttp.path);
params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'));
params.set("mode", splithttp.mode);
break;
}
@@ -1799,18 +1633,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");
}
@@ -2271,7 +2093,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,
@@ -2283,7 +2104,6 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
) {
super();
this.password = password;
this.flow = flow;
this.email = email;
this.limitIp = limitIp;
this.totalGB = totalGB;
@@ -2297,7 +2117,6 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
toJson() {
return {
password: this.password,
flow: this.flow,
email: this.email,
limitIp: this.limitIp,
totalGB: this.totalGB,
@@ -2312,7 +2131,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,
@@ -2395,13 +2213,15 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
method = SSMethods.BLAKE3_AES_256_GCM,
password = RandomUtil.randomShadowsocksPassword(),
network = 'tcp,udp',
shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()]
shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()],
ivCheck = false,
) {
super(protocol);
this.method = method;
this.password = password;
this.network = network;
this.shadowsockses = shadowsockses;
this.ivCheck = ivCheck;
}
static fromJson(json = {}) {
@@ -2411,6 +2231,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
json.password,
json.network,
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
json.ivCheck,
);
}
@@ -2419,7 +2240,8 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
method: this.method,
password: this.password,
network: this.network,
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses)
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses),
ivCheck: this.ivCheck,
};
}
};
@@ -2598,7 +2420,7 @@ Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass {
Inbound.HttpSettings = class extends Inbound.Settings {
constructor(
protocol,
protocol,
accounts = [new Inbound.HttpSettings.HttpAccount()],
allowTransparent = false,
) {
@@ -2644,13 +2466,19 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
};
Inbound.WireguardSettings = class extends XrayCommonClass {
constructor(protocol, mtu = 1420, secretKey = Wireguard.generateKeypair().privateKey, peers = [new Inbound.WireguardSettings.Peer()], kernelMode = false) {
constructor(
protocol,
mtu = 1420,
secretKey = Wireguard.generateKeypair().privateKey,
peers = [new Inbound.WireguardSettings.Peer()],
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.noKernelTun = noKernelTun;
}
addPeer() {
@@ -2667,7 +2495,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
json.mtu,
json.secretKey,
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
json.noKernelTun,
);
}
@@ -2676,7 +2504,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
mtu: this.mtu ?? undefined,
secretKey: this.secretKey,
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
noKernelTun: this.noKernelTun,
};
}
};

View File

@@ -77,6 +77,12 @@ const USERS_SECURITY = {
ZERO: "zero",
};
const MODE_OPTION = {
AUTO: "auto",
PACKET_UP: "packet-up",
STREAM_UP: "stream-up",
};
Object.freeze(Protocols);
Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL);
@@ -85,6 +91,7 @@ Object.freeze(ALPN_OPTION);
Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy);
Object.freeze(USERS_SECURITY);
Object.freeze(MODE_OPTION);
class CommonClass {
@@ -320,16 +327,22 @@ class HttpUpgradeStreamSettings extends CommonClass {
}
class SplitHTTPStreamSettings extends CommonClass {
constructor(path = '/', host = '') {
constructor(
path = '/',
host = '',
mode = '',
) {
super();
this.path = path;
this.host = host;
this.mode = mode;
}
static fromJson(json = {}) {
return new SplitHTTPStreamSettings(
json.path,
json.host,
json.mode,
);
}
@@ -337,6 +350,7 @@ class SplitHTTPStreamSettings extends CommonClass {
return {
path: this.path,
host: this.host,
mode: this.mode,
};
}
}
@@ -602,7 +616,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() {
@@ -710,7 +724,7 @@ class Outbound extends CommonClass {
} else if (network === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
} else if (network === 'splithttp') {
stream.splithttp = new SplitHTTPStreamSettings(json.path, json.host);
stream.splithttp = new SplitHTTPStreamSettings(json.path, json.host, json.mode);
}
if (json.tls && json.tls == 'tls') {
@@ -754,7 +768,7 @@ class Outbound extends CommonClass {
} else if (type === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(path, host);
} else if (type === 'splithttp') {
stream.splithttp = new SplitHTTPStreamSettings(path, host);
stream.splithttp = new SplitHTTPStreamSettings(path, host, mode);
}
if (security == 'tls') {
@@ -875,22 +889,26 @@ 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),
};
}
};
Outbound.FreedomSettings.Fragment = class extends CommonClass {
constructor(packets = '1-3', length = '', interval = '') {
constructor(
packets = '1-3',
length = '',
interval = ''
) {
super();
this.packets = packets;
this.length = length;
@@ -933,10 +951,6 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
delay: this.delay,
};
}
static toJsonArray(noises) {
return noises.map(noise => noise.toJson());
}
};
Outbound.BlackholeSettings = class extends CommonClass {
@@ -960,7 +974,7 @@ Outbound.BlackholeSettings = class extends CommonClass {
Outbound.DNSSettings = class extends CommonClass {
constructor(
network = 'udp',
address = '1.1.1.1',
address = '',
port = 53,
nonIPQuery = 'drop',
blockTypes = []
@@ -1178,7 +1192,7 @@ Outbound.WireguardSettings = class extends CommonClass {
domainStrategy = '',
reserved = '',
peers = [new Outbound.WireguardSettings.Peer()],
kernelMode = false
noKernelTun = false,
) {
super();
this.mtu = mtu;
@@ -1189,7 +1203,7 @@ Outbound.WireguardSettings = class extends CommonClass {
this.domainStrategy = domainStrategy;
this.reserved = Array.isArray(reserved) ? reserved.join(',') : reserved;
this.peers = peers;
this.kernelMode = kernelMode;
this.noKernelTun = noKernelTun;
}
addPeer() {
@@ -1209,7 +1223,7 @@ Outbound.WireguardSettings = class extends CommonClass {
json.domainStrategy,
json.reserved,
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
json.noKernelTun,
);
}
@@ -1222,7 +1236,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,
noKernelTun: this.noKernelTun,
};
}
};

View File

@@ -16,6 +16,7 @@ class AllSetting {
this.tgBotEnable = false;
this.tgBotToken = "";
this.tgBotProxy = "";
this.tgBotAPIServer = "";
this.tgBotChatId = "";
this.tgRunTime = "@daily";
this.tgBotBackup = false;

View File

@@ -5,9 +5,9 @@ const ONE_TB = ONE_GB * 1024;
const ONE_PB = ONE_TB * 1024;
function sizeFormat(size) {
if (size < 0) {
return "0 B";
} else if (size < ONE_KB) {
if (size <= 0) return "0 B";
if (size < ONE_KB) {
return size.toFixed(0) + " B";
} else if (size < ONE_MB) {
return (size / ONE_KB).toFixed(2) + " KB";
@@ -59,7 +59,7 @@ function formatSecond(second) {
return (second / 3600).toFixed(0) + 'h';
} else {
day = Math.floor(second / 3600 / 24);
remain = ((second/3600) - (day*24)).toFixed(0);
remain = ((second / 3600) - (day * 24)).toFixed(0);
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
}
}
@@ -149,7 +149,7 @@ function userExpiryColor(threshold, client, isDark = false) {
return isDark ? '#2c3950' : '#bcbcbc';
}
now = new Date().getTime(),
expiry = client.expiryTime;
expiry = client.expiryTime;
switch (true) {
case expiry === null:
return "#7a316f"; // purple

View File

@@ -30,6 +30,7 @@ type AllSetting struct {
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -147,8 +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 label='No Kernel Tun'>
<a-switch v-model="outbound.settings.noKernelTun"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
@@ -267,13 +267,13 @@
<template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="tcp">TCP (RAW)</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">H2</a-select-option>
<a-select-option value="http">HTTP</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
<a-select-option value="splithttp">SplitHTTP</a-select-option>
<a-select-option value="splithttp">SplitHTTP (XHTTP)</a-select-option>
</a-select>
</a-form-item>
<template v-if="outbound.stream.network === 'tcp'">
@@ -380,6 +380,11 @@
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.splithttp.path"></a-input>
</a-form-item>
<a-form-item label='Mode'>
<a-select v-model="outbound.stream.splithttp.mode" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</template>
</template>

View File

@@ -43,5 +43,8 @@
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='ivCheck'>
<a-switch v-model="inbound.settings.ivCheck"></a-switch>
</a-form-item>
</a-form>
{{end}}

View File

@@ -18,8 +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 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>

View File

@@ -12,7 +12,7 @@
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-form-item label='Dest (Target)'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>

View File

@@ -10,7 +10,7 @@
<a-select-option value="http">HTTP</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
<a-select-option value="splithttp">SplitHTTP</a-select-option>
<a-select-option value="splithttp">SplitHTTP (XHTTP)</a-select-option>
</a-select>
</a-form-item>
</a-form>

View File

@@ -4,21 +4,30 @@
<a-input v-model.trim="inbound.stream.splithttp.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.splithttp.path"></a-input>
<a-input v-model.trim="inbound.stream.splithttp.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button icon="plus" size="small" @click="inbound.stream.splithttp.addHeader('host', '')"></a-button>
<a-button icon="plus" size="small" @click="inbound.stream.splithttp.addHeader('host', '')"></a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.splithttp.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.splithttp.removeHeader(index)">-</a-button>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small"
@click="inbound.stream.splithttp.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item label='Mode'>
<a-select v-model="inbound.stream.splithttp.mode" style="width: 50%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Max Concurrent Upload">
<a-input v-model.trim="inbound.stream.splithttp.scMaxConcurrentPosts"></a-input>
</a-form-item>
@@ -35,16 +44,16 @@
<a-switch v-model="inbound.stream.splithttp.noSSEHeader"></a-switch>
</a-form-item>
<a-form-item label="Max Concurrency" v-if="!inbound.stream.splithttp.xmux.maxConnections">
<a-input-number v-model="inbound.stream.splithttp.xmux.maxConcurrency"></a-input-number>
<a-input v-model="inbound.stream.splithttp.xmux.maxConcurrency"></a-input>
</a-form-item>
<a-form-item label="Max Connections" v-if="!inbound.stream.splithttp.xmux.maxConcurrency">
<a-input-number v-model="inbound.stream.splithttp.xmux.maxConnections"></a-input-number>
<a-input v-model="inbound.stream.splithttp.xmux.maxConnections"></a-input>
</a-form-item>
<a-form-item label="Max Reuse Times">
<a-input-number v-model="inbound.stream.splithttp.xmux.cMaxReuseTimes"></a-input-number>
<a-input v-model="inbound.stream.splithttp.xmux.cMaxReuseTimes"></a-input>
</a-form-item>
<a-form-item label="Max Lifetime (ms)">
<a-input-number v-model="inbound.stream.splithttp.xmux.cMaxLifetimeMs"></a-input-number>
<a-input v-model="inbound.stream.splithttp.xmux.cMaxLifetimeMs"></a-input>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -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"}}

View File

@@ -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}}

View File

@@ -58,6 +58,14 @@
</td>
</tr>
</template>
<template v-if="inbound.isSplithttp">
<tr>
<td>Mode</td>
<td>
<a-tag>[[ inbound.stream.splithttp.mode ]]</a-tag>
</td>
</tr>
</template>
<template v-if="inbound.isKcp">
<tr>
<td>kcp {{ i18n "encryption" }}</td>
@@ -154,15 +162,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,8 +369,8 @@
<td>[[ inbound.settings.mtu ]]</td>
</tr>
<tr>
<td>Kernel Mode</td>
<td>[[ inbound.settings.kernelMode ]]</td>
<td>No Kernel Tun</td>
<td>[[ inbound.settings.noKernelTun ]]</td>
</tr>
<template v-for="(peer, index) in inbound.settings.peers">
<tr>

View File

@@ -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');

View File

@@ -15,7 +15,7 @@
overflow-y: hidden;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
margin:-10px 22px -10px !important;
margin:-10px 22px !important;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table {
border-bottom-left-radius: 1rem;
@@ -40,7 +40,7 @@
padding: .5rem;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
margin:-10px 2px -10px !important;
margin:-10px 2px !important;
}
}
.ant-col-sm-24 {
@@ -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" .}}

View File

@@ -291,6 +291,7 @@
<a-select-option value="20">20</a-select-option>
<a-select-option value="50">50</a-select-option>
<a-select-option value="100">100</a-select-option>
<a-select-option value="500">500</a-select-option>
</a-select>
<a-select size="small" v-model="logModal.level" style="width:95px;"
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">

View File

@@ -26,7 +26,7 @@
padding: .5rem 1rem;
text-align: center;
background: rgb(255 145 0 / 15%);
margin: 1.5rem 2.5rem 0rem 2.5rem;
margin: 1.5rem 2.5rem 0rem;
border-radius: .5rem;
transition: all 0.5s;
animation: signal 3s cubic-bezier(0.18, 0.89, 0.32, 1.28) infinite;
@@ -246,6 +246,7 @@
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyLogin" }}' desc='{{ i18n "pages.settings.tgNotifyLoginDesc" }}' v-model="allSetting.tgBotLoginNotify"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramProxy"}}' desc='{{ i18n "pages.settings.telegramProxyDesc"}}' v-model="allSetting.tgBotProxy" placeholder="socks5://user:pass@host:port"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramAPIServer"}}' desc='{{ i18n "pages.settings.telegramAPIServerDesc"}}' v-model="allSetting.tgBotAPIServer" placeholder="https://api.example.com"></setting-list-item>
<a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
@@ -382,12 +383,12 @@
<a-collapse v-if="enableDirect" style="margin-top: 14px;">
<a-collapse-panel header='{{ i18n "pages.xray.directips"}}'>
<a-list-item style="padding: 10px 20px">
<a-checkbox-group v-model="geoIP" :options="geoIPOptions"></a-checkbox-group>
<a-checkbox-group v-model="directIPs" :options="IPsOptions"></a-checkbox-group>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.directdomains"}}'>
<a-list-item style="padding: 10px 20px">
<a-checkbox-group v-model="geoSite" :options="geoSiteOptions"></a-checkbox-group>
<a-checkbox-group v-model="directDomains" :options="DomainsOptions"></a-checkbox-group>
</a-list-item>
</a-collapse-panel>
</a-collapse>
@@ -475,7 +476,7 @@
]
},
],
geoIPOptions: [
IPsOptions: [
{ label: 'Private IP', value: 'private' },
{ label: '🇮🇷 Iran', value: 'ir' },
{ label: '🇨🇳 China', value: 'cn' },
@@ -487,7 +488,7 @@
{ label: '🇹🇷 Türkiye', value: 'tr' },
{ label: '🇧🇷 Brazil', value: 'br' },
],
geoSiteOptions: [
DomainsOptions: [
{ label: '🇮🇷 Iran', value: 'ir' },
{ label: '🇨🇳 China', value: 'cn' },
{ label: '🇷🇺 Russia', value: 'ru' },
@@ -745,7 +746,7 @@
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
}
},
geoIP: {
directIPs: {
get: function () {
if (!this.enableDirect) return [];
const rules = JSON.parse(this.allSetting.subJsonRules);
@@ -771,7 +772,7 @@
this.allSetting.subJsonRules = JSON.stringify(rules);
}
},
geoSite: {
directDomains: {
get: function () {
if (!this.enableDirect) return [];
const rules = JSON.parse(this.allSetting.subJsonRules);

View File

@@ -147,7 +147,7 @@
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
kernelMode: false
noKernelTun: false,
}
});
}

View File

@@ -52,7 +52,7 @@
font-size: 24px;
}
.ant-collapse-content-box>li {
padding: 12px 0 0 0 !important;
padding: 12px 0 0 !important;
}
.ant-list-item>li {
padding: 10px 20px !important;
@@ -270,7 +270,7 @@
v-model="blockedDomains"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.DomainsOptions"> [[ p.label ]]
v-for="p in settingsData.BlockDomainsOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
@@ -316,7 +316,7 @@
v-model="directDomains"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.DirectDomainsOptions"> [[ p.label ]]
v-for="p in settingsData.DomainsOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
@@ -898,7 +898,7 @@
bittorrent: ["bittorrent"],
},
IPsOptions: [
{ label: 'Private IP', value: 'geoip:private' },
{ label: 'Private IPs', value: 'geoip:private' },
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
{ label: '🇨🇳 China', value: 'geoip:cn' },
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
@@ -910,12 +910,6 @@
{ label: '🇧🇷 Brazil', value: 'geoip:br' },
],
DomainsOptions: [
{ label: 'Ads All', value: 'geosite:category-ads-all' },
{ label: 'Ads IR 🇮🇷', value: 'ext:geosite_IR.dat:category-ads-all' },
{ label: 'Ads VN 🇻🇳', value: 'ext:geosite_VN.dat:ads' },
{ label: 'Sec-IR malware', value: 'geosite_IR.dat:malware' },
{ label: 'Sec-IR phishing', value: 'geosite_IR.dat:phishing' },
{ label: 'Sec-IR cryptominers', value: 'geosite_IR.dat:cryptominers' },
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
@@ -926,7 +920,13 @@
{ label: '🇻🇳 Vietnam', value: 'ext:geosite_VN.dat:vn' },
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
],
DirectDomainsOptions: [
BlockDomainsOptions: [
{ label: 'Ads All', value: 'geosite:category-ads-all' },
{ label: 'Ads IR 🇮🇷', value: 'ext:geosite_IR.dat:category-ads-all' },
{ label: 'Ads VN 🇻🇳', value: 'ext:geosite_VN.dat:ads' },
{ label: 'Malware 🇮🇷', value: 'ext:geosite_IR.dat:malware' },
{ label: 'Phishing 🇮🇷', value: 'ext:geosite_IR.dat:phishing' },
{ label: 'Cryptominers 🇮🇷', value: 'ext:geosite_IR.dat:cryptominers' },
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },

View File

@@ -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

View File

@@ -30,7 +30,9 @@
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
"domainStrategy": "UseIP",
"redirect": "",
"noises": []
}
},
{

View File

@@ -1574,7 +1574,7 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
return false, err
}
for _, client := range clients {
if client.Email == clientEmail {
if client.Email == clientEmail && client.Enable {
s.xrayApi.Init(p.GetAPIPort())
cipher := ""
if string(inbound.Protocol) == "shadowsocks" {

View File

@@ -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 > 11) || (minor == 11 && patch >= 11))) ||
(major > 24) {
versions = append(versions, release.TagName)
}
}

View File

@@ -41,6 +41,7 @@ var defaultValueMap = map[string]string{
"tgBotEnable": "false",
"tgBotToken": "",
"tgBotProxy": "",
"tgBotAPIServer": "",
"tgBotChatId": "",
"tgRunTime": "@daily",
"tgBotBackup": "false",
@@ -242,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")
}
@@ -262,6 +267,14 @@ func (s *SettingService) SetTgBotProxy(token string) error {
return s.setString("tgBotProxy", token)
}
func (s *SettingService) GetTgBotAPIServer() (string, error) {
return s.getString("tgBotAPIServer")
}
func (s *SettingService) SetTgBotAPIServer(token string) error {
return s.setString("tgBotAPIServer", token)
}
func (s *SettingService) GetTgBotChatId() (string, error) {
return s.getString("tgBotChatId")
}

View File

@@ -108,8 +108,14 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
logger.Warning("Failed to get Telegram bot proxy URL:", err)
}
// Get Telegram bot API server URL
tgBotAPIServer, err := t.settingService.GetTgBotAPIServer()
if err != nil {
logger.Warning("Failed to get Telegram bot API server URL:", err)
}
// Create new Telegram bot instance
bot, err = t.NewBot(tgBotToken, tgBotProxy)
bot, err = t.NewBot(tgBotToken, tgBotProxy, tgBotAPIServer)
if err != nil {
logger.Error("Failed to initialize Telegram bot API:", err)
return err
@@ -125,26 +131,40 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
return nil
}
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
if proxyUrl == "" {
// No proxy URL provided, use default instance
func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*telego.Bot, error) {
if proxyUrl == "" && apiServerUrl == "" {
return telego.NewBot(token)
}
if !strings.HasPrefix(proxyUrl, "socks5://") {
logger.Warning("Invalid socks5 URL, starting with default")
if proxyUrl != "" {
if !strings.HasPrefix(proxyUrl, "socks5://") {
logger.Warning("Invalid socks5 URL, using default")
return telego.NewBot(token)
}
_, err := url.Parse(proxyUrl)
if err != nil {
logger.Warningf("Can't parse proxy URL, using default instance for tgbot: %v", err)
return telego.NewBot(token)
}
return telego.NewBot(token, telego.WithFastHTTPClient(&fasthttp.Client{
Dial: fasthttpproxy.FasthttpSocksDialer(proxyUrl),
}))
}
if !strings.HasPrefix(apiServerUrl, "http") {
logger.Warning("Invalid http(s) URL, using default")
return telego.NewBot(token)
}
_, err := url.Parse(proxyUrl)
_, err := url.Parse(apiServerUrl)
if err != nil {
logger.Warning("Can't parse proxy URL, using default instance for tgbot:", err)
logger.Warningf("Can't parse API server URL, using default instance for tgbot: %v", err)
return telego.NewBot(token)
}
return telego.NewBot(token, telego.WithFastHTTPClient(&fasthttp.Client{
Dial: fasthttpproxy.FasthttpSocksDialer(proxyUrl),
}))
return telego.NewBot(token, telego.WithAPIServer(apiServerUrl))
}
func (t *Tgbot) IsRunning() bool {
@@ -243,7 +263,12 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
command, _, commandArgs := tu.ParseCommand(message.Text)
// Extract the command from the Message.
// Helper function to handle unknown commands.
handleUnknownCommand := func() {
msg += t.I18nBot("tgbot.commands.unknown")
}
// Handle the command.
switch command {
case "help":
msg += t.I18nBot("tgbot.commands.help")
@@ -266,9 +291,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
if isAdmin {
t.searchClient(chatId, commandArgs[0])
} else {
// Convert message.From.ID to int64
fromID := int64(message.From.ID)
t.getClientUsage(chatId, fromID, commandArgs[0])
t.getClientUsage(chatId, int64(message.From.ID), commandArgs[0])
}
} else {
msg += t.I18nBot("tgbot.commands.usage")
@@ -278,19 +301,46 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
if isAdmin && len(commandArgs) > 0 {
t.searchInbound(chatId, commandArgs[0])
} else {
msg += t.I18nBot("tgbot.commands.unknown")
handleUnknownCommand()
}
case "restart":
onlyMessage = true
if isAdmin {
if len(commandArgs) == 0 {
msg += t.I18nBot("tgbot.commands.restartUsage")
} else if strings.ToLower(commandArgs[0]) == "force" {
if t.xrayService.IsXrayRunning() {
err := t.xrayService.RestartXray(true)
if err != nil {
msg += t.I18nBot("tgbot.commands.restartFailed", "Error=="+err.Error())
} else {
msg += t.I18nBot("tgbot.commands.restartSuccess")
}
} else {
msg += t.I18nBot("tgbot.commands.xrayNotRunning")
}
} else {
handleUnknownCommand()
msg += t.I18nBot("tgbot.commands.restartUsage")
}
} else {
handleUnknownCommand()
}
default:
msg += t.I18nBot("tgbot.commands.unknown")
handleUnknownCommand()
}
if msg != "" {
if onlyMessage {
t.SendMsgToTgbot(chatId, msg)
return
} else {
t.SendAnswer(chatId, msg, isAdmin)
}
t.sendResponse(chatId, msg, onlyMessage, isAdmin)
}
}
// Helper function to send the message based on onlyMessage flag.
func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) {
if onlyMessage {
t.SendMsgToTgbot(chatId, msg)
} else {
t.SendAnswer(chatId, msg, isAdmin)
}
}
@@ -872,6 +922,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")),
),
// TODOOOOOOOOOOOOOO: Add restart button here.
)
numericKeyboardClient := tu.InlineKeyboard(
tu.InlineKeyboardRow(

View File

@@ -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"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "The Telegram bot token obtained from '@BotFather'."
"telegramProxy" = "SOCKS Proxy"
"telegramProxyDesc" = "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)"
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "The Telegram API server to use. Leave blank to use the default server."
"telegramChatId" = "Admin Chat ID"
"telegramChatIdDesc" = "The Telegram Admin Chat ID(s). (comma-separated)(get it here @userinfobot) or (use '/id' command in the bot)"
"telegramNotifyTime" = "Notification Time"
@@ -483,8 +483,12 @@
"status" = "✅ Bot is OK!"
"usage" = "❗ Please provide a text to search!"
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "To search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"helpAdminCommands" = "To restart Xray Core:\r\n<code>/restart force</code>\r\n\r\nTo search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ Operation successful!"
"restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core is not running."
[tgbot.messages]
"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%"

View File

@@ -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"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "Debe obtener el token del administrador de bots de Telegram @botfather."
"telegramProxy" = "Socks5 Proxy"
"telegramProxyDesc" = "Si necesita el proxy Socks5 para conectarse a Telegram. Ajuste su configuración según la guía."
"telegramAPIServer" = "API Server de Telegram"
"telegramAPIServerDesc" = "El servidor API de Telegram a utilizar. Déjelo en blanco para utilizar el servidor predeterminado."
"telegramChatId" = "IDs de Chat de Telegram para Administradores"
"telegramChatIdDesc" = "IDs de Chat múltiples separados por comas. Use @userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat."
"telegramNotifyTime" = "Hora de Notificación del Bot de Telegram"
@@ -483,8 +483,12 @@
"status" = "✅ ¡El bot está bien!"
"usage" = "❗ ¡Por favor proporciona un texto para buscar!"
"getID" = "🆔 Tu ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Para buscar un correo electrónico de cliente:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Observación]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
"helpAdminCommands" = "Para reiniciar Xray Core:\r\n<code>/restart force</code>\r\n\r\nPara buscar un correo electrónico de cliente:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Observación]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Para buscar estadísticas, utiliza el siguiente comando:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ ¡Operación exitosa!"
"restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core no está en ejecución."
[tgbot.messages]
"cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }} : Deshabilitado exitosamente."
"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ChatID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ChatID de usuario: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Elige un Cliente para Inbound {{ .Inbound }}"
"chooseInbound" = "Elige un Inbound"
"chooseInbound" = "Elige un Inbound"

View File

@@ -178,8 +178,6 @@
"IPLimitlogDesc" = "گزارش تاریخچه آی‌پی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید"
"IPLimitlogclear" = "پاک کردن گزارش‌ها"
"setDefaultCert" = "استفاده از گواهی پنل"
"xtlsDesc" = "ایکس‌ری باید 1.7.5 باشد"
"realityDesc" = "ایکس‌ری باید +1.8.0 باشد"
"telegramDesc" = "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا (@userinfobot)"
"subscriptionDesc" = "شما می‌توانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید"
"info" = "اطلاعات"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "دریافت کنید @botfather توکن را می‌توانید از"
"telegramProxy" = "SOCKS پراکسی"
"telegramProxyDesc" = "را برای اتصال به تلگرام فعال می کند SOCKS5 پراکسی"
"telegramAPIServer" = "سرور API تلگرام"
"telegramAPIServerDesc" = "API سرور تلگرام برای اتصال را تغییر میدهد. برای استفاده از سرور پیش فرض خالی بگذارید"
"telegramChatId" = "آی‌دی چت مدیر"
"telegramChatIdDesc" = "دریافت ‌کنید ('/id'یا (دستور (@userinfobot) آی‌دی(های) چت تلگرام مدیر، از"
"telegramNotifyTime" = "زمان نوتیفیکیشن"
@@ -483,8 +483,12 @@
"status" = "✅ ربات در حالت عادی است!"
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیحات]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
"helpAdminCommands" = "برای راه‌اندازی مجدد Xray Core:\r\n<code>/restart force</code>\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیحات]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
"helpClientCommands" = "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n<code>/usage [ایمیل]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ عملیات با موفقیت انجام شد!"
"restartFailed" = "❗ خطا در عملیات.\r\n\r\n<code>خطا: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core در حال اجرا نیست."
[tgbot.messages]
"cpuThreshold" = "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد."
"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <code>{{ .TgUserID }}</code>"
"chooseClient" = "یک مشتری برای ورودی {{ .Inbound }} انتخاب کنید"
"chooseInbound" = "یک ورودی انتخاب کنید"
"chooseInbound" = "یک ورودی انتخاب کنید"

View File

@@ -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"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'."
"telegramProxy" = "Proxy SOCKS"
"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)"
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "Server API Telegram yang akan digunakan. Biarkan kosong untuk menggunakan server default."
"telegramChatId" = "ID Obrolan Admin"
"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
"telegramNotifyTime" = "Waktu Notifikasi"
@@ -482,9 +482,13 @@
"welcome" = "🤖 Selamat datang di <b>{{.Hostname }}</b> bot managemen.\r\n"
"status" = "✅ Bot dalam keadaan baik!"
"usage" = "❗ Harap berikan teks untuk mencari!"
"getID" = "🆔 ID Anda:<code>{{.ID }}</code>"
"helpAdminCommands" = "Untuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n<code>/inbound [Catatan]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
"getID" = "🆔 ID Anda: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n<code>/restart force</code>\r\n\r\nUntuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n<code>/inbound [Catatan]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n<code>/usage [Email]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ Operasi berhasil!"
"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core tidak berjalan."
[tgbot.messages]
"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil."
"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ChatID Telegram Anda dalam konfigurasi Anda.\r\n\r\nChatID Pengguna Anda: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Pilih Klien untuk Inbound {{ .Inbound }}"
"chooseInbound" = "Pilih Inbound"
"chooseInbound" = "Pilih Inbound"

View File

@@ -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"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "O token do bot do Telegram obtido de '@BotFather'."
"telegramProxy" = "Proxy SOCKS"
"telegramProxyDesc" = "Ativa o proxy SOCKS5 para conectar ao Telegram. (ajuste as configurações conforme o guia)"
"telegramAPIServer" = "API Server do Telegram"
"telegramAPIServerDesc" = "O servidor API do Telegram a ser usado. Deixe em branco para usar o servidor padrão."
"telegramChatId" = "ID de Chat do Administrador"
"telegramChatIdDesc" = "O(s) ID(s) de Chat do Administrador no Telegram. (separado por vírgulas)(obtenha aqui @userinfobot) ou (use o comando '/id' no bot)"
"telegramNotifyTime" = "Hora da Notificação"
@@ -483,8 +483,12 @@
"status" = "✅ Bot está OK!"
"usage" = "❗ Por favor, forneça um texto para pesquisar!"
"getID" = "🆔 Seu ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Para pesquisar por um email de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"helpAdminCommands" = "Para reiniciar o Xray Core:\r\n<code>/restart force</code>\r\n\r\nPara pesquisar por um email de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"helpClientCommands" = "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ Operação bem-sucedida!"
"restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core não está em execução."
[tgbot.messages]
"cpuThreshold" = "🔴 A carga da CPU {{ .Percent }}% excede o limite de {{ .Threshold }}%"

View File

@@ -1,6 +1,6 @@
"username" = "Имя пользователя"
"password" = "Пароль"
"login" = "Логин"
"login" = "Войти"
"confirm" = "Подтвердить"
"cancel" = "Отмена"
"close" = "Закрыть"
@@ -67,12 +67,12 @@
"settings" = "Настройки панели"
"xray" = "Настройки Xray"
"logout" = "Выход"
"link" = "Менеджмент"
"link" = "Управление"
[pages.login]
"hello" = "Привет"
"title" = "Добро пожаловать"
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
"loginAgain" = "Ваша сессия истекла. Пожалуйста, войдите в систему снова"
[pages.login.toasts]
"invalidFormData" = "Недопустимый формат данных"
@@ -93,8 +93,8 @@
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
"operationHours" = "Время работы системы"
"systemLoad" = "Системная нагрузка"
"systemLoadDesc" = "средняя загрузка системы за последние 1, 5 и 15 минут"
"connectionTcpCountDesc" = "Всего подключений TCP по всем сетевым картам."
"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут"
"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам."
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
"connectionCount" = "Количество соединений"
"upSpeed" = "Общая скорость upload для всех сетей"
@@ -133,21 +133,21 @@
"update" = "Обновить"
"modifyInbound" = "Изменить подключение"
"deleteInbound" = "Удалить подключение"
"deleteInboundContent" = "Подтвердите удаление подключения?"
"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?"
"deleteClient" = "Удалить клиента"
"deleteClientContent" = "Вы уверены, что хотите удалить клиента?"
"resetTrafficContent" = "Подтвердите сброс трафика?"
"resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?"
"copyLink" = "Копировать ключ"
"address" = "Адрес"
"network" = "Сеть"
"destinationPort" = "Порт назначения"
"targetAddress" = "Целевой адрес"
"monitorDesc" = "Оставьте пустым по умолчанию"
"monitorDesc" = "Оставьте пустым для прослушивания всех IP-адресов"
"meansNoLimit" = "= Без ограничений (значение: ГБ)"
"totalFlow" = "Общий расход"
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало"
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
"certificatePath" = "Путь файла"
"noRecommendKeepDefault" = "Не рекомендуется оставлять настройки по умолчанию"
"certificatePath" = "Путь к файлу"
"certificateContent" = "Содержимое файла"
"publicKey" = "Публичный ключ"
"privatekey" = "Приватный ключ"
@@ -160,16 +160,16 @@
"cloneInboundOk" = "Клонировано"
"resetAllTraffic" = "Сбросить трафик всех подключений"
"resetAllTrafficTitle" = "Сброс трафика всех подключений"
"resetAllTrafficContent" = "Подтверждаете сброс трафика всех подключений?"
"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?"
"resetInboundClientTraffics" = "Сбросить трафик пользователей"
"resetInboundClientTrafficTitle" = "Сброс трафика пользователей"
"resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить весь трафик для этих пользователей?"
"resetAllClientTraffics" = "Сбросить трафик всех пользователей"
"resetAllClientTrafficTitle" = "Сброс трафика всех пользователей"
"resetAllClientTrafficContent" = "Подтверждаете сброс трафика всех пользователей?"
"resetAllClientTrafficContent" = "Вы уверены, что хотите сбросить трафик всех пользователей?"
"delDepletedClients" = "Удалить отключенных пользователей"
"delDepletedClientsTitle" = "Удаление отключенных пользователей"
"delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?"
"email" = "Email"
"emailDesc" = "Пожалуйста, укажите уникальный Email"
"IPLimit" = "Ограничение по IP"
@@ -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,13 +212,13 @@
"request" = "Запрос"
"response" = "Ответ"
"name" = "Имя"
"value" = "Ценить"
"value" = "Значение"
[pages.inbounds.stream.tcp]
"version" = "Версия"
"method" = "Метод"
"path" = "Путь"
"status" = "Положение дел"
"status" = "Статус"
"statusDescription" = "Описание статуса"
"requestHeader" = "Заголовок запроса"
"responseHeader" = "Заголовок ответа"
@@ -228,9 +226,9 @@
[pages.settings]
"title" = "Настройки"
"save" = "Сохранить"
"infoDesc" = "Каждое сделанное здесь изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу"
"infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу"
"restartPanel" = "Перезапуск панели"
"restartPanelDesc" = "Подтвердите перезапуск панели? ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере"
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере"
"actions" = "Действия"
"resetDefaultConfig" = "Сбросить на конфигурацию по умолчанию"
"panelSettings" = "Настройки панели"
@@ -243,21 +241,21 @@
"panelPort" = "Порт панели"
"panelPortDesc" = "Порт, используемый для отображения этой панели"
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
"publicKeyPathDesc" = "Введите полный путь, начинающийся с"
"publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
"privateKeyPathDesc" = "Введите полный путь, начинающийся с"
"privateKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
"panelUrlPath" = "Корневой путь URL адреса панели"
"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться на"
"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
"pageSize" = "Размер нумерации страниц"
"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить"
"remarkModel" = "Модель примечания и символ разделения"
"datepicker" = "Выбор даты"
"datepickerPlaceholder" = "Выберите дату"
"datepickerDescription" = "Тип календаря выбора указывает дату истечения срока действия."
"datepickerDescription" = "Запланированные задачи выполняются в соответствии с данным календарём"
"sampleRemark" = "Пример замечания"
"oldUsername" = "Текущее имя пользователя"
"oldUsername" = "Текущий логин"
"currentPassword" = "Текущий пароль"
"newUsername" = "Новое имя пользователя"
"newUsername" = "Новый логин"
"newPassword" = "Новый пароль"
"telegramBotEnable" = "Включить Telegram бота"
"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота"
@@ -265,8 +263,10 @@
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
"telegramProxy" = "Прокси Socks5"
"telegramProxyDesc" = "Если для подключения к Telegram вам нужен прокси Socks5. Настройте его параметры согласно руководству."
"telegramChatId" = "Telegram ChatID админа бота"
"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте."
"telegramAPIServer" = "API-сервер Telegram"
"telegramAPIServerDesc" = "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию."
"telegramChatId" = "Идентификатор Telegram администратора бота"
"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте."
"telegramNotifyTime" = "Частота уведомлений бота Telegram"
"telegramNotifyTimeDesc" = "Используйте формат времени Crontab"
"tgNotifyBackup" = "Резервное копирование базы данных"
@@ -291,19 +291,19 @@
"subPort" = "Порт подписки"
"subPortDesc" = "Номер порта для обслуживания службы подписки не должен использоваться на сервере"
"subCertPath" = "Путь к файлу открытого ключа сертификата подписки"
"subCertPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
"subCertPathDesc" = "Введите полный путь, начинающийся с '/'"
"subKeyPath" = "Путь к файлу закрытого ключа сертификата подписки"
"subKeyPathDesc" = "Введите абсолютный путь, начинающийся с '/'"
"subKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
"subPath" = "Корневой путь URL-адреса подписки"
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
"subDomain" = "Домен прослушивания"
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все домены и IP-адреса"
"subUpdates" = "Интервалы обновления подписки"
"subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении"
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
"subEncrypt" = "Шифровать конфиги"
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
"subShowInfo" = "Показать информацию об использовании"
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
"subShowInfoDesc" = "Показывать оставшиеся трафик и дату после имени конфигурации"
"subURI" = "URI обратного прокси"
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
"fragment" = "Фрагментация"
@@ -327,7 +327,7 @@
"generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
"logConfigs" = "Журнал"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их только в случае необходимости!"
"blockConfigs" = "Блокировка конфигураций"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
"basicRouting" = "Базовые соединения"
@@ -344,7 +344,7 @@
"Template" = "Шаблон конфигурации Xray"
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона"
"FreedomStrategy" = "Настройка стратегии протокола Freedom"
"FreedomStrategyDesc" = "Установка стратегию вывода сети в протоколе Freedom"
"FreedomStrategyDesc" = "Установка стратегии вывода сети в протоколе Freedom"
"RoutingStrategy" = "Настройка стратегии маршрутизации доменов"
"RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS"
"Torrent" = "Запрет использования BitTorrent"
@@ -362,7 +362,7 @@
"logLevel" = "Уровень журнала"
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
"accessLog" = "Журнал доступа"
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключает журналы доступа."
"errorLog" = "Журнал ошибок"
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
"dnsLog" = "DNS Журнал"
@@ -379,7 +379,7 @@
"dest" = "Пункт назначения"
"inbound" = "Входящий"
"outbound" = "Исходящий"
"balancer" = "балансир"
"balancer" = "Балансировщик"
"info" = "Информация"
"add" = "Добавить правило"
"edit" = "Редактировать правило"
@@ -391,7 +391,7 @@
"editOutbound" = "Изменить исходящий"
"editReverse" = "Редактировать реверс"
"tag" = "Тег"
"tagDesc" = "уникальный тег"
"tagDesc" = "Уникальный тег"
"address" = "Адрес"
"reverse" = "Обратный"
"domain" = "Домен"
@@ -400,21 +400,21 @@
"portal" = "Портал"
"intercon" = "Соединение"
"settings" = "Настройки"
"accountInfo" = "Информация Об Учетной Записи"
"accountInfo" = "Информация об учетной записи"
"outboundStatus" = "Исходящий статус"
"sendThrough" = "Отправить через"
[pages.xray.balancer]
"addBalancer" = "Добавить балансир"
"editBalancer" = "Редактировать балансир"
"addBalancer" = "Добавить балансировщик"
"editBalancer" = "Редактировать балансировщик"
"balancerStrategy" = "Стратегия"
"balancerSelectors" = "Селекторы"
"tag" = "Тег"
"tagDesc" = "уникальный тег"
"tagDesc" = "Уникальный тег"
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
[pages.xray.wireguard]
"secretKey" = "Секретный ключ"
"secretKey" = "Приватный ключ"
"publicKey" = "Публичный ключ"
"allowedIPs" = "Разрешенные IP-адреса"
"endpoint" = "Конечная точка"
@@ -483,8 +483,12 @@
"status" = "✅ Бот работает нормально!"
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Для поиска электронной почты клиента:\r\n<code>/usage [Email]</code>\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n<code>/inbound [Примечание]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
"helpAdminCommands" = "Для перезапуска Xray Core:\r\n<code>/restart force</code>\r\n\r\nДля поиска электронной почты клиента:\r\n<code>/usage [Email]</code>\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n<code>/inbound [Примечание]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Для поиска статистики используйте следующую команду:\r\n<code>/usage [Email]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ Операция успешно завершена!"
"restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core не запущен."
[tgbot.messages]
"cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%"

View File

@@ -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"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "'@BotFather'dan alınan Telegram bot token."
"telegramProxy" = "SOCKS Proxy"
"telegramProxyDesc" = "Telegram'a bağlanmak için SOCKS5 proxy'sini etkinleştirir. (ayarları kılavuzda belirtilen şekilde ayarlayın)"
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "Kullanılacak Telegram API sunucusu. Varsayılan sunucuyu kullanmak için boş bırakın."
"telegramChatId" = "Yönetici Sohbet Kimliği"
"telegramChatIdDesc" = "Telegram Yönetici Sohbet Kimliği(leri). (virgülle ayrılmış)(buradan alın @userinfobot) veya (botta '/id' komutunu kullanın)"
"telegramNotifyTime" = "Bildirim Zamanı"
@@ -483,8 +483,12 @@
"status" = "✅ Bot çalışıyor!"
"usage" = "❗ Lütfen aramak için bir metin sağlayın!"
"getID" = "🆔 Kimliğiniz: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Bir müşteri e-postasını aramak için:\r\n<code>/usage [E-posta]</code>\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n<code>/inbound [Açıklama]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
"helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n<code>/restart force</code>\r\n\r\nBir müşteri e-postasını aramak için:\r\n<code>/usage [E-posta]</code>\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n<code>/inbound [Açıklama]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
"helpClientCommands" = "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n<code>/usage [E-posta]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ İşlem başarılı!"
"restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core çalışmıyor."
[tgbot.messages]
"cpuThreshold" = "🔴 CPU Yükü {{ .Percent }}% eşiği {{ .Threshold }}%'yi aşıyor"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }}: Başarıyla devre dışı bırakıldı."
"askToAddUserId" = "Yapılandırmanız bulunamadı!\r\nLütfen yöneticinizden yapılandırmalarınıza Telegram ChatID'nizi eklemesini isteyin.\r\n\r\nKullanıcı ChatID'niz: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Gelen {{ .Inbound }} için bir Müşteri Seçin"
"chooseInbound" = "Bir Gelen Seçin"
"chooseInbound" = "Bir Gelen Seçin"

View File

@@ -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" = "Інформація"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "Токен бота Telegram, отриманий від '@BotFather'."
"telegramProxy" = "SOCKS Проксі"
"telegramProxyDesc" = "Вмикає проксі-сервер SOCKS5 для підключення до Telegram. (відкоригуйте параметри відповідно до посібника)"
"telegramAPIServer" = "Сервер Telegram API"
"telegramAPIServerDesc" = "Сервер Telegram API для використання. Залиште поле порожнім, щоб використовувати сервер за умовчанням."
"telegramChatId" = "Ідентифікатор чату адміністратора"
"telegramChatIdDesc" = "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут @userinfobot) або (використовуйте команду '/id' у боті)"
"telegramNotifyTime" = "Час сповіщення"
@@ -483,8 +483,12 @@
"status" = "✅ Бот в порядку!"
"usage" = "❗ Введіть текст для пошуку!"
"getID" = "🆔 Ваш ідентифікатор: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Для пошуку електронної пошти клієнта:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
"helpAdminCommands" = "Для перезапуску Xray Core:\r\n<code>/restart force</code>\r\n\r\nДля пошуку електронної пошти клієнта:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Для пошуку статистики використовуйте наступну команду:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ Операція успішна!"
"restartFailed" = "❗ Помилка в операції.\r\n\r\n<code>Помилка: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core не запущений."
[tgbot.messages]
"cpuThreshold" = "🔴 Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }}: Успішно вимкнено."
"askToAddUserId" = "Вашу конфігурацію не знайдено!\r\nБудь ласка, попросіть свого адміністратора використовувати ваш ідентифікатор Telegram у вашій конфігурації.\r\n\r\nВаш ідентифікатор користувача: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Виберіть клієнта для Вхідного {{ .Inbound }}"
"chooseInbound" = "Виберіть Вхідний"
"chooseInbound" = "Виберіть Вхідний"

View File

@@ -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"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "Bạn phải nhận token từ quản lý bot Telegram @botfather"
"telegramProxy" = "Socks5 Proxy"
"telegramProxyDesc" = "Nếu bạn cần socks5 proxy để kết nối với Telegram. Điều chỉnh cài đặt của nó theo hướng dẫn."
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định."
"telegramChatId" = "Chat ID Telegram của quản trị viên"
"telegramChatIdDesc" = "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng @userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn."
"telegramNotifyTime" = "Thời gian thông báo của bot Telegram"
@@ -483,8 +483,12 @@
"status" = "✅ Bot hoạt động bình thường!"
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Để tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
"helpAdminCommands" = "Để khởi động lại Xray Core:\r\n<code>/restart force</code>\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n<code>/usage [Email]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ Hoạt động thành công!"
"restartFailed" = "❗ Lỗi trong quá trình hoạt động.\r\n\r\n<code>Lỗi: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core không chạy."
[tgbot.messages]
"cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }} : Đã Tắt Thành Công."
"askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Chọn một Khách hàng cho Inbound {{ .Inbound }}"
"chooseInbound" = "Chọn một Inbound"
"chooseInbound" = "Chọn một Inbound"

View File

@@ -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" = "信息"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "从 '@BotFather' 获取的 Telegram 机器人令牌"
"telegramProxy" = "SOCKS5 Proxy"
"telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram根据指南调整设置"
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "要使用的 Telegram API 服务器。留空以使用默认服务器。"
"telegramChatId" = "管理员聊天 ID"
"telegramChatIdDesc" = "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令获取)"
"telegramNotifyTime" = "通知时间"
@@ -483,8 +483,12 @@
"status" = "✅ 机器人正常运行!"
"usage" = "❗ 请输入要搜索的文本!"
"getID" = "🆔 您的 ID 为:<code>{{ .ID }}</code>"
"helpAdminCommands" = "要搜索客户电子邮件:\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站带有客户统计数据\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpAdminCommands" = "要重新启动 Xray Core\r\n<code>/restart force</code>\r\n\r\n要搜索客户电子邮件:\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站带有客户统计数据\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n<code>/usage [电子邮件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未运行。"
[tgbot.messages]
"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }}:已成功禁用。"
"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID<code>{{ .TgUserID }}</code>"
"chooseClient" = "为入站 {{ .Inbound }} 选择一个客户"
"chooseInbound" = "选择一个入站"
"chooseInbound" = "选择一个入站"

View File

@@ -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" = "資訊"
@@ -265,6 +263,8 @@
"telegramTokenDesc" = "從 '@BotFather' 獲取的 Telegram 機器人令牌"
"telegramProxy" = "SOCKS5 Proxy"
"telegramProxyDesc" = "啟用 SOCKS5 代理連線到 Telegram根據指南調整設定"
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "要使用的 Telegram API 伺服器。留空以使用預設伺服器。"
"telegramChatId" = "管理員聊天 ID"
"telegramChatIdDesc" = "Telegram 管理員聊天 ID (多個以逗號分隔)(可通過 @userinfobot 獲取,或在機器人中使用 '/id' 命令獲取)"
"telegramNotifyTime" = "通知時間"
@@ -483,8 +483,12 @@
"status" = "✅ 機器人正常執行!"
"usage" = "❗ 請輸入要搜尋的文字!"
"getID" = "🆔 您的 ID 為:<code>{{ .ID }}</code>"
"helpAdminCommands" = "要搜尋客戶電子郵件:\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站帶有客戶統計資料\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpAdminCommands" = "要重新啟動 Xray Core\r\n<code>/restart force</code>\r\n\r\n要搜尋客戶電子郵件:\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站帶有客戶統計資料\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpClientCommands" = "要搜尋統計資料,請使用以下命令:\r\n<code>/usage [電子郵件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未運行。"
[tgbot.messages]
"cpuThreshold" = "🔴 CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%"
@@ -588,4 +592,4 @@
"disableSuccess" = "✅ {{ .Email }}:已成功禁用。"
"askToAddUserId" = "未找到您的配置!\r\n請向管理員詢問在您的配置中使用您的 Telegram 使用者 ChatID。\r\n\r\n您的使用者 ChatID<code>{{ .TgUserID }}</code>"
"chooseClient" = "為入站 {{ .Inbound }} 選擇一個客戶"
"chooseInbound" = "選擇一個入站"
"chooseInbound" = "選擇一個入站"

395
x-ui.sh
View File

@@ -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
@@ -162,7 +164,7 @@ update() {
bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh)
if [[ $? == 0 ]]; then
LOGI "Update is complete, Panel has automatically restarted "
exit 0
before_show_menu
fi
}
@@ -183,14 +185,14 @@ update_menu() {
if [[ $? == 0 ]]; then
echo -e "${green}Update successful. The panel has automatically restarted.${plain}"
exit 0
before_show_menu
else
echo -e "${red}Failed to update the menu.${plain}"
return 1
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
@@ -301,17 +294,35 @@ reset_config() {
return 0
fi
/usr/local/x-ui/x-ui setting -reset
echo -e "All panel settings have been reset to default, Please restart the panel now, and use the default ${green}2053${plain} Port to Access the web Panel"
confirm_restart
echo -e "All panel settings have been reset to default."
restart
}
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 existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true | grep -Eo 'cert: .+' | awk '{print $2}')
local server_ip=$(curl -s https://api.ipify.org)
if [[ -n "$existing_cert" ]]; then
local domain=$(basename "$(dirname "$existing_cert")")
if [[ "$domain" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo -e "${green}Access URL: https://${domain}:${existing_port}${existing_webBasePath}${plain}"
else
echo -e "${green}Access URL: https://${server_ip}:${existing_port}${existing_webBasePath}${plain}"
fi
else
echo -e "${green}Access URL: http://${server_ip}:${existing_port}${existing_webBasePath}${plain}"
fi
}
set_port() {
@@ -416,22 +427,63 @@ disable() {
}
show_log() {
journalctl -u x-ui.service -e --no-pager -f
if [[ $# == 0 ]]; then
echo -e "${green}\t1.${plain} Debug Log"
echo -e "${green}\t2.${plain} Clear All logs"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu
;;
1)
journalctl -u x-ui -e --no-pager -f -p debug
if [[ $# == 0 ]]; then
before_show_menu
fi
fi
;;
2)
sudo journalctl --rotate
sudo journalctl --vacuum-time=1s
echo "All Logs cleared."
restart
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
show_log
;;
esac
}
show_banlog() {
if test -f "${iplimit_banned_log_path}"; then
local system_log="/var/log/fail2ban.log"
echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1
fi
if [[ -f "$system_log" ]]; then
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
echo ""
fi
if [[ -f "${iplimit_banned_log_path}" ]]; then
echo -e "${green}3X-IPL ban log entries:${plain}"
if [[ -s "${iplimit_banned_log_path}" ]]; then
cat ${iplimit_banned_log_path}
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
else
echo -e "${red}Log file is empty.${plain}\n"
echo -e "${yellow}Ban log file is empty${plain}"
fi
else
echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
fi
echo -e "\n${green}Current jail status:${plain}"
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
}
bbr_menu() {
@@ -445,11 +497,16 @@ bbr_menu() {
;;
1)
enable_bbr
bbr_menu
;;
2)
disable_bbr
bbr_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
bbr_menu
;;
*) echo "Invalid choice" ;;
esac
}
@@ -457,7 +514,7 @@ disable_bbr() {
if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo -e "${yellow}BBR is not currently enabled.${plain}"
exit 0
before_show_menu
fi
# Replace BBR with CUBIC configurations
@@ -478,7 +535,7 @@ disable_bbr() {
enable_bbr() {
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo -e "${green}BBR is already enabled!${plain}"
exit 0
before_show_menu
fi
# Check the OS and install necessary packages
@@ -524,7 +581,8 @@ update_shell() {
before_show_menu
else
chmod +x /usr/bin/x-ui
LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
LOGI "Upgrade script succeeded, Please rerun the script"
before_show_menu
fi
}
@@ -636,17 +694,24 @@ firewall_menu() {
;;
1)
open_ports
firewall_menu
;;
2)
sudo ufw status
firewall_menu
;;
3)
delete_ports
firewall_menu
;;
4)
sudo ufw disable
firewall_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
firewall_menu
;;
*) echo "Invalid choice" ;;
esac
}
@@ -765,21 +830,25 @@ update_geo() {
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
echo -e "${green}Loyalsoldier datasets have been updated successfully!${plain}"
restart
;;
2)
rm -f geoip_IR.dat geosite_IR.dat
wget -O geoip_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
echo -e "${green}chocolate4u datasets have been updated successfully!${plain}"
restart
;;
3)
rm -f geoip_VN.dat geosite_VN.dat
wget -O geoip_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
echo -e "${green}vuong2023 datasets have been updated successfully!${plain}"
restart
;;
*)
echo "Invalid option selected! No updates made."
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
update_geo
;;
esac
@@ -823,6 +892,7 @@ ssl_cert_issue_main() {
;;
1)
ssl_cert_issue
ssl_cert_issue_main
;;
2)
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
@@ -839,6 +909,7 @@ ssl_cert_issue_main() {
echo "Invalid domain entered."
fi
fi
ssl_cert_issue_main
;;
3)
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
@@ -855,6 +926,7 @@ ssl_cert_issue_main() {
echo "Invalid domain entered."
fi
fi
ssl_cert_issue_main
;;
4)
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
@@ -874,6 +946,7 @@ ssl_cert_issue_main() {
fi
done
fi
ssl_cert_issue_main
;;
5)
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
@@ -901,15 +974,19 @@ ssl_cert_issue_main() {
echo "Invalid domain entered."
fi
fi
ssl_cert_issue_main
;;
*)
echo "Invalid choice"
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
ssl_cert_issue_main
;;
esac
}
ssl_cert_issue() {
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}')
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it"
@@ -1028,6 +1105,7 @@ ssl_cert_issue() {
LOGI "Panel paths set for domain: $domain"
LOGI " - Certificate File: $webCertFile"
LOGI " - Private Key File: $webKeyFile"
echo -e "${green}Access URL: https://${domain}:${existing_port}${existing_webBasePath}${plain}"
restart
else
LOGE "Error: Certificate or private key file not found for domain: $domain."
@@ -1038,76 +1116,113 @@ ssl_cert_issue() {
}
ssl_cert_issue_CF() {
echo -E ""
LOGD "******Instructions for use******"
LOGI "This Acme script requires the following data:"
LOGI "1.Cloudflare Registered e-mail"
LOGI "2.Cloudflare Global API Key"
LOGI "3.The domain name that has been resolved dns to the current server by Cloudflare"
LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
confirm "Confirmed?[y/n]" "y"
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}')
LOGI "****** Instructions for Use ******"
LOGI "Follow the steps below to complete the process:"
LOGI "1. Cloudflare Registered E-mail."
LOGI "2. Cloudflare Global API Key."
LOGI "3. The Domain Name."
LOGI "4. Once the certificate is issued, you will be prompted to set the certificate for the panel (optional)."
LOGI "5. The script also supports automatic renewal of the SSL certificate after installation."
confirm "Do you confirm the information and wish to proceed? [y/n]" "y"
if [ $? -eq 0 ]; then
# check for acme.sh first
# Check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it"
echo "acme.sh could not be found. We will install it."
install_acme
if [ $? -ne 0 ]; then
LOGE "install acme failed, please check logs"
LOGE "Install acme failed, please check logs."
exit 1
fi
fi
CF_Domain=""
CF_GlobalKey=""
CF_AccountEmail=""
certPath=/root/cert
certPath="/root/cert-CF"
if [ ! -d "$certPath" ]; then
mkdir $certPath
mkdir -p $certPath
else
rm -rf $certPath
mkdir $certPath
mkdir -p $certPath
fi
LOGD "Please set a domain name:"
read -p "Input your domain here:" CF_Domain
LOGD "Your domain name is set to:${CF_Domain}"
read -p "Input your domain here: " CF_Domain
LOGD "Your domain name is set to: ${CF_Domain}"
# Set up Cloudflare API details
CF_GlobalKey=""
CF_AccountEmail=""
LOGD "Please set the API key:"
read -p "Input your key here:" CF_GlobalKey
LOGD "Your API key is:${CF_GlobalKey}"
read -p "Input your key here: " CF_GlobalKey
LOGD "Your API key is: ${CF_GlobalKey}"
LOGD "Please set up registered email:"
read -p "Input your email here:" CF_AccountEmail
LOGD "Your registered email address is:${CF_AccountEmail}"
read -p "Input your email here: " CF_AccountEmail
LOGD "Your registered email address is: ${CF_AccountEmail}"
# Set the default CA to Let's Encrypt
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
if [ $? -ne 0 ]; then
LOGE "Default CA, Lets'Encrypt fail, script exiting..."
LOGE "Default CA, Let'sEncrypt fail, script exiting..."
exit 1
fi
export CF_Key="${CF_GlobalKey}"
export CF_Email=${CF_AccountEmail}
export CF_Email="${CF_AccountEmail}"
# Issue the certificate using Cloudflare DNS
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log
if [ $? -ne 0 ]; then
LOGE "Certificate issuance failed, script exiting..."
exit 1
else
LOGI "Certificate issued Successfully, Installing..."
LOGI "Certificate issued successfully, Installing..."
fi
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
--fullchain-file /root/cert/fullchain.cer
# Install the certificate
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
--cert-file ${certPath}/${CF_Domain}/fullchain.pem \
--key-file ${certPath}/${CF_Domain}/privkey.pem
if [ $? -ne 0 ]; then
LOGE "Certificate installation failed, script exiting..."
exit 1
else
LOGI "Certificate installed Successfully,Turning on automatic updates..."
LOGI "Certificate installed successfully, Turning on automatic updates..."
fi
# Enable auto-update
~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then
LOGE "Auto update setup Failed, script exiting..."
ls -lah cert
chmod 755 $certPath
LOGE "Auto update setup failed, script exiting..."
exit 1
else
LOGI "The certificate is installed and auto-renewal is turned on, Specific information is as follows"
ls -lah cert
chmod 755 $certPath
LOGI "The certificate is installed and auto-renewal is turned on. Specific information is as follows:"
ls -lah ${certPath}/${CF_Domain}
chmod 755 ${certPath}/${CF_Domain}
fi
# Prompt user to set panel paths after successful certificate installation
read -p "Would you like to set this certificate for the panel? (y/n): " setPanel
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
local webCertFile="${certPath}/${CF_Domain}/fullchain.pem"
local webKeyFile="${certPath}/${CF_Domain}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
LOGI "Panel paths set for domain: $CF_Domain"
LOGI " - Certificate File: $webCertFile"
LOGI " - Private Key File: $webKeyFile"
echo -e "${green}Access URL: https://${CF_Domain}:${existing_port}${existing_webBasePath}${plain}"
restart
else
LOGE "Error: Certificate or private key file not found for domain: $CF_Domain."
fi
else
LOGI "Skipping panel path setting."
fi
else
show_menu
@@ -1147,6 +1262,7 @@ run_speedtest() {
# Run Speedtest
speedtest
}
create_iplimit_jails() {
# Use default bantime if not passed => 15 minutes
local bantime="${1:-15}"
@@ -1154,7 +1270,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
@@ -1164,7 +1280,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
@@ -1180,7 +1296,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>
@@ -1200,6 +1316,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}"
@@ -1224,10 +1345,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
@@ -1266,17 +1388,28 @@ iplimit_main() {
;;
4)
show_banlog
iplimit_main
;;
5)
service fail2ban status
tail -f /var/log/fail2ban.log
iplimit_main
;;
6)
systemctl restart fail2ban
service fail2ban status
iplimit_main
;;
7)
remove_iplimit
systemctl restart fail2ban
iplimit_main
;;
8)
remove_iplimit
iplimit_main
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
iplimit_main
;;
*) echo "Invalid choice" ;;
esac
}
@@ -1357,7 +1490,7 @@ install_iplimit() {
remove_iplimit() {
echo -e "${green}\t1.${plain} Only remove IP Limit configurations"
echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit"
echo -e "${green}\t0.${plain} Abort"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " num
case "$num" in
1)
@@ -1397,8 +1530,7 @@ remove_iplimit() {
before_show_menu
;;
0)
echo -e "${yellow}Cancelled.${plain}\n"
iplimit_main
show_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
@@ -1407,6 +1539,83 @@ 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}"
before_show_menu
fi
if [[ -z "$existing_cert" && -z "$existing_key" && (-z "$existing_listenIP" || "$existing_listenIP" == "0.0.0.0") ]]; 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" && "$existing_listenIP" != "0.0.0.0" && (-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} Back to Main Menu"
read -p "Choose an option: " num
case "$num" in
1)
if [[ -z "$existing_listenIP" || "$existing_listenIP" == "0.0.0.0" ]]; 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}"
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}"
restart
else
config_listenIP="${existing_listenIP}"
echo -e "${green}Current listen IP is already set to ${config_listenIP}.${plain}"
fi
;;
2)
/usr/local/x-ui/x-ui setting -listenIP 0.0.0.0 >/dev/null 2>&1
echo -e "${green}Listen IP has been cleared.${plain}"
restart
;;
0)
show_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
SSH_port_forwarding
;;
esac
}
show_usage() {
echo "x-ui control menu usages: "
echo "------------------------------------------"
@@ -1436,7 +1645,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
@@ -1449,7 +1658,7 @@ show_menu() {
${green}12.${plain} Stop
${green}13.${plain} Restart
${green}14.${plain} Check Status
${green}15.${plain} Check Logs
${green}15.${plain} Logs Management
————————————————
${green}16.${plain} Enable Autostart
${green}17.${plain} Disable Autostart
@@ -1458,13 +1667,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)
@@ -1480,7 +1690,7 @@ show_menu() {
check_install && update_menu
;;
4)
check_install && custom_version
check_install && legacy_version
;;
5)
check_install && uninstall
@@ -1534,16 +1744,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
}
@@ -1580,8 +1793,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

View File

@@ -129,7 +129,7 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
CipherType: ssCipherType,
})
} else {
account = serial.ToTypedMessage(&shadowsocks_2022.User{
account = serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{
Key: user["password"].(string),
Email: user["email"].(string),
})