Compare commits

...

33 Commits

Author SHA1 Message Date
MHSanaei
f9c703ea44 remove multi protocol script 2024-01-27 19:07:17 +03:30
MHSanaei
9fba92d879 v2.1.2
revert #1650 #1661 #1664 #1670
made panel full of bug
2024-01-27 00:32:19 +03:30
MHSanaei
daa4354047 ipv6 for familyProtect and some changes 2024-01-26 21:57:46 +03:30
dependabot[bot]
ec88053df0 Bump gorm.io/gorm from 1.25.5 to 1.25.6 (#1683)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.5 to 1.25.6.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.5...v1.25.6)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  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-01-26 20:15:07 +03:30
MHSanaei
08e259327b unnecessary use of fmt.Sprintf 2024-01-26 17:06:54 +03:30
MHSanaei
98384ac236 fix - direct tag 2024-01-26 16:50:50 +03:30
MHSanaei
5f9058c84f fix downloaded log format
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-26 16:50:22 +03:30
MHSanaei
979fdedbbe [bug] avoid empty inbound
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-24 20:50:58 +03:30
dependabot[bot]
2463b99479 Bump google.golang.org/grpc from 1.60.1 to 1.61.0 (#1671)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.60.1 to 1.61.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.60.1...v1.61.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-01-24 15:13:53 +03:30
MHSanaei
ff547a258d gen button - publicKey & psk 2024-01-24 01:24:17 +03:30
Ali Rahimi
251ceeedba bug fixed (import inbounds) (#1670)
* add single client bug fixed

* bug fixed
2024-01-24 01:23:15 +03:30
Ali Rahimi
c1422be269 persian datepicker bug fixed (#1668)
* add single client bug fixed

* persian datepicker bug fixed
2024-01-23 23:16:33 +03:30
Ali Rahimi
538fc9b365 add single client bug fixed (#1664) 2024-01-23 13:30:21 +03:30
Ali Rahimi
b172d450e3 Group editing feature of users with the same subscription (#1661) 2024-01-22 15:08:36 +03:30
Ali Rahimi
5c695ca652 add group user with the same subscription id to all inbounds (#1650) 2024-01-21 17:56:19 +03:30
MHSanaei
e7ce8c8ddb minor changes
disAllowedIps doesn't show on debug if there is no ip
change copy to clipboard to export inbound
2024-01-21 14:39:15 +03:30
MHSanaei
4b9bbbc34b clear banned log after unban everyone 2024-01-21 04:45:17 +03:30
MHSanaei
820e302aac revert fail2ban v1.0.2
it doesn't work well
2024-01-21 04:00:58 +03:30
quydang
ebaabf6150 Update README (#1648) 2024-01-20 20:11:58 +03:30
MHSanaei
a7bea936c5 Update x-ui.sh 2024-01-20 19:46:10 +03:30
MHSanaei
38378fe36f update fail2ban v1.0.2 for debian 2024-01-20 17:02:16 +03:30
MHSanaei
7f13adbd05 fail2ban - auto allowipv6 2024-01-20 15:49:34 +03:30
quydang
74ba6d7825 Add support for ARMv5 and x86 (#1642) 2024-01-20 13:02:35 +03:30
MHSanaei
7fd4015f17 Update x-ui.sh
Fail2ban installation failed
2024-01-19 18:28:09 +03:30
Shahin
589a8702aa Revert-back some edits (#1617)
* Update index.html

* Update translate.en_US.toml
2024-01-18 10:30:38 +03:30
emirjorge
91af54abf1 Fix Spanish Translation (#1619) 2024-01-18 10:29:43 +03:30
MHSanaei
82244ced73 v2.1.1 2024-01-17 21:10:06 +03:30
Nguyễn Cao Nguyên
6856807726 Normalize VN translation (#1607)
Fixed some translations that were too hard to understand.
2024-01-17 20:13:49 +03:30
MHSanaei
2488adc042 remove old files, codes, info 2024-01-17 17:09:29 +03:30
Amin
62f08e877d Fix inbounds with the same port and different IPs (#1595) 2024-01-17 16:21:28 +03:30
somebodywashere
3ed6fc4036 Quick fix RU ,ZH,FA tgbot.messages.active (#1596) 2024-01-17 16:18:21 +03:30
quydang
6550aa6bad Hotfix for ARMv7 and ARMv6 (#1589) 2024-01-16 03:31:15 +03:30
MHSanaei
2a20243751 v2.1.0 2024-01-16 02:01:36 +03:30
41 changed files with 562 additions and 877 deletions

View File

@@ -1,4 +1,5 @@
name: Release 3X-UI dockerhub name: Release 3X-UI for Docker
on: on:
push: push:
tags: tags:
@@ -6,36 +7,44 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build_and_push: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out the code - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/mhsanaei/3x-ui
tags: |
type=ref,event=branch
type=ref,event=tag
type=pep440,pattern={{version}}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0 uses: docker/setup-buildx-action@v3.0.0
- name: Log in to GitHub Container Registry - name: Login to GHCR
uses: docker/login-action@v3.0.0 uses: docker/login-action@v3.0.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta - name: Build and push
id: meta
uses: docker/metadata-action@v5.5.0
with:
images: ghcr.io/${{ github.repository }}
- name: Build and push Docker image
uses: docker/build-push-action@v5.1.0 uses: docker/build-push-action@v5.1.0
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: true
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6 platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6,linux/386
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -15,6 +15,8 @@ jobs:
- arm64 - arm64
- armv7 - armv7
- armv6 - armv6
- 386
- armv5
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -30,8 +32,14 @@ jobs:
sudo apt-get update sudo apt-get update
if [ "${{ matrix.platform }}" == "arm64" ]; then if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "armv6" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
elif [ "${{ matrix.platform }}" == "armv5" ]; then
sudo apt install gcc-arm-linux-gnueabi
fi fi
- name: Build x-ui - name: Build x-ui
@@ -42,10 +50,21 @@ jobs:
if [ "${{ matrix.platform }}" == "arm64" ]; then if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64 export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm export GOARCH=arm
export GOARM=7 export GOARM=7
export CC=arm-linux-gnueabihf-gcc export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "armv6" ]; then
export GOARCH=arm
export GOARM=6
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv5" ]; then
export GOARCH=arm
export GOARM=5
export CC=arm-linux-gnueabi-gcc
fi fi
go build -o xui-release -v main.go go build -o xui-release -v main.go
@@ -75,6 +94,14 @@ jobs:
wget ${Xray_URL}Xray-linux-arm32-v6.zip wget ${Xray_URL}Xray-linux-arm32-v6.zip
unzip Xray-linux-arm32-v6.zip unzip Xray-linux-arm32-v6.zip
rm -f Xray-linux-arm32-v6.zip rm -f Xray-linux-arm32-v6.zip
elif [ "${{ matrix.platform }}" == "386" ]; then
wget ${Xray_URL}Xray-linux-32.zip
unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip
elif [ "${{ matrix.platform }}" == "armv5" ]; then
wget ${Xray_URL}Xray-linux-arm32-v5.zip
unzip Xray-linux-arm32-v5.zip
rm -f Xray-linux-arm32-v5.zip
fi fi
rm -f geoip.dat geosite.dat rm -f geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat

View File

@@ -1,10 +1,13 @@
#!/bin/sh #!/bin/sh
case $1 in case $1 in
amd64) amd64)
ARCH="64" ARCH="64"
FNAME="amd64" FNAME="amd64"
;; ;;
i386)
ARCH="32"
FNAME="i386"
;;
armv8 | arm64 | aarch64) armv8 | arm64 | aarch64)
ARCH="arm64-v8a" ARCH="arm64-v8a"
FNAME="arm64" FNAME="arm64"
@@ -22,19 +25,16 @@ case $1 in
FNAME="amd64" FNAME="amd64"
;; ;;
esac esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip" wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat geoip_VN.dat geosite_VN.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
cd ../../

View File

@@ -1,11 +1,9 @@
# ======================================================== # ========================================================
# Stage: Builder # Stage: Builder
# ======================================================== # ========================================================
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder FROM golang:1.21-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN apk --no-cache --update add \ RUN apk --no-cache --update add \
build-base \ build-base \
@@ -15,6 +13,8 @@ RUN apk --no-cache --update add \
COPY . . COPY . .
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN go build -o build/x-ui main.go RUN go build -o build/x-ui main.go
RUN ./DockerInit.sh "$TARGETARCH" RUN ./DockerInit.sh "$TARGETARCH"
@@ -30,9 +30,9 @@ RUN apk add --no-cache --update \
tzdata \ tzdata \
fail2ban fail2ban
COPY --from=builder /app/build/ /app/ COPY --from=builder /app/build/ /app/
COPY --from=builder /app/DockerEntrypoint.sh /app/ COPY --from=builder /app/DockerEntrypoint.sh /app/
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
# Configure fail2ban # Configure fail2ban
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \

View File

@@ -25,10 +25,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Install Custom Version ## Install Custom Version
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.0.2`: To install your desired version, add the version to the end of the installation command. e.g., ver `v2.1.2`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.0.2 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2
``` ```
## Manual Install & Upgrade ## Manual Install & Upgrade
@@ -103,6 +103,24 @@ systemctl restart x-ui
ghcr.io/mhsanaei/3x-ui:latest ghcr.io/mhsanaei/3x-ui:latest
``` ```
update to latest version
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
remove 3x-ui from docker
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details> </details>
@@ -118,7 +136,22 @@ systemctl restart x-ui
- AlmaLinux 9+ - AlmaLinux 9+
- Rockylinux 9+ - Rockylinux 9+
## Compatible Architectures & Devices
Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
- **x86 / i386**: This architecture is prevalent in desktop and laptop computers. It's widely supported by various operating systems and applications. (Ex: Most Windows, macOS, and Linux systems)
- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
- **armv5 / arm / arm32**: This is an older architecture primarily used in early embedded systems. While it's less common today, some legacy devices may still rely on this architecture. (Ex: Early versions of Raspberry Pi, some older smartphones)
## Languages ## Languages
- English - English
@@ -135,7 +168,7 @@ systemctl restart x-ui
- Search within all inbounds and clients - Search within all inbounds and clients
- Dark/Light theme - Dark/Light theme
- Supports multi-user and multi-protocol - Supports multi-user and multi-protocol
- Supports protocols, including VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP - Supports protocols, including VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP, wireguard
- Supports XTLS native Protocols, including RPRX-Direct, Vision, REALITY - Supports XTLS native Protocols, including RPRX-Direct, Vision, REALITY
- Traffic statistics, traffic limit, expiration time limit - Traffic statistics, traffic limit, expiration time limit
- Customizable Xray configuration templates - Customizable Xray configuration templates
@@ -196,26 +229,6 @@ certbot renew --dry-run
</details> </details>
## Xray Configurations
<details>
<summary>Click for Xray configurations details</summary>
#### Usage
**1.** Copy & paste into the Advanced Xray Configuration:
- [traffic](./media/configs/traffic.json)
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
***Tip:*** *You don't need to do this for a fresh install.*
</details>
## [WARP Configuration](https://gitlab.com/fscarmen/warp) ## [WARP Configuration](https://gitlab.com/fscarmen/warp)
<details> <details>
@@ -223,21 +236,21 @@ certbot renew --dry-run
#### Usage #### Usage
If you want to use routing to WARP follow steps as below: If you want to use routing to WARP before v2.1.0 follow steps as below:
**1.** If you already installed warp, you can uninstall using below command: **1.** Install WARP on **SOCKS Proxy Mode**:
```sh
warp u
```
**2.** Install WARP on **SOCKS Proxy Mode**:
```sh ```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
``` ```
**3.** Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json) **2.** If you already installed warp, you can uninstall using below command:
```sh
warp u
```
**3.** Turn on the config you need in panel
Config Features: Config Features:
@@ -409,20 +422,6 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
</details> </details>
## Supported Architectures and Devices
Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
## Preview ## Preview
![1](./media/1.png) ![1](./media/1.png)

View File

@@ -1 +1 @@
2.0.2 2.1.2

View File

@@ -37,7 +37,7 @@ type Inbound struct {
// config part // config part
Listen string `json:"listen" form:"listen"` Listen string `json:"listen" form:"listen"`
Port int `json:"port" form:"port" gorm:"unique"` Port int `json:"port" form:"port"`
Protocol Protocol `json:"protocol" form:"protocol"` Protocol Protocol `json:"protocol" form:"protocol"`
Settings string `json:"settings" form:"settings"` Settings string `json:"settings" form:"settings"`
StreamSettings string `json:"streamSettings" form:"streamSettings"` StreamSettings string `json:"streamSettings" form:"streamSettings"`

6
go.mod
View File

@@ -17,9 +17,9 @@ require (
github.com/xtls/xray-core v1.8.7 github.com/xtls/xray-core v1.8.7
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
google.golang.org/grpc v1.60.1 google.golang.org/grpc v1.61.0
gorm.io/driver/sqlite v1.5.4 gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.6
) )
require ( require (
@@ -36,7 +36,7 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-playground/validator/v10 v10.17.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect

12
go.sum
View File

@@ -76,8 +76,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -413,8 +413,8 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@@ -437,8 +437,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=

View File

@@ -26,12 +26,15 @@ echo "The OS release is: $release"
arch3xui() { arch3xui() {
case "$(uname -m)" in case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;; x86_64 | x64 | amd64) echo 'amd64' ;;
i*86 | x86) echo '386' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm) echo 'armv7' ;; armv7* | armv7 | arm) echo 'armv7' ;;
armv6* | armv6 | arm) echo 'armv6' ;; armv6* | armv6) echo 'armv6' ;;
armv5* | armv5) echo 'armv5' ;;
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
esac esac
} }
echo "arch: $(arch3xui)" echo "arch: $(arch3xui)"
os_version="" os_version=""
@@ -78,19 +81,21 @@ fi
install_base() { install_base() {
case "${release}" in case "${release}" in
centos|fedora|almalinux|rocky) centos | almalinux | rocky)
yum -y update && yum install -y -q wget curl tar yum -y update && yum install -y -q wget curl tar
;; ;;
arch|manjaro) fedora)
pacman -Syu && pacman -Syu --noconfirm wget curl tar dnf -y update && dnf install -y -q wget curl tar
;; ;;
*) arch | manjaro)
apt-get update && apt install -y -q wget curl tar pacman -Syu && pacman -Syu --noconfirm wget curl tar
;; ;;
*)
apt-get update && apt install -y -q wget curl tar
;;
esac esac
} }
# This function will be called when user installed x-ui out of security # This function will be called when user installed x-ui out of security
config_after_install() { config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}" echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
@@ -118,9 +123,9 @@ config_after_install() {
echo -e "${green}username:${usernameTemp}${plain}" echo -e "${green}username:${usernameTemp}${plain}"
echo -e "${green}password:${passwordTemp}${plain}" echo -e "${green}password:${passwordTemp}${plain}"
echo -e "###############################################" echo -e "###############################################"
echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}" echo -e "${red}if you forgot your login info,you can type x-ui and then type 8 to check after installation${plain}"
else else
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}" echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 8 to check${plain}"
fi fi
fi fi
/usr/local/x-ui/x-ui migrate /usr/local/x-ui/x-ui migrate
@@ -160,18 +165,21 @@ install_x-ui() {
tar zxvf x-ui-linux-$(arch3xui).tar.gz tar zxvf x-ui-linux-$(arch3xui).tar.gz
rm x-ui-linux-$(arch3xui).tar.gz -f rm x-ui-linux-$(arch3xui).tar.gz -f
cd x-ui cd x-ui
chmod +x x-ui
# Check the system's architecture and rename the file accordingly
if [[ $(arch3xui) == "armv5" || $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then
mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
chmod +x x-ui bin/xray-linux-$(arch3xui) chmod +x x-ui bin/xray-linux-$(arch3xui)
cp -f x-ui.service /etc/systemd/system/ cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
config_after_install config_after_install
#echo -e "If it is a new installation, the default web port is ${green}2053${plain}, The username and password are ${green}admin${plain} by default"
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 2053 has been released${plain}"
# echo -e "If you want to modify the 2053 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
#echo -e ""
#echo -e "If it is updated panel, access the panel in your previous way"
#echo -e ""
systemctl daemon-reload systemctl daemon-reload
systemctl enable x-ui systemctl enable x-ui
systemctl start x-ui systemctl start x-ui

View File

@@ -1,97 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
},
{
"tag": "IPv4",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv4"
}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all"
]
},
{
"type": "field",
"outboundTag": "IPv4",
"domain": [
"geosite:google"
]
}
]
},
"stats": {}
}

View File

@@ -1,105 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
},
{
"tag": "WARP",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 40000
}
]
}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all"
]
},
{
"type": "field",
"outboundTag": "WARP",
"domain": [
"geosite:spotify",
"geosite:netflix",
"geosite:openai",
"geosite:google"
]
}
]
},
"stats": {}
}

View File

@@ -1,84 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"regexp:.*\\.ir$",
"regexp:.*\\.xn--mgba3a4f16a$",
"ext:geosite_IR.dat:ir"
]
}
]
},
"stats": {}
}

View File

@@ -1,76 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private",
"ext:geoip_IR.dat:ir"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
}
]
},
"stats": {}
}

View File

@@ -1,65 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": ["HandlerService", "LoggerService", "StatsService"]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": ["geoip:private"]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": ["bittorrent"]
}
]
},
"stats": {}
}

View File

@@ -915,7 +915,7 @@ Outbound.HttpSettings = class extends CommonClass {
Outbound.WireguardSettings = class extends CommonClass { Outbound.WireguardSettings = class extends CommonClass {
constructor( constructor(
mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
address='', workers=2, domainStrategy='', reserved='', address=[''], workers=2, domainStrategy='ForceIPv6v4', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) { peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super(); super();
this.mtu = mtu; this.mtu = mtu;
@@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass {
}; };
Outbound.WireguardSettings.Peer = class extends CommonClass { Outbound.WireguardSettings.Peer = class extends CommonClass {
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) { constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
super(); super();
this.publicKey = publicKey; this.publicKey = publicKey;
this.psk = psk; this.psk = psk;

View File

@@ -2297,7 +2297,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
}; };
Inbound.WireguardSettings.Peer = class extends XrayCommonClass { Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) { constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
super(); super();
this.publicKey = publicKey; this.publicKey = publicKey;
this.psk = psk; this.psk = psk;

View File

@@ -85,7 +85,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
} }
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
inbound.UserId = user.Id inbound.UserId = user.Id
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
} else {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound) inbound, needRestart, err = a.inboundService.AddInbound(inbound)
@@ -278,7 +282,11 @@ func (a *InboundController) importInbound(c *gin.Context) {
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
inbound.Id = 0 inbound.Id = 0
inbound.UserId = user.Id inbound.UserId = user.Id
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
inbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
} else {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
for index := range inbound.ClientStats { for index := range inbound.ClientStats {
inbound.ClientStats[index].Id = 0 inbound.ClientStats[index].Id = 0

View File

@@ -106,7 +106,7 @@
</template> </template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker> :dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker>
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme" <persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker> value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> <a-form-item v-if="clientsBulkModal.expiryTime != 0">

View File

@@ -3,7 +3,7 @@
<div> <div>
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker" <a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();" @input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
placeholder="انتخاب تاریخ"> :placeholder="placeholder">
<template #addonAfter> <template #addonAfter>
<a-icon type="calendar" style="font-size: 16px;"/> <a-icon type="calendar" style="font-size: 16px;"/>
</template> </template>
@@ -21,7 +21,7 @@
const persianDatepicker = {}; const persianDatepicker = {};
Vue.component('persian-datepicker', { Vue.component('persian-datepicker', {
props: ['dropdown-class-name', 'format', 'value'], props: ['placeholder', 'format', 'value'],
template: `{{template "component/persianDatepickerTemplate"}}`, template: `{{template "component/persianDatepickerTemplate"}}`,
data() { data() {
return { return {
@@ -48,7 +48,7 @@
listenToDatepicker() { listenToDatepicker() {
jalaliDatepicker.startWatch({ jalaliDatepicker.startWatch({
time: true, time: true,
container: '.ant-modal-wrap', zIndex: '9999',
hideAfterChange: true, hideAfterChange: true,
useDropDownYears: false, useDropDownYears: false,
changeMonthRotateYear: true, changeMonthRotateYear: true,

View File

@@ -11,6 +11,7 @@
function createThemeSwitcher() { function createThemeSwitcher() {
const isDarkTheme = localStorage.getItem('dark-mode') === 'true'; const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
const theme = isDarkTheme ? 'dark' : 'light'; const theme = isDarkTheme ? 'dark' : 'light';
document.querySelector('body').setAttribute('class', theme)
return { return {
isDarkTheme, isDarkTheme,
get currentTheme() { get currentTheme() {
@@ -19,6 +20,7 @@
toggleTheme() { toggleTheme() {
this.isDarkTheme = !this.isDarkTheme; this.isDarkTheme = !this.isDarkTheme;
localStorage.setItem('dark-mode', this.isDarkTheme); localStorage.setItem('dark-mode', this.isDarkTheme);
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light')
}, },
}; };
} }

View File

@@ -152,7 +152,7 @@
</template> </template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker> :dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme" <persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker> value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag> <a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item> </a-form-item>

View File

@@ -57,8 +57,8 @@
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker> v-model="dbInbound._expiryTime"></a-date-picker>
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme" <persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker> value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker>
</a-form-item> </a-form-item>
</a-form> </a-form>

View File

@@ -134,10 +134,28 @@
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'> <a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input> <a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.publicKey" }}
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.publicKey"></a-input> <a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input> <a-input v-model.trim="peer.psk"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

View File

@@ -32,10 +32,28 @@
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)" <a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/> style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.publicKey" }}
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.publicKey"></a-input> <a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'> <a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input> <a-input v-model.trim="peer.psk"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

View File

@@ -230,7 +230,7 @@
</template> </template>
<a-menu-item key="clipboard"> <a-menu-item key="clipboard">
<a-icon type="copy"></a-icon> <a-icon type="copy"></a-icon>
{{ i18n "pages.inbounds.copyToClipboard" }} {{ i18n "pages.inbounds.exportInbound" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="resetTraffic"> <a-menu-item key="resetTraffic">
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }} <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}

View File

@@ -93,7 +93,7 @@
<a-col :sm="24" :md="12"> <a-col :sm="24" :md="12">
<a-card hoverable> <a-card hoverable>
{{ i18n "pages.index.xrayStatus" }}: {{ i18n "pages.index.xrayStatus" }}:
<a-tag :color="status.xray.color">[[ status.xray.state.toUpperCase() ]]</a-tag> <a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
<a-popover v-if="status.xray.state === State.Error" <a-popover v-if="status.xray.state === State.Error"
:overlay-class-name="themeSwitcher.currentTheme"> :overlay-class-name="themeSwitcher.currentTheme">
<span slot="title" style="font-size: 12pt">An error occurred while running Xray <span slot="title" style="font-size: 12pt">An error occurred while running Xray
@@ -299,12 +299,12 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button type="primary" style="margin-bottom: 10px;" <a-button type="primary" style="margin-bottom: 10px;"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log"> :href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
{{ i18n "download" }} x-ui.log {{ i18n "download" }} x-ui.log
</a-button> </a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div> <div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.formattedLogs"></div>
</a-modal> </a-modal>
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" <a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
@@ -432,14 +432,16 @@
const logModal = { const logModal = {
visible: false, visible: false,
logs: '', logs: [],
formattedLogs: '',
rows: 20, rows: 20,
level: 'info', level: 'info',
syslog: false, syslog: false,
loading: false, loading: false,
show(logs) { show(logs) {
this.visible = true; this.visible = true;
this.logs = logs? this.formatLogs(logs) : "No Record..."; this.logs = logs;
this.formattedLogs = logs.length > 0 ? this.formatLogs(logs) : "No Record...";
}, },
formatLogs(logs) { formatLogs(logs) {
let formattedLogs = ''; let formattedLogs = '';

View File

@@ -244,7 +244,7 @@
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
<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="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="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.telegramProxy"}}' desc='{{ i18n "pages.settings.telegramProxyDesc"}}' v-model="allSetting.tgBotProxy" placeholder="socks5://user:pass@host:port"></setting-list-item>
<a-list-item> <a-list-item>
<a-row style="padding: 20px"> <a-row style="padding: 20px">
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">

View File

@@ -76,7 +76,7 @@
</table> </table>
<a-divider style="margin: 10px 0;">WARP {{ i18n "pages.xray.rules.outbound" }}</a-divider> <a-divider style="margin: 10px 0;">WARP {{ i18n "pages.xray.rules.outbound" }}</a-divider>
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="{{ i18n "status" }}"> <a-form-item label='{{ i18n "status" }}'>
<template v-if="warpOutboundIndex>=0"> <template v-if="warpOutboundIndex>=0">
<a-tag color="green">{{ i18n "enabled" }}</a-tag> <a-tag color="green">{{ i18n "enabled" }}</a-tag>
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading">{{ i18n "reset" }}</a-button> <a-button @click="resetOutbound" :loading="warpModal.confirmLoading">{{ i18n "reset" }}</a-button>
@@ -113,7 +113,7 @@
}, },
async getData(){ async getData(){
this.loading(true); this.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/data'); const msg = await HttpUtil.post('/panel/xray/warp/data');
this.loading(false); this.loading(false);
if (msg.success) { if (msg.success) {
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null; this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
@@ -140,6 +140,7 @@
mtu: 1420, mtu: 1420,
secretKey: warpModal.warpData.private_key, secretKey: warpModal.warpData.private_key,
address: Object.values(config.interface.addresses), address: Object.values(config.interface.addresses),
domainStrategy: 'ForceIPv6v4',
peers: [{ peers: [{
publicKey: peer.public_key, publicKey: peer.public_key,
endpoint: peer.endpoint.host, endpoint: peer.endpoint.host,
@@ -152,7 +153,7 @@
async register(){ async register(){
warpModal.loading(true); warpModal.loading(true);
keys = Wireguard.generateKeypair(); keys = Wireguard.generateKeypair();
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys); const msg = await HttpUtil.post('/panel/xray/warp/reg',keys);
if (msg.success) { if (msg.success) {
resp = JSON.parse(msg.obj); resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data; warpModal.warpData = resp.data;
@@ -163,7 +164,7 @@
}, },
async updateLicense(l){ async updateLicense(l){
warpModal.loading(true); warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l}); const msg = await HttpUtil.post('/panel/xray/warp/license',{license: l});
if (msg.success) { if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj); warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null; warpModal.warpConfig = null;
@@ -173,7 +174,7 @@
}, },
async getConfig(){ async getConfig(){
warpModal.loading(true); warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/config'); const msg = await HttpUtil.post('/panel/xray/warp/config');
warpModal.loading(false); warpModal.loading(false);
if (msg.success) { if (msg.success) {
warpModal.warpConfig = JSON.parse(msg.obj); warpModal.warpConfig = JSON.parse(msg.obj);

View File

@@ -228,6 +228,7 @@
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
</template> </template>
<a-button v-else style="margin: 10px 0;" @click="showWarp">WARP {{ i18n "pages.xray.rules.outbound" }}</a-button>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
</a-tab-pane> </a-tab-pane>
@@ -568,9 +569,11 @@
familyProtectDNS: { familyProtectDNS: {
"servers": [ "servers": [
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/ "1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
"1.0.0.3" "1.0.0.3",
"2606:4700:4700::1113",
"2606:4700:4700::1003"
], ],
"queryStrategy": "UseIPv4" "queryStrategy": "UseIP"
}, },
} }
}, },
@@ -1121,30 +1124,6 @@
this.templateRuleSetter({ outboundTag: "warp", property: "domain", data: newValue }); this.templateRuleSetter({ outboundTag: "warp", property: "domain", data: newValue });
} }
}, },
manualBlockedIPs: {
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
},
manualBlockedDomains: {
get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
},
manualDirectIPs: {
get: function () { return JSON.stringify(this.directIPs, null, 2); },
set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
},
manualDirectDomains: {
get: function () { return JSON.stringify(this.directDomains, null, 2); },
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
},
manualIPv4Domains: {
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
},
manualWARPDomains: {
get: function () { return JSON.stringify(this.warpDomains, null, 2); },
set: debounce(function (value) { this.warpDomains = JSON.parse(value); }, 1000)
},
torrentSettings: { torrentSettings: {
get: function () { get: function () {
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols); return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);

View File

@@ -16,17 +16,18 @@ import (
"x-ui/xray" "x-ui/xray"
) )
type CheckClientIpJob struct{} type CheckClientIpJob struct {
disAllowedIps []string
}
var job *CheckClientIpJob var job *CheckClientIpJob
var disAllowedIps []string
var ipFiles = []string{ var ipFiles = []string{
xray.GetIPLimitLogPath(), xray.GetIPLimitLogPath(),
xray.GetIPLimitPrevLogPath(), xray.GetIPLimitPrevLogPath(),
xray.GetIPLimitBannedLogPath(), xray.GetIPLimitBannedLogPath(),
xray.GetIPLimitBannedPrevLogPath(), xray.GetIPLimitBannedPrevLogPath(),
xray.GetAccessPersistentLogPath(), xray.GetAccessPersistentLogPath(),
xray.GetAccessPersistentPrevLogPath(), xray.GetAccessPersistentPrevLogPath(),
} }
func NewCheckClientIpJob() *CheckClientIpJob { func NewCheckClientIpJob() *CheckClientIpJob {
@@ -130,7 +131,6 @@ func (j *CheckClientIpJob) processLogFile() {
} }
} }
disAllowedIps = []string{}
shouldCleanLog := false shouldCleanLog := false
for clientEmail, ips := range InboundClientIps { for clientEmail, ips := range InboundClientIps {
@@ -237,6 +237,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
json.Unmarshal([]byte(inbound.Settings), &settings) json.Unmarshal([]byte(inbound.Settings), &settings)
clients := settings["clients"] clients := settings["clients"]
shouldCleanLog := false shouldCleanLog := false
j.disAllowedIps = []string{}
// create iplimit log file channel // create iplimit log file channel
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
@@ -255,7 +256,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
shouldCleanLog = true shouldCleanLog = true
if limitIp < len(ips) && inbound.Enable { if limitIp < len(ips) && inbound.Enable {
disAllowedIps = append(disAllowedIps, ips[limitIp:]...) j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
for i := limitIp; i < len(ips); i++ { for i := limitIp; i < len(ips); i++ {
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i]) log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
} }
@@ -263,8 +264,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
} }
} }
} }
logger.Debug("disAllowedIps ", disAllowedIps)
sort.Strings(disAllowedIps) sort.Strings(j.disAllowedIps)
if len(j.disAllowedIps) > 0 {
logger.Debug("disAllowedIps ", j.disAllowedIps)
}
db := database.GetDB() db := database.GetDB()
err = db.Save(inboundClientIps).Error err = db.Save(inboundClientIps).Error

View File

@@ -24,6 +24,7 @@
], ],
"outbounds": [ "outbounds": [
{ {
"tag": "direct",
"protocol": "freedom", "protocol": "freedom",
"settings": {} "settings": {}
}, },

View File

@@ -38,9 +38,25 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
return inbounds, nil return inbounds, nil
} }
func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) { func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
db := database.GetDB() db := database.GetDB()
db = db.Model(model.Inbound{}).Where("port = ?", port) if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
db = db.Model(model.Inbound{}).Where("port = ?", port)
} else {
db = db.Model(model.Inbound{}).
Where("port = ?", port).
Where(
db.Model(model.Inbound{}).Where(
"listen = ?", listen,
).Or(
"listen = \"\"",
).Or(
"listen = \"0.0.0.0\"",
).Or(
"listen = \"::\"",
).Or(
"listen = \"::0\""))
}
if ignoreId > 0 { if ignoreId > 0 {
db = db.Where("id != ?", ignoreId) db = db.Where("id != ?", ignoreId)
} }
@@ -135,7 +151,7 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
} }
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
exist, err := s.checkPortExist(inbound.Port, 0) exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0)
if err != nil { if err != nil {
return inbound, false, err return inbound, false, err
} }
@@ -252,7 +268,7 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) {
} }
func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
exist, err := s.checkPortExist(inbound.Port, inbound.Id) exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id)
if err != nil { if err != nil {
return inbound, false, err return inbound, false, err
} }
@@ -295,7 +311,11 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing oldInbound.Sniffing = inbound.Sniffing
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
oldInbound.Tag = fmt.Sprintf("inbound-0.0.0.0:%v", inbound.Port)
} else {
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false needRestart := false
s.xrayApi.Init(p.GetAPIPort()) s.xrayApi.Init(p.GetAPIPort())
@@ -488,6 +508,10 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
} }
} }
if len(newClients) == 0 {
return false, common.NewError("no client remained in Inbound")
}
settings["clients"] = newClients settings["clients"] = newClients
newSettings, err := json.MarshalIndent(settings, "", " ") newSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {

View File

@@ -80,7 +80,7 @@ func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string
hostName, _ := os.Hostname() hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName) data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg") url := "https://api.cloudflareclient.com/v0a2158/reg"
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data))) req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil { if err != nil {

View File

@@ -54,11 +54,11 @@
"security" = "Security" "security" = "Security"
[menu] [menu]
"dashboard" = "OVERVIEW" "dashboard" = "Overview"
"inbounds" = "INBOUNDS" "inbounds" = "Inbounds"
"settings" = "PANEL SETTINGS" "settings" = "Panel Settings"
"xray" = "XRAY CONFIGS" "xray" = "Xray Configs"
"logout" = "LOG OUT" "logout" = "Log Out"
"link" = "Manage" "link" = "Manage"
[pages.login] [pages.login]
@@ -178,7 +178,7 @@
"info" = "Info" "info" = "Info"
"same" = "Same" "same" = "Same"
"inboundData" = "Inbound's Data" "inboundData" = "Inbound's Data"
"copyToClipboard" = "Copy to Clipboard" "exportInbound" = "Export Inbound"
"import" = "Import" "import" = "Import"
"importInbound" = "Import an Inbound" "importInbound" = "Import an Inbound"
@@ -248,6 +248,7 @@
"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)" "pageSizeDesc" = "Define page size for inbounds table. (0 = disable)"
"remarkModel" = "Remark Model & Separation Character" "remarkModel" = "Remark Model & Separation Character"
"datepicker" = "Calendar Type" "datepicker" = "Calendar Type"
"datepickerPlaceholder" = "Select date"
"datepickerDescription" = "Scheduled tasks will run based on this calendar." "datepickerDescription" = "Scheduled tasks will run based on this calendar."
"sampleRemark" = "Sample Remark" "sampleRemark" = "Sample Remark"
"oldUsername" = "Current Username" "oldUsername" = "Current Username"
@@ -319,7 +320,7 @@
"ipv4Configs" = "IPv4 Routing" "ipv4Configs" = "IPv4 Routing"
"ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4." "ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4."
"warpConfigs" = "WARP Routing" "warpConfigs" = "WARP Routing"
"warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP. (follow the guide on the Panels GitHub)" "warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP."
"Template" = "Advanced Xray Configuration Template" "Template" = "Advanced Xray Configuration Template"
"TemplateDesc" = "The final Xray config file will be generated based on this template." "TemplateDesc" = "The final Xray config file will be generated based on this template."
"FreedomStrategy" = "Freedom Protocol Strategy" "FreedomStrategy" = "Freedom Protocol Strategy"
@@ -476,7 +477,6 @@
"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>" "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>"
"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>" "helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%" "cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%"
"selectUserFailed" = "❌ Error in user selection!" "selectUserFailed" = "❌ Error in user selection!"
@@ -522,7 +522,6 @@
"yes" = "✅ Yes" "yes" = "✅ Yes"
"no" = "❌ No" "no" = "❌ No"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Close Keyboard" "closeKeyboard" = "❌ Close Keyboard"
"cancel" = "❌ Cancel" "cancel" = "❌ Cancel"
@@ -556,7 +555,6 @@
"limitTraffic" = "🚧 Traffic Limit" "limitTraffic" = "🚧 Traffic Limit"
"getBanLogs" = "Get Ban Logs" "getBanLogs" = "Get Ban Logs"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Operation successful!" "successfulOperation" = "✅ Operation successful!"
"errorOperation" = "❗ Error in operation." "errorOperation" = "❗ Error in operation."

View File

@@ -1,6 +1,6 @@
"username" = "Nombre de Usuario" "username" = "Nombre de Usuario"
"password" = "Contraseña" "password" = "Contraseña"
"login" = "Acceso" "login" = "Acceder"
"confirm" = "Confirmar" "confirm" = "Confirmar"
"cancel" = "Cancelar" "cancel" = "Cancelar"
"close" = "Cerrar" "close" = "Cerrar"
@@ -59,10 +59,10 @@
"settings" = "Configuraciones" "settings" = "Configuraciones"
"xray" = "Configuración Xray" "xray" = "Configuración Xray"
"logout" = "Cerrar Sesión" "logout" = "Cerrar Sesión"
"link" = "Otro" "link" = "Gestionar"
[pages.login] [pages.login]
"title" = "Grata" "title" = "Bienvenido"
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente." "loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
[pages.login.toasts] [pages.login.toasts]
@@ -178,7 +178,7 @@
"info" = "Info" "info" = "Info"
"same" = "misma" "same" = "misma"
"inboundData" = "Datos de entrada" "inboundData" = "Datos de entrada"
"copyToClipboard" = "Copiar al portapapeles" "exportInbound" = "Exportación entrante"
"import" = "Importar" "import" = "Importar"
"importInbound" = "Importar un entrante" "importInbound" = "Importar un entrante"
@@ -248,6 +248,7 @@
"pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar" "pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar"
"remarkModel" = "Modelo de observación y carácter de separación" "remarkModel" = "Modelo de observación y carácter de separación"
"datepicker" = "selector de fechas" "datepicker" = "selector de fechas"
"datepickerPlaceholder" = "Seleccionar fecha"
"datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento" "datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento"
"sampleRemark" = "Observación de muestra" "sampleRemark" = "Observación de muestra"
"oldUsername" = "Nombre de Usuario Actual" "oldUsername" = "Nombre de Usuario Actual"

View File

@@ -178,7 +178,7 @@
"info" = "اطلاعات" "info" = "اطلاعات"
"same" = "همسان" "same" = "همسان"
"inboundData" = "داده‌های ورودی" "inboundData" = "داده‌های ورودی"
"copyToClipboard" = "کپی در حافظه" "exportInbound" = "استخراج ورودی"
"import" = "افزودن" "import" = "افزودن"
"importInbound" = "افزودن یک ورودی" "importInbound" = "افزودن یک ورودی"
@@ -248,6 +248,7 @@
"pageSizeDesc" = "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال" "pageSizeDesc" = "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال"
"remarkModel" = "نام‌کانفیگ و جداکننده" "remarkModel" = "نام‌کانفیگ و جداکننده"
"datepicker" = "نوع تقویم" "datepicker" = "نوع تقویم"
"datepickerPlaceholder" = "انتخاب تاریخ"
"datepickerDescription" = "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود" "datepickerDescription" = "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود"
"sampleRemark" = "نمونه‌نام" "sampleRemark" = "نمونه‌نام"
"oldUsername" = "نام‌کاربری فعلی" "oldUsername" = "نام‌کاربری فعلی"
@@ -501,8 +502,8 @@
"time" = "⏰ زمان: {{ .Time }}\r\n" "time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n" "inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n" "port" = "🔌 پورت: {{ .Port }}\r\n"
"expire" = "📅 تاریخ‌انقضا: {{ .DateTime }}\r\n \r\n" "expire" = "📅 تاریخ‌انقضا: {{ .Time }}\r\n\r\n"
"expireIn" = "📅 باقی‌مانده‌تاانقضا: {{ .Time }}\r\n \r\n" "expireIn" = "📅 باقی‌مانده‌تاانقضا: {{ .Time }}\r\n\r\n"
"active" = "💡 فعال: {{ .Enable }}\r\n" "active" = "💡 فعال: {{ .Enable }}\r\n"
"enabled" = "🚨 وضعیت: {{ .Enable }}\r\n" "enabled" = "🚨 وضعیت: {{ .Enable }}\r\n"
"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n" "online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n"
@@ -515,7 +516,7 @@
"exhaustedCount" = "🚨 تعداد {{ .Type }} به‌اتمام‌رسیده‌است:\r\n" "exhaustedCount" = "🚨 تعداد {{ .Type }} به‌اتمام‌رسیده‌است:\r\n"
"onlinesCount" = "🌐 کاربران‌آنلاین: {{ .Count }}\r\n" "onlinesCount" = "🌐 کاربران‌آنلاین: {{ .Count }}\r\n"
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n" "disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 به‌زودی‌به‌پایان‌خواهدرسید: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 به‌زودی‌به‌پایان‌خواهدرسید: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 زمان‌پشتیبان‌گیری: {{ .Time }}\r\n" "backupTime" = "🗄 زمان‌پشتیبان‌گیری: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n" "refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n"
"yes" = "✅ بله" "yes" = "✅ بله"
@@ -537,7 +538,6 @@
"clientUsage" = "دریافت آمار کاربر" "clientUsage" = "دریافت آمار کاربر"
"onlines" = "کاربران آنلاین" "onlines" = "کاربران آنلاین"
"commands" = "دستورات" "commands" = "دستورات"
"refresh" = "🔄 تازه‌سازی" "refresh" = "🔄 تازه‌سازی"
"clearIPs" = "❌ پاک‌سازی آدرس‌ها" "clearIPs" = "❌ پاک‌سازی آدرس‌ها"
"removeTGUser" = "❌ حذف کاربر تلگرام" "removeTGUser" = "❌ حذف کاربر تلگرام"

View File

@@ -178,7 +178,7 @@
"info" = "Информация" "info" = "Информация"
"same" = "Тот же" "same" = "Тот же"
"inboundData" = "Входящие данные" "inboundData" = "Входящие данные"
"copyToClipboard" = "Копировать в буфер обмена" "exportInbound" = "Экспорт входящих"
"import" = "Импортировать" "import" = "Импортировать"
"importInbound" = "Импортировать входящее сообщение" "importInbound" = "Импортировать входящее сообщение"
@@ -248,6 +248,7 @@
"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить" "pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить"
"remarkModel" = "Модель примечания и символ разделения" "remarkModel" = "Модель примечания и символ разделения"
"datepicker" = "выбор даты" "datepicker" = "выбор даты"
"datepickerPlaceholder" = "Выберите дату"
"datepickerDescription" = "Тип календаря выбора указывает дату истечения срока действия." "datepickerDescription" = "Тип календаря выбора указывает дату истечения срока действия."
"sampleRemark" = "Пример замечания" "sampleRemark" = "Пример замечания"
"oldUsername" = "Текущее имя пользователя" "oldUsername" = "Текущее имя пользователя"
@@ -503,7 +504,7 @@
"port" = "🔌 Порт: {{ .Port }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n"
"expire" = "📅 Дата окончания: {{ .Time }}\r\n" "expire" = "📅 Дата окончания: {{ .Time }}\r\n"
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n" "expireIn" = "📅 Окончание через: {{ .Time }}\r\n"
"active" = "💡 Активен: ✅ Да\r\n" "active" = "💡 Активен: {{ .Enable }}\r\n"
"enabled" = "🚨 Включен: {{ .Enable }}\r\n" "enabled" = "🚨 Включен: {{ .Enable }}\r\n"
"online" = "🌐 Статус соединения: {{ .Status }}\r\n" "online" = "🌐 Статус соединения: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"

View File

@@ -32,7 +32,7 @@
"transmission" = "Truyền tải" "transmission" = "Truyền tải"
"host" = "Máy chủ" "host" = "Máy chủ"
"path" = "Đường dẫn" "path" = "Đường dẫn"
"camouflage" = "camouflage" "camouflage" = "Ngụy trang"
"status" = "Trạng thái" "status" = "Trạng thái"
"enabled" = "Đã kích hoạt" "enabled" = "Đã kích hoạt"
"disabled" = "Đã tắt" "disabled" = "Đã tắt"
@@ -59,7 +59,7 @@
"settings" = "Cài đặt bảng điều khiển" "settings" = "Cài đặt bảng điều khiển"
"logout" = "Đăng xuất" "logout" = "Đăng xuất"
"xray" = "Cài đặt Xray" "xray" = "Cài đặt Xray"
"link" = "sự quản lý" "link" = "Quản lý"
[pages.login] [pages.login]
"title" = "Chào mừng" "title" = "Chào mừng"
@@ -74,9 +74,9 @@
[pages.index] [pages.index]
"title" = "Trạng thái hệ thống" "title" = "Trạng thái hệ thống"
"memory" = "Bộ nhớ" "memory" = "Ram"
"hard" = "Dung lượng" "hard" = "Dung lượng"
"xrayStatus" = "Trạng thái" "xrayStatus" = "Trạng thái Xray"
"stopXray" = "Dừng lại" "stopXray" = "Dừng lại"
"restartXray" = "Khởi động lại" "restartXray" = "Khởi động lại"
"xraySwitch" = "Phiên bản" "xraySwitch" = "Phiên bản"
@@ -125,8 +125,8 @@
"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)" "modifyInbound" = "Chỉnh sửa điểm vào (Inbound)"
"deleteInbound" = "Xóa điểm vào (Inbound)" "deleteInbound" = "Xóa điểm vào (Inbound)"
"deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)" "deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)"
"deleteClient" = "Xóa khách hàng" "deleteClient" = "Xóa người dùng"
"deleteClientContent" = "Bạn có chắc chắn muốn xóa ng dng khách không?" "deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?"
"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?" "resetTrafficContent" = "Xác nhận đặt lại lưu lượng?"
"copyLink" = "Sao chép liên kết" "copyLink" = "Sao chép liên kết"
"address" = "Địa chỉ" "address" = "Địa chỉ"
@@ -145,7 +145,7 @@
"keyPath" = "Đường dẫn khóa riêng tư" "keyPath" = "Đường dẫn khóa riêng tư"
"keyContent" = "Nội dung khóa riêng tư" "keyContent" = "Nội dung khóa riêng tư"
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép" "clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
"client" = "Máy Khách" "client" = "Người dùng"
"export" = "Xuất liên kết" "export" = "Xuất liên kết"
"clone" = "Sao chép" "clone" = "Sao chép"
"cloneInbound" = "Sao chép điểm vào (Inbound)" "cloneInbound" = "Sao chép điểm vào (Inbound)"
@@ -154,15 +154,15 @@
"resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào" "resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào"
"resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào" "resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào"
"resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?" "resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?"
"resetInboundClientTraffics" = "Đặt lại lưu lượng cho các client của điểm vào" "resetInboundClientTraffics" = "Đặt lại lưu lượng toàn bộ người dùng của điểm vào"
"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả lưu lượng của client" "resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng của điểm vào"
"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các client của điểm vào này không?" "resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các người dùng của điểm vào này không?"
"resetAllClientTraffics" = "Đặt lại lưu lượng cho tất cả client" "resetAllClientTraffics" = "Đặt lại lưu lượng cho toàn bộ người dùng"
"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả client" "resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng"
"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho tất cả client không?" "resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho toàn bộ người dùng không?"
"delDepletedClients" = "Xóa các client đã cạn kiệt" "delDepletedClients" = "Xóa các người dùng đã cạn kiệt"
"delDepletedClientsTitle" = "Xóa các client đã cạn kiệt" "delDepletedClientsTitle" = "Xóa các người dùng đã cạn kiệt"
"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa tất cả các client đã cạn kiệt không?" "delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa toàn bộ người dùng đã cạn kiệt không?"
"email" = "Email" "email" = "Email"
"emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất." "emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất."
"IPLimit" = "Giới hạn IP" "IPLimit" = "Giới hạn IP"
@@ -174,20 +174,20 @@
"xtlsDesc" = "Xray core cần phiên bản 1.7.5" "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." "realityDesc" = "Xray core cần phiên bản 1.8.0 hoặc cao hơn."
"telegramDesc" = "Chỉ sử dụng ID trò chuyện (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)" "telegramDesc" = "Chỉ sử dụng ID trò chuyện (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)"
"subscriptionDesc" = "Bạn có thể tìm liên kết đă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" "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" "info" = "Thông tin"
"same" = "Giống nhau" "same" = "Giống nhau"
"inboundData" = "Dữ liệu gửi đến" "inboundData" = "Dữ liệu gửi đến"
"copyToClipboard" = "Sao chép vào bảng nhớ tạm" "exportInbound" = "Xuất nhập khẩu"
"import" = "Nhập" "import" = "Nhập"
"importInbound" = "Nhập hàng gửi về" "importInbound" = "Nhập inbound"
[pages.client] [pages.client]
"add" = "Thêm Client" "add" = "Thêm người dùng"
"edit" = "Chỉnh sửa Client" "edit" = "Chỉnh sửa người dùng"
"submitAdd" = "Thêm Client" "submitAdd" = "Thêm"
"submitEdit" = "Lưu thay đổi" "submitEdit" = "Lưu thay đổi"
"clientCount" = "Số lượng Client" "clientCount" = "Số lượng người dùng"
"bulk" = "Thêm hàng loạt" "bulk" = "Thêm hàng loạt"
"method" = "Phương pháp" "method" = "Phương pháp"
"first" = "Đầu tiên" "first" = "Đầu tiên"
@@ -212,11 +212,11 @@
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "Phiên bản" "version" = "Phiên bản"
"method" = "Phương pháp" "method" = "Phương pháp"
"path" = "Con đường" "path" = "Đường dẫn"
"status" = "Trạng thái" "status" = "Trạng thái"
"statusDescription" = "Tình trạng Mô tả" "statusDescription" = "Tình trạng Mô tả"
"requestHeader" = "Tiêu đề yêu cầu" "requestHeader" = "Header yêu cầu"
"responseHeader" = "Tiêu đề phản hồi" "responseHeader" = "Header phản hồi"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "Mã hóa" "encryption" = "Mã hóa"
@@ -225,30 +225,31 @@
"title" = "Cài đặt" "title" = "Cài đặt"
"save" = "Lưu" "save" = "Lưu"
"infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi." "infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi."
"restartPanel" = "Khởi động lại Bảng điều khiển" "restartPanel" = "Khởi động lại bảng điều khiển"
"restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ." "restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ."
"actions" = "Hành động" "actions" = "Hành động"
"resetDefaultConfig" = "Đặt lại Cấu hình Mặc định" "resetDefaultConfig" = "Đặt lại cấu hình mặc định"
"panelSettings" = "Cài đặt Bảng điều khiển" "panelSettings" = "Bảng điều khiển"
"securitySettings" = "Cài đặt Bảo mật" "securitySettings" = "Bảo mật"
"TGBotSettings" = "Cài đặt Bot Telegram" "TGBotSettings" = "Bot Telegram"
"panelListeningIP" = "IP Nghe của Bảng điều khiển" "panelListeningIP" = "IP Nghe của bảng điều khiển"
"panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP." "panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP."
"panelListeningDomain" = "Tên miền của nghe Bảng điều khiển" "panelListeningDomain" = "Tên miền của nghe bảng điều khiển"
"panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" "panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
"panelPort" = "Cổng Bảng điều khiển" "panelPort" = "Cổng bảng điều khiển"
"panelPortDesc" = "Cổng được sử dụng để hiển thị bảng điều khiển này" "panelPortDesc" = "Cổng được sử dụng để kết nối với bảng điều khiển này"
"publicKeyPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Bảng điều khiển" "publicKeyPath" = "Đường dẫn file chứng chỉ bảng điều khiển"
"publicKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với." "publicKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')"
"privateKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Bảng điều khiển" "privateKeyPath" = "Đường dẫn file khóa của chứng chỉ bảng điều khiển"
"privateKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với." "privateKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')"
"panelUrlPath" = "Đường dẫn gốc URL Bảng điều khiển" "panelUrlPath" = "Đường dẫn gốc URL bảng điều khiển"
"panelUrlPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng." "panelUrlPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"pageSize" = "Kích thước phân trang" "pageSize" = "Kích thước phân trang"
"pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt" "pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt"
"remarkModel" = "Ghi chú mô hình và ký tự phân tách" "remarkModel" = "Ghi chú mô hình và ký tự phân tách"
"datepicker" = "bảng chọn ngày" "datepicker" = "Kiểu lịch"
"datepickerDescription" = "Loại lịch chọn chỉ định ngày hết hạn" "datepickerPlaceholder" = "Chọn ngày"
"datepickerDescription" = "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này."
"sampleRemark" = "Nhận xét mẫu" "sampleRemark" = "Nhận xét mẫu"
"oldUsername" = "Tên người dùng hiện tại" "oldUsername" = "Tên người dùng hiện tại"
"currentPassword" = "Mật khẩu hiện tại" "currentPassword" = "Mật khẩu hiện tại"
@@ -268,7 +269,7 @@
"tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo." "tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo."
"tgNotifyLogin" = "Thông báo Đăng nhập" "tgNotifyLogin" = "Thông báo Đăng nhập"
"tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn." "tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn."
"sessionMaxAge" = "Tuổi tối đa của phiên" "sessionMaxAge" = "Thời gian tối đa của phiên"
"sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)" "sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)"
"expireTimeDiff" = "Ngưỡng hết hạn cho thông báo" "expireTimeDiff" = "Ngưỡng hết hạn cho thông báo"
"expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)" "expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)"
@@ -278,29 +279,29 @@
"tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)" "tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)"
"timeZone" = "Múi giờ" "timeZone" = "Múi giờ"
"timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này." "timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này."
"subSettings" = "Đăng ký" "subSettings" = "Gói đăng ký"
"subEnable" = "Bật dịch vụ" "subEnable" = "Bật dịch vụ"
"subEnableDesc" = "Tính năng đăng ký với cấu hình riêng" "subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
"subListen" = "Listening IP" "subListen" = "Listening IP"
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP" "subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
"subPort" = "Cổng Đăng ký" "subPort" = "Cổng gói đăng ký"
"subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ" "subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ"
"subCertPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Đăng ký" "subCertPath" = "Đường dẫn file chứng chỉ gói đăng ký"
"subCertPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'" "subCertPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')"
"subKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Đăng ký" "subKeyPath" = "Đường dẫn file khóa của chứng chỉ gói đăng ký"
"subKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'" "subKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')"
"subPath" = "Đường dẫn gốc URL Đăng ký" "subPath" = "Đường dẫn gốc URL gói đăng ký"
"subPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng '/'" "subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"subDomain" = "Tên miền con" "subDomain" = "Tên miền con"
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" "subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
"subUpdates" = "Khoảng thời gian cập nhật đăng ký" "subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách" "subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
"subEncrypt" = "Mã hóa cấu hình" "subEncrypt" = "Mã hóa cấu hình"
"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong đăng ký" "subEncryptDesc" = "Mã hóa các cấu hình được trả về trong gói đăng ký"
"subShowInfo" = "Hiển thị thông tin sử dụng" "subShowInfo" = "Hiển thị thông tin sử dụng"
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình" "subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
"subURI" = "URI proxy ngược" "subURI" = "URI proxy trung gian"
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy" "subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
[pages.xray] [pages.xray]
"title" = "Cài đặt Xray" "title" = "Cài đặt Xray"
@@ -422,7 +423,7 @@
"intercon" = "Kết nối" "intercon" = "Kết nối"
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Chìa khoá bí mật" "secretKey" = "Khoá bí mật"
"publicKey" = "Khóa công khai" "publicKey" = "Khóa công khai"
"allowedIPs" = "IP được phép" "allowedIPs" = "IP được phép"
"endpoint" = "Điểm cuối" "endpoint" = "Điểm cuối"
@@ -434,8 +435,8 @@
"secret" = "Mã thông báo bí mật" "secret" = "Mã thông báo bí mật"
"loginSecurity" = "Bảo mật đăng nhập" "loginSecurity" = "Bảo mật đăng nhập"
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng" "loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
"secretToken" = "Mã thông báo bí mật" "secretToken" = "Mã bí mật"
"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã thông báo này một cách an toàn ở nơi an toàn. Mã thông báo này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui." "secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã này một cách an toàn ở nơi an toàn. Mã này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "Chỉnh sửa cài đặt " "modifySettings" = "Chỉnh sửa cài đặt "
@@ -460,7 +461,7 @@
"hours" = "Giờ" "hours" = "Giờ"
"unknown" = "Không rõ" "unknown" = "Không rõ"
"inbounds" = "Vào" "inbounds" = "Vào"
"clients" = "Các khách hàng" "clients" = "Các người dùng"
"offline" = "🔴 Ngoại tuyến" "offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến" "online" = "🟢 Trực tuyến"

View File

@@ -178,7 +178,7 @@
"info" = "信息" "info" = "信息"
"same" = "相同" "same" = "相同"
"inboundData" = "入站数据" "inboundData" = "入站数据"
"copyToClipboard" = "复制到剪贴板" "exportInbound" = "出口 入境"
"import"="导入" "import"="导入"
"importInbound" = "导入入站" "importInbound" = "导入入站"
@@ -248,6 +248,7 @@
"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" "pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用"
"remarkModel" = "备注模型和分隔符" "remarkModel" = "备注模型和分隔符"
"datepicker" = "日期选择器" "datepicker" = "日期选择器"
"datepickerPlaceholder" = "选择日期"
"datepickerDescription" = "选择器日历类型指定到期日期" "datepickerDescription" = "选择器日历类型指定到期日期"
"sampleRemark" = "备注示例" "sampleRemark" = "备注示例"
"oldUsername" = "原用户名" "oldUsername" = "原用户名"
@@ -503,7 +504,7 @@
"port" = "🔌 端口:{{ .Port }}\r\n" "port" = "🔌 端口:{{ .Port }}\r\n"
"expire" = "📅 过期日期:{{ .Time }}\r\n" "expire" = "📅 过期日期:{{ .Time }}\r\n"
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n" "expireIn" = "📅 剩余时间:{{ .Time }}\r\n"
"active" = "💡 激活:\r\n" "active" = "💡 激活:{{ .Enable }}\r\n"
"enabled" = "🚨 已启用:{{ .Enable }}\r\n" "enabled" = "🚨 已启用:{{ .Enable }}\r\n"
"online" = "🌐 连接状态:{{ .Status }}\r\n" "online" = "🌐 连接状态:{{ .Status }}\r\n"
"email" = "📧 邮箱:{{ .Email }}\r\n" "email" = "📧 邮箱:{{ .Email }}\r\n"

395
x-ui.sh
View File

@@ -70,13 +70,11 @@ elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian" echo "Your OS is Armbian"
fi fi
# Declare Variables # Declare Variables
log_folder="${XUI_LOG_FOLDER:=/var/log}" log_folder="${XUI_LOG_FOLDER:=/var/log}"
iplimit_log_path="${log_folder}/3xipl.log" iplimit_log_path="${log_folder}/3xipl.log"
iplimit_banned_log_path="${log_folder}/3xipl-banned.log" iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
confirm() { confirm() {
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
echo && read -p "$1 [Default $2]: " temp echo && read -p "$1 [Default $2]: " temp
@@ -140,7 +138,7 @@ custom_version() {
if [ -z "$panel_version" ]; then if [ -z "$panel_version" ]; then
echo "Panel version cannot be empty. Exiting." echo "Panel version cannot be empty. Exiting."
exit 1 exit 1
fi fi
download_link="https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh" download_link="https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh"
@@ -329,15 +327,15 @@ show_log() {
} }
show_banlog() { show_banlog() {
if test -f "${iplimit_banned_log_path}"; then if test -f "${iplimit_banned_log_path}"; then
if [[ -s "${iplimit_banned_log_path}" ]]; then if [[ -s "${iplimit_banned_log_path}" ]]; then
cat ${iplimit_banned_log_path} cat ${iplimit_banned_log_path}
else
echo -e "${red}Log file is empty.${plain}\n"
fi
else else
echo -e "${red}Log file is empty.${plain}\n" echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
fi fi
else
echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
fi
} }
enable_bbr() { enable_bbr() {
@@ -348,19 +346,19 @@ enable_bbr() {
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu|debian) ubuntu | debian)
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
;; ;;
centos|almalinux|rocky) centos | almalinux | rocky)
yum -y update && yum -y install ca-certificates yum -y update && yum -y install ca-certificates
;; ;;
fedora) fedora)
dnf -y update && dnf -y install ca-certificates dnf -y update && dnf -y install ca-certificates
;; ;;
*) *)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1 exit 1
;; ;;
esac esac
# Enable BBR # Enable BBR
@@ -581,21 +579,24 @@ ssl_cert_issue_main() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
0) 0)
show_menu ;; show_menu
1) ;;
ssl_cert_issue ;; 1)
2) ssl_cert_issue
local domain="" ;;
read -p "Please enter your domain name to revoke the certificate: " domain 2)
~/.acme.sh/acme.sh --revoke -d ${domain} local domain=""
LOGI "Certificate revoked" read -p "Please enter your domain name to revoke the certificate: " domain
;; ~/.acme.sh/acme.sh --revoke -d ${domain}
3) LOGI "Certificate revoked"
local domain="" ;;
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain 3)
~/.acme.sh/acme.sh --renew -d ${domain} --force ;; local domain=""
*) echo "Invalid choice" ;; read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
~/.acme.sh/acme.sh --renew -d ${domain} --force
;;
*) echo "Invalid choice" ;;
esac esac
} }
@@ -611,15 +612,19 @@ ssl_cert_issue() {
fi fi
# install socat second # install socat second
case "${release}" in case "${release}" in
ubuntu|debian|armbian) ubuntu | debian | armbian)
apt update && apt install socat -y ;; apt update && apt install socat -y
centos|almalinux|rocky) ;;
yum -y update && yum -y install socat ;; centos | almalinux | rocky)
fedora) yum -y update && yum -y install socat
dnf -y update && dnf -y install socat ;; ;;
*) fedora)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" dnf -y update && dnf -y install socat
exit 1 ;; ;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1
;;
esac esac
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "install socat failed, please check logs" LOGE "install socat failed, please check logs"
@@ -750,8 +755,8 @@ ssl_cert_issue_CF() {
LOGI "Certificate issued Successfully, Installing..." LOGI "Certificate issued Successfully, Installing..."
fi fi
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \ ~/.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 \ --cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
--fullchain-file /root/cert/fullchain.cer --fullchain-file /root/cert/fullchain.cer
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Certificate installation failed, script exiting..." LOGE "Certificate installation failed, script exiting..."
exit 1 exit 1
@@ -782,72 +787,46 @@ warp_cloudflare() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
0) 0)
show_menu ;; show_menu
1) ;;
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) 1)
;; bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
2) ;;
warp a 2)
;; warp a
3) ;;
warp y 3)
;; warp y
4) ;;
warp u 4)
;; warp u
*) echo "Invalid choice" ;; ;;
esac *) echo "Invalid choice" ;;
}
multi_protocol() {
echo "This script only supports Vless and Vmess. if you use another protocols, DON'T INSTALL or get backup first! "
echo -e "${green}\t1.${plain} Install Multi Protocol Script"
echo -e "${green}\t2.${plain} Uninstall"
echo -e "${green}\t3.${plain} Start Service"
echo -e "${green}\t4.${plain} Stop Service"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu ;;
1)
bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/install.sh --ipv4)
;;
2)
bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/unistall.sh --ipv4)
;;
3)
systemctl start 3xui-multi-protocol
;;
4)
systemctl stop 3xui-multi-protocol
;;
*) echo "Invalid choice" ;;
esac esac
} }
run_speedtest() { run_speedtest() {
# Check if Speedtest is already installed # Check if Speedtest is already installed
if ! command -v speedtest &> /dev/null; then if ! command -v speedtest &>/dev/null; then
# If not installed, install it # If not installed, install it
local pkg_manager="" local pkg_manager=""
local speedtest_install_script="" local speedtest_install_script=""
if command -v dnf &> /dev/null; then if command -v dnf &>/dev/null; then
pkg_manager="dnf" pkg_manager="dnf"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
elif command -v yum &> /dev/null; then elif command -v yum &>/dev/null; then
pkg_manager="yum" pkg_manager="yum"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
elif command -v apt-get &> /dev/null; then elif command -v apt-get &>/dev/null; then
pkg_manager="apt-get" pkg_manager="apt-get"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
elif command -v apt &> /dev/null; then elif command -v apt &>/dev/null; then
pkg_manager="apt" pkg_manager="apt"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
fi fi
if [[ -z $pkg_manager ]]; then if [[ -z $pkg_manager ]]; then
echo "Error: Package manager not found. You may need to install Speedtest manually." echo "Error: Package manager not found. You may need to install Speedtest manually."
return 1 return 1
@@ -862,8 +841,11 @@ run_speedtest() {
} }
create_iplimit_jails() { create_iplimit_jails() {
# Use default bantime if not passed => 5 minutes # Use default bantime if not passed => 30 minutes
local bantime="${1:-5}" local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl] [3x-ipl]
@@ -907,7 +889,7 @@ actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
[Init] [Init]
EOF EOF
echo -e "${green}Created Ip Limit jail files with a bantime of ${bantime} minutes.${plain}" echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
} }
iplimit_remove_conflicts() { iplimit_remove_conflicts() {
@@ -935,62 +917,80 @@ iplimit_main() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
0) 0)
show_menu ;; show_menu
1) ;;
confirm "Proceed with installation of Fail2ban & IP Limit?" "y" 1)
if [[ $? == 0 ]]; then confirm "Proceed with installation of Fail2ban & IP Limit?" "y"
install_iplimit if [[ $? == 0 ]]; then
else install_iplimit
iplimit_main else
fi ;; iplimit_main
2) fi
read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM ;;
if [[ $NUM =~ ^[0-9]+$ ]]; then 2)
create_iplimit_jails ${NUM} read -rp "Please enter new Ban Duration in Minutes [default 30]: " NUM
systemctl restart fail2ban if [[ $NUM =~ ^[0-9]+$ ]]; then
else create_iplimit_jails ${NUM}
echo -e "${red}${NUM} is not a number! Please, try again.${plain}" systemctl restart fail2ban
fi else
iplimit_main ;; echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
3) fi
confirm "Proceed with Unbanning everyone from IP Limit jail?" "y" iplimit_main
if [[ $? == 0 ]]; then ;;
fail2ban-client reload --restart --unban 3x-ipl 3)
echo -e "${green}All users Unbanned successfully.${plain}" confirm "Proceed with Unbanning everyone from IP Limit jail?" "y"
iplimit_main if [[ $? == 0 ]]; then
else fail2ban-client reload --restart --unban 3x-ipl
echo -e "${yellow}Cancelled.${plain}" truncate -s 0 "${iplimit_banned_log_path}"
fi echo -e "${green}All users Unbanned successfully.${plain}"
iplimit_main ;; iplimit_main
4) else
show_banlog echo -e "${yellow}Cancelled.${plain}"
;; fi
5) iplimit_main
service fail2ban status ;;
;; 4)
show_banlog
;;
5)
service fail2ban status
;;
6) 6)
remove_iplimit ;; remove_iplimit
*) echo "Invalid choice" ;; ;;
*) echo "Invalid choice" ;;
esac esac
} }
install_iplimit() { install_iplimit() {
if ! command -v fail2ban-client &>/dev/null; then if ! command -v fail2ban-client &>/dev/null; then
echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n"
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu|debian) ubuntu | debian)
apt update && apt install fail2ban -y ;; apt update && apt install fail2ban -y
centos|almalinux|rocky) ;;
yum -y update && yum -y install fail2ban ;; centos | almalinux | rocky)
fedora) yum update -y && yum install epel-release -y
dnf -y update && dnf -y install fail2ban ;; yum -y install fail2ban
*) ;;
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" fedora)
exit 1 ;; dnf -y update && dnf -y install fail2ban
;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1
;;
esac esac
if ! command -v fail2ban-client &>/dev/null; then
echo -e "${red}Fail2ban installation failed.${plain}\n"
exit 1
fi
echo -e "${green}Fail2ban installed successfully!${plain}\n" echo -e "${green}Fail2ban installed successfully!${plain}\n"
else else
echo -e "${yellow}Fail2ban is already installed.${plain}\n" echo -e "${yellow}Fail2ban is already installed.${plain}\n"
@@ -1018,6 +1018,7 @@ install_iplimit() {
# Launching fail2ban # Launching fail2ban
if ! systemctl is-active --quiet fail2ban; then if ! systemctl is-active --quiet fail2ban; then
systemctl start fail2ban systemctl start fail2ban
systemctl enable fail2ban
else else
systemctl restart fail2ban systemctl restart fail2ban
fi fi
@@ -1027,41 +1028,53 @@ install_iplimit() {
before_show_menu before_show_menu
} }
remove_iplimit(){ remove_iplimit() {
echo -e "${green}\t1.${plain} Only remove IP Limit configurations" echo -e "${green}\t1.${plain} Only remove IP Limit configurations"
echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit" echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit"
echo -e "${green}\t0.${plain} Abort" echo -e "${green}\t0.${plain} Abort"
read -p "Choose an option: " num read -p "Choose an option: " num
case "$num" in case "$num" in
1) 1)
rm -f /etc/fail2ban/filter.d/3x-ipl.conf rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.d/3x-ipl.conf rm -f /etc/fail2ban/action.d/3x-ipl.conf
rm -f /etc/fail2ban/jail.d/3x-ipl.conf rm -f /etc/fail2ban/jail.d/3x-ipl.conf
systemctl restart fail2ban systemctl restart fail2ban
echo -e "${green}IP Limit removed successfully!${plain}\n" echo -e "${green}IP Limit removed successfully!${plain}\n"
before_show_menu ;; before_show_menu
2) ;;
rm -rf /etc/fail2ban 2)
systemctl stop fail2ban rm -rf /etc/fail2ban
case "${release}" in systemctl stop fail2ban
ubuntu|debian) case "${release}" in
apt-get purge fail2ban -y;; ubuntu | debian)
centos|almalinux|rocky) apt-get remove -y fail2ban
yum remove fail2ban -y;; apt-get purge -y fail2ban -y
fedora) apt-get autoremove -y
dnf remove fail2ban -y;; ;;
*) centos | almalinux | rocky)
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" yum remove fail2ban -y
exit 1 ;; yum autoremove -y
esac ;;
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n" fedora)
before_show_menu ;; dnf remove fail2ban -y
0) dnf autoremove -y
echo -e "${yellow}Cancelled.${plain}\n" ;;
iplimit_main ;; *)
*) echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" exit 1
remove_iplimit ;; ;;
esac
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n"
before_show_menu
;;
0)
echo -e "${yellow}Cancelled.${plain}\n"
iplimit_main
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
remove_iplimit
;;
esac esac
} }
@@ -1104,22 +1117,21 @@ show_menu() {
${green}12.${plain} Check Status ${green}12.${plain} Check Status
${green}13.${plain} Check Logs ${green}13.${plain} Check Logs
———————————————— ————————————————
${green}14.${plain} Enable x-ui On System Startup ${green}14.${plain} Enable Autostart
${green}15.${plain} Disable x-ui On System Startup ${green}15.${plain} Disable Autostart
———————————————— ————————————————
${green}16.${plain} SSL Certificate Management ${green}16.${plain} SSL Certificate Management
${green}17.${plain} Cloudflare SSL Certificate ${green}17.${plain} Cloudflare SSL Certificate
${green}18.${plain} IP Limit Management ${green}18.${plain} IP Limit Management
${green}19.${plain} WARP Management ${green}19.${plain} WARP Management
${green}20.${plain} Multi Protocol Management
———————————————— ————————————————
${green}21.${plain} Enable BBR ${green}20.${plain} Enable BBR
${green}22.${plain} Update Geo Files ${green}21.${plain} Update Geo Files
${green}23.${plain} Active Firewall and open ports ${green}22.${plain} Active Firewall and open ports
${green}24.${plain} Speedtest by Ookla ${green}23.${plain} Speedtest by Ookla
" "
show_status show_status
echo && read -p "Please enter your selection [0-24]: " num echo && read -p "Please enter your selection [0-23]: " num
case "${num}" in case "${num}" in
0) 0)
@@ -1183,22 +1195,19 @@ show_menu() {
warp_cloudflare warp_cloudflare
;; ;;
20) 20)
multi_protocol
;;
21)
enable_bbr enable_bbr
;; ;;
22) 21)
update_geo update_geo
;; ;;
23) 22)
open_ports open_ports
;; ;;
24) 23)
run_speedtest run_speedtest
;; ;;
*) *)
LOGE "Please enter the correct number [0-24]" LOGE "Please enter the correct number [0-23]"
;; ;;
esac esac
} }