mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-19 17:15:49 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9ba393ce7 | ||
|
|
aa40016ec8 | ||
|
|
dc49304aa5 | ||
|
|
bb7b667467 | ||
|
|
d171850255 | ||
|
|
1207501405 | ||
|
|
2a2bf531ee | ||
|
|
9d724d34e1 | ||
|
|
618a566283 | ||
|
|
6804facabc | ||
|
|
98dd6bb949 | ||
|
|
f0e9aa0b8f | ||
|
|
68a16ef0e2 | ||
|
|
012775833a | ||
|
|
e4567a2b24 | ||
|
|
6c0775b120 | ||
|
|
9fbaede59f | ||
|
|
fd75cca266 | ||
|
|
9f904f8f47 | ||
|
|
78e1194ebb | ||
|
|
e04283c1fb | ||
|
|
a6742f395a | ||
|
|
9fba92d879 | ||
|
|
daa4354047 | ||
|
|
ec88053df0 | ||
|
|
08e259327b | ||
|
|
98384ac236 | ||
|
|
5f9058c84f | ||
|
|
979fdedbbe | ||
|
|
2463b99479 | ||
|
|
ff547a258d | ||
|
|
251ceeedba | ||
|
|
c1422be269 | ||
|
|
538fc9b365 | ||
|
|
b172d450e3 | ||
|
|
5c695ca652 | ||
|
|
e7ce8c8ddb | ||
|
|
4b9bbbc34b | ||
|
|
820e302aac | ||
|
|
ebaabf6150 | ||
|
|
a7bea936c5 | ||
|
|
38378fe36f | ||
|
|
7f13adbd05 | ||
|
|
74ba6d7825 | ||
|
|
7fd4015f17 | ||
|
|
589a8702aa | ||
|
|
91af54abf1 | ||
|
|
82244ced73 | ||
|
|
6856807726 | ||
|
|
2488adc042 | ||
|
|
62f08e877d | ||
|
|
3ed6fc4036 | ||
|
|
6550aa6bad | ||
|
|
2a20243751 |
10
.github/workflows/docker.yml
vendored
10
.github/workflows/docker.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Release 3X-UI dockerhub
|
name: Release 3X-UI for Docker
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- 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
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5.5.0
|
uses: docker/metadata-action@v5.5.1
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
@@ -36,6 +36,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
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 }}
|
||||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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 ../../
|
||||||
17
Dockerfile
17
Dockerfile
@@ -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"
|
||||||
|
|
||||||
@@ -28,11 +28,13 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache --update \
|
RUN apk add --no-cache --update \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban
|
fail2ban \
|
||||||
|
bash
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/ /app/
|
||||||
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
|
||||||
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 \
|
||||||
@@ -47,4 +49,5 @@ RUN chmod +x \
|
|||||||
/usr/bin/x-ui
|
/usr/bin/x-ui
|
||||||
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
|
CMD [ "./x-ui" ]
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
|
|||||||
153
README.md
153
README.md
@@ -25,11 +25,39 @@ 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.3`:
|
||||||
|
|
||||||
```
|
```
|
||||||
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.3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSL Certificate
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for SSL Certificate</summary>
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
|
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
||||||
|
|
||||||
|
- Cloudflare registered email
|
||||||
|
- Cloudflare Global API Key
|
||||||
|
- The domain name has been resolved to the current server through cloudflare
|
||||||
|
|
||||||
|
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
|
||||||
|
|
||||||
|
|
||||||
|
### Certbot
|
||||||
|
```
|
||||||
|
apt-get install certbot -y
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Manual Install & Upgrade
|
## Manual Install & Upgrade
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -103,6 +131,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 +164,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 +196,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
|
||||||
@@ -168,54 +229,6 @@ systemctl restart x-ui
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## SSL Certificate
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click for SSL Certificate</summary>
|
|
||||||
|
|
||||||
### Cloudflare
|
|
||||||
|
|
||||||
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
|
||||||
|
|
||||||
- Cloudflare registered email
|
|
||||||
- Cloudflare Global API Key
|
|
||||||
- The domain name has been resolved to the current server through cloudflare
|
|
||||||
|
|
||||||
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
|
|
||||||
|
|
||||||
|
|
||||||
### Certbot
|
|
||||||
```
|
|
||||||
apt-get install certbot -y
|
|
||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
|
||||||
certbot renew --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
|
|
||||||
|
|
||||||
</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:
|
||||||
|
|
||||||
@@ -268,13 +281,13 @@ If you want to use routing to WARP follow steps as below:
|
|||||||
2. Select `IP Limit Management`.
|
2. Select `IP Limit Management`.
|
||||||
3. Choose the appropriate options based on your needs.
|
3. Choose the appropriate options based on your needs.
|
||||||
|
|
||||||
- make sure you have access.log on your Xray Configuration
|
- make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
|
||||||
"access": "./access.log",
|
"access": "./access.log",
|
||||||
"error": "./error.log"
|
"dnsLog": false,
|
||||||
|
"loglevel": "warning"
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -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 +1 @@
|
|||||||
2.0.2
|
2.1.3
|
||||||
@@ -21,6 +21,7 @@ var db *gorm.DB
|
|||||||
var initializers = []func() error{
|
var initializers = []func() error{
|
||||||
initUser,
|
initUser,
|
||||||
initInbound,
|
initInbound,
|
||||||
|
initOutbound,
|
||||||
initSetting,
|
initSetting,
|
||||||
initInboundClientIps,
|
initInboundClientIps,
|
||||||
initClientTraffic,
|
initClientTraffic,
|
||||||
@@ -51,6 +52,10 @@ func initInbound() error {
|
|||||||
return db.AutoMigrate(&model.Inbound{})
|
return db.AutoMigrate(&model.Inbound{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initOutbound() error {
|
||||||
|
return db.AutoMigrate(&model.OutboundTraffics{})
|
||||||
|
}
|
||||||
|
|
||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,13 +37,22 @@ 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"`
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundTraffics struct {
|
||||||
|
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
Up int64 `json:"up" form:"up" gorm:"default:0"`
|
||||||
|
Down int64 `json:"down" form:"down" gorm:"default:0"`
|
||||||
|
Total int64 `json:"total" form:"total" gorm:"default:0"`
|
||||||
|
}
|
||||||
|
|
||||||
type InboundClientIps struct {
|
type InboundClientIps struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -8,18 +8,18 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.28.0
|
github.com/mymmrac/telego v0.28.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.3.0
|
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12
|
github.com/shirou/gopsutil/v3 v3.24.1
|
||||||
github.com/valyala/fasthttp v1.51.0
|
github.com/valyala/fasthttp v1.51.0
|
||||||
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
|
||||||
|
|||||||
21
go.sum
21
go.sum
@@ -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=
|
||||||
@@ -179,8 +179,8 @@ github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE
|
|||||||
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
@@ -230,8 +230,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJ
|
|||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@@ -369,7 +369,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -413,8 +412,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 +436,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=
|
||||||
|
|||||||
46
install.sh
46
install.sh
@@ -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
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ func Infof(format string, args ...interface{}) {
|
|||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Notice(args ...interface{}) {
|
||||||
|
logger.Notice(args...)
|
||||||
|
addToBuffer("NOTICE", fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Noticef(format string, args ...interface{}) {
|
||||||
|
logger.Noticef(format, args...)
|
||||||
|
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
|
|||||||
@@ -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": {}
|
|
||||||
}
|
|
||||||
@@ -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": {}
|
|
||||||
}
|
|
||||||
@@ -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": {}
|
|
||||||
}
|
|
||||||
@@ -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": {}
|
|
||||||
}
|
|
||||||
@@ -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": {}
|
|
||||||
}
|
|
||||||
@@ -861,13 +861,13 @@ Outbound.SocksSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
servers = json.servers;
|
let servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.SocksSettings(
|
return new Outbound.SocksSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,13 +891,13 @@ Outbound.HttpSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
servers = json.servers;
|
let servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.HttpSettings(
|
return new Outbound.HttpSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,8 +914,8 @@ 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='',
|
||||||
address='', workers=2, domainStrategy='', reserved='',
|
address=[''], workers=2, domainStrategy='', reserved='',
|
||||||
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type XraySettingController struct {
|
|||||||
XraySettingService service.XraySettingService
|
XraySettingService service.XraySettingService
|
||||||
SettingService service.SettingService
|
SettingService service.SettingService
|
||||||
InboundService service.InboundService
|
InboundService service.InboundService
|
||||||
|
OutboundService service.OutboundService
|
||||||
XrayService service.XrayService
|
XrayService service.XrayService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/getXrayResult", a.getXrayResult)
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/warp/:action", a.warp)
|
g.POST("/warp/:action", a.warp)
|
||||||
|
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
@@ -84,3 +86,12 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
|||||||
|
|
||||||
jsonObj(c, resp, err)
|
jsonObj(c, resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
|
||||||
|
outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error getting traffics", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, outboundsTraffic, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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')
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
<a-button size="small" @click="inbound.stream.ws.addHeader('host', '')">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
|
|||||||
@@ -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" }}
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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: 'ForceIP',
|
||||||
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);
|
||||||
|
|||||||
@@ -147,6 +147,40 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta
|
||||||
|
title='{{ i18n "pages.xray.logLevel" }}'
|
||||||
|
description='{{ i18n "pages.xray.logLevelDesc" }}'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="setLogLevel"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
style="width: 100%">
|
||||||
|
<a-select-option v-for="s in logLevel" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta
|
||||||
|
title='{{ i18n "pages.xray.accessLog" }}'
|
||||||
|
description='{{ i18n "pages.xray.accessLogDesc" }}'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="setAccessLog"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
style="width: 100%">
|
||||||
|
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
@@ -228,6 +262,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>
|
||||||
@@ -340,8 +375,15 @@
|
|||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-row>
|
||||||
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
|
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
|
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"/>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="outboundData"
|
:data-source="outboundData"
|
||||||
@@ -377,6 +419,9 @@
|
|||||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="traffic" slot-scope="text, outbound, index">
|
||||||
|
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
||||||
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
||||||
@@ -462,6 +507,7 @@
|
|||||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const reverseColumns = [
|
const reverseColumns = [
|
||||||
@@ -482,7 +528,9 @@
|
|||||||
oldXraySetting: '',
|
oldXraySetting: '',
|
||||||
xraySetting: '',
|
xraySetting: '',
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
|
outboundsTraffic: [],
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
|
refreshing: false,
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
@@ -520,6 +568,8 @@
|
|||||||
protocol: "freedom"
|
protocol: "freedom"
|
||||||
},
|
},
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
|
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
||||||
|
access: ["none" , "./access.log" ],
|
||||||
settingsData: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
@@ -568,9 +618,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"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -578,6 +630,12 @@
|
|||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
|
async getOutboundsTraffic() {
|
||||||
|
const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
|
||||||
|
if (msg.success) {
|
||||||
|
this.outboundsTraffic = msg.obj;
|
||||||
|
}
|
||||||
|
},
|
||||||
async getXraySetting() {
|
async getXraySetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/xray/");
|
const msg = await HttpUtil.post("/panel/xray/");
|
||||||
@@ -756,6 +814,14 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
findOutboundTraffic(o) {
|
||||||
|
for (const otraffic of this.outboundsTraffic) {
|
||||||
|
if (otraffic.tag == o.tag) {
|
||||||
|
return sizeFormat(otraffic.up) + ' / ' + sizeFormat(otraffic.down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sizeFormat(0) + ' / ' + sizeFormat(0);
|
||||||
|
},
|
||||||
findOutboundAddress(o) {
|
findOutboundAddress(o) {
|
||||||
serverObj = null;
|
serverObj = null;
|
||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
@@ -813,6 +879,22 @@
|
|||||||
outbounds.splice(index,1);
|
outbounds.splice(index,1);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
this.outboundSettings = JSON.stringify(outbounds);
|
||||||
},
|
},
|
||||||
|
async refreshOutboundTraffic() {
|
||||||
|
if (!this.refreshing) {
|
||||||
|
this.refreshing = true;
|
||||||
|
await this.getOutboundsTraffic();
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if (this.templateSettings != null) {
|
||||||
|
this.templateSettings.outbounds.forEach((o, index) => {
|
||||||
|
data.push({'key': index, ...o});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outboundData = data;
|
||||||
|
this.refreshing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
addReverse(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
@@ -946,6 +1028,7 @@
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getXraySetting();
|
await this.getXraySetting();
|
||||||
await this.getXrayResult();
|
await this.getXrayResult();
|
||||||
|
await this.getOutboundsTraffic();
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(800);
|
await PromiseUtil.sleep(800);
|
||||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
@@ -1062,6 +1145,28 @@
|
|||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setLogLevel: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
||||||
|
return this.templateSettings.log.loglevel;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.loglevel = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setAccessLog: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "none";
|
||||||
|
return this.templateSettings.log.access;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.access = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
blockedIPs: {
|
blockedIPs: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
@@ -1121,30 +1226,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);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -16,17 +18,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 {
|
||||||
@@ -91,24 +94,34 @@ func (j *CheckClientIpJob) checkFail2BanInstalled() {
|
|||||||
|
|
||||||
func (j *CheckClientIpJob) processLogFile() {
|
func (j *CheckClientIpJob) processLogFile() {
|
||||||
accessLogPath := xray.GetAccessLogPath()
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
if accessLogPath == "" {
|
|
||||||
logger.Warning("access.log doesn't exist in your config.json")
|
if accessLogPath == "none" {
|
||||||
|
logger.Warning("Access log is set to 'none' check your Xray Configs")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(accessLogPath)
|
if accessLogPath == "" {
|
||||||
InboundClientIps := make(map[string][]string)
|
logger.Warning("Access log doesn't exist in your Xray Configs")
|
||||||
j.checkError(err)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\n")
|
file, err := os.Open(accessLogPath)
|
||||||
for _, line := range lines {
|
j.checkError(err)
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
defer file.Close()
|
||||||
|
|
||||||
|
InboundClientIps := make(map[string][]string)
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
ipRegx, _ := regexp.Compile(`(\d+\.\d+\.\d+\.\d+).* accepted`)
|
||||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||||
|
|
||||||
matchesIp := ipRegx.FindString(line)
|
matches := ipRegx.FindStringSubmatch(line)
|
||||||
if len(matchesIp) > 0 {
|
if len(matches) > 1 {
|
||||||
ip := string(matchesIp)
|
ip := matches[1]
|
||||||
if ip == "127.0.0.1" || ip == "1.1.1.1" {
|
if ip == "127.0.0.1" || ip == "[::1]" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,14 +136,14 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disAllowedIps = []string{}
|
j.checkError(scanner.Err())
|
||||||
|
|
||||||
shouldCleanLog := false
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
@@ -141,7 +154,6 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
} else {
|
} else {
|
||||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||||
@@ -151,13 +163,17 @@ func (j *CheckClientIpJob) processLogFile() {
|
|||||||
// copy access log to persistent file
|
// copy access log to persistent file
|
||||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
input, err := os.ReadFile(accessLogPath)
|
|
||||||
j.checkError(err)
|
|
||||||
if _, err := logAccessP.Write(input); err != nil {
|
|
||||||
j.checkError(err)
|
|
||||||
}
|
|
||||||
defer logAccessP.Close()
|
defer logAccessP.Close()
|
||||||
|
|
||||||
|
// reopen the access log file for reading
|
||||||
|
file, err := os.Open(accessLogPath)
|
||||||
|
j.checkError(err)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// copy access log content to persistent file
|
||||||
|
_, err = io.Copy(logAccessP, file)
|
||||||
|
j.checkError(err)
|
||||||
|
|
||||||
// clean access log
|
// clean access log
|
||||||
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
@@ -237,6 +253,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 +272,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 +280,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
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
|
outboundService service.OutboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXrayTrafficJob() *XrayTrafficJob {
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
||||||
@@ -24,11 +25,15 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
logger.Warning("get xray traffic failed:", err)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("add traffic failed:", err)
|
logger.Warning("add inbound traffic failed:", err)
|
||||||
}
|
}
|
||||||
if needRestart {
|
err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("add outbound traffic failed:", err)
|
||||||
|
}
|
||||||
|
if needRestart0 || needRestart1 {
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"loglevel": "warning",
|
"access": "none",
|
||||||
"error": "./error.log"
|
"dnsLog": false,
|
||||||
|
"loglevel": "warning"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
|
"tag": "direct",
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {}
|
"settings": {}
|
||||||
},
|
},
|
||||||
@@ -42,7 +44,9 @@
|
|||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"statsInboundDownlink": true,
|
"statsInboundDownlink": true,
|
||||||
"statsInboundUplink": true
|
"statsInboundUplink": true,
|
||||||
|
"statsOutboundDownlink": true,
|
||||||
|
"statsOutboundUplink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -658,7 +682,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return needRestart, tx.Save(oldInbound).Error
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
var err error
|
var err error
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -670,7 +694,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = s.addInboundTraffic(tx, inboundTraffics)
|
err = s.addInboundTraffic(tx, traffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|||||||
80
web/service/outbound.go
Normal file
80
web/service/outbound.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutboundService struct {
|
||||||
|
xrayApi xray.XrayAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
||||||
|
var err error
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = s.addOutboundTraffic(tx, traffics)
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
if traffic.IsOutbound {
|
||||||
|
|
||||||
|
var outbound model.OutboundTraffics
|
||||||
|
|
||||||
|
err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag).
|
||||||
|
FirstOrCreate(&outbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.Tag = traffic.Tag
|
||||||
|
outbound.Up = outbound.Up + traffic.Up
|
||||||
|
outbound.Down = outbound.Down + traffic.Down
|
||||||
|
outbound.Total = outbound.Up + outbound.Down
|
||||||
|
|
||||||
|
err = tx.Save(&outbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []*model.OutboundTraffics
|
||||||
|
|
||||||
|
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return traffics, nil
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
@@ -115,14 +115,19 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
|
func (t *Tgbot) NewBot(token string, proxyUrl string) (*telego.Bot, error) {
|
||||||
if proxyUrl == "" || !strings.HasPrefix(proxyUrl, "socks5://") {
|
if proxyUrl == "" {
|
||||||
logger.Warning("invalid socks5 url, start with default")
|
// No proxy URL provided, use default instance
|
||||||
|
return telego.NewBot(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(proxyUrl, "socks5://") {
|
||||||
|
logger.Warning("Invalid socks5 URL, starting with default")
|
||||||
return telego.NewBot(token)
|
return telego.NewBot(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := url.Parse(proxyUrl)
|
_, err := url.Parse(proxyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("cant parse proxy url, use default instance for tgbot:", err)
|
logger.Warning("Can't parse proxy URL, using default instance for tgbot:", err)
|
||||||
return telego.NewBot(token)
|
return telego.NewBot(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +265,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
msg += t.I18nBot("tgbot.commands.unknown")
|
msg += t.I18nBot("tgbot.commands.unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != ""{
|
if msg != "" {
|
||||||
if onlyMessage {
|
if onlyMessage {
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
@@ -346,7 +351,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
tu.InlineKeyboardButton("40 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 40")),
|
||||||
),
|
),
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
tu.InlineKeyboardButton("50 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 50")),
|
||||||
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
tu.InlineKeyboardButton("60 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 60")),
|
||||||
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
tu.InlineKeyboardButton("80 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 80")),
|
||||||
),
|
),
|
||||||
@@ -1022,7 +1027,7 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool,
|
||||||
printDate bool, printTraffic bool, printRefreshed bool) string {
|
printDate bool, printTraffic bool, printRefreshed bool) string {
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
@@ -1380,7 +1385,6 @@ func (t *Tgbot) getExhausted(chatId int64) {
|
|||||||
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
||||||
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
||||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
||||||
|
|
||||||
|
|
||||||
if exhaustedCC > 0 {
|
if exhaustedCC > 0 {
|
||||||
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
||||||
@@ -1490,7 +1494,6 @@ func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
|||||||
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount))
|
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount))
|
||||||
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
||||||
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
||||||
|
|
||||||
|
|
||||||
if onlinesCount > 0 {
|
if onlinesCount > 0 {
|
||||||
var buttons []telego.InlineKeyboardButton
|
var buttons []telego.InlineKeyboardButton
|
||||||
@@ -1565,30 +1568,44 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|||||||
|
|
||||||
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
document := tu.Document(
|
// Check if the file is non-empty before attempting to upload
|
||||||
tu.ID(chatId),
|
fileInfo, _ := file.Stat()
|
||||||
tu.File(file),
|
if fileInfo.Size() > 0 {
|
||||||
)
|
document := tu.Document(
|
||||||
_, err = bot.SendDocument(document)
|
tu.ID(chatId),
|
||||||
if err != nil {
|
tu.File(file),
|
||||||
logger.Error("Error in uploading backup: ", err)
|
)
|
||||||
|
_, err = bot.SendDocument(document)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Warning("IPLimitBannedPrevLog file is empty, not uploading.")
|
||||||
}
|
}
|
||||||
|
file.Close()
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening db file for backup: ", err)
|
logger.Error("Error in opening IPLimitBannedPrevLog file for backup: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
document := tu.Document(
|
// Check if the file is non-empty before attempting to upload
|
||||||
tu.ID(chatId),
|
fileInfo, _ := file.Stat()
|
||||||
tu.File(file),
|
if fileInfo.Size() > 0 {
|
||||||
)
|
document := tu.Document(
|
||||||
_, err = bot.SendDocument(document)
|
tu.ID(chatId),
|
||||||
if err != nil {
|
tu.File(file),
|
||||||
logger.Error("Error in uploading config.json: ", err)
|
)
|
||||||
|
_, err = bot.SendDocument(document)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Warning("IPLimitBannedLog file is empty, not uploading.")
|
||||||
}
|
}
|
||||||
|
file.Close()
|
||||||
} else {
|
} else {
|
||||||
logger.Error("Error in opening config.json file for backup: ", err)
|
logger.Error("Error in opening IPLimitBannedLog file for backup: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
if !clientTraffic.Enable {
|
if !clientTraffic.Enable {
|
||||||
clients = RemoveIndex(clients, index-indexDecrease)
|
clients = RemoveIndex(clients, index-indexDecrease)
|
||||||
indexDecrease++
|
indexDecrease++
|
||||||
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
|
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -308,8 +309,8 @@
|
|||||||
"restart" = "Restart Xray"
|
"restart" = "Restart Xray"
|
||||||
"basicTemplate" = "Basics"
|
"basicTemplate" = "Basics"
|
||||||
"advancedTemplate" = "Advanced"
|
"advancedTemplate" = "Advanced"
|
||||||
"generalConfigs" = "General Strategy"
|
"generalConfigs" = "General"
|
||||||
"generalConfigsDesc" = "These options will determine general strategy adjustments."
|
"generalConfigsDesc" = "These options will determine general adjustments."
|
||||||
"blockConfigs" = "Protection Shield"
|
"blockConfigs" = "Protection Shield"
|
||||||
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
||||||
"blockCountryConfigs" = "Block Country"
|
"blockCountryConfigs" = "Block Country"
|
||||||
@@ -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 Panel’s 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"
|
||||||
@@ -391,6 +392,10 @@
|
|||||||
"Routings" = "Routing Rules"
|
"Routings" = "Routing Rules"
|
||||||
"RoutingsDesc" = "The priority of each rule is important!"
|
"RoutingsDesc" = "The priority of each rule is important!"
|
||||||
"completeTemplate" = "All"
|
"completeTemplate" = "All"
|
||||||
|
"logLevel" = "Log Level"
|
||||||
|
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
|
||||||
|
"accessLog" = "Access Log"
|
||||||
|
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
@@ -451,7 +456,7 @@
|
|||||||
"wentWrong" = "❌ Something went wrong!"
|
"wentWrong" = "❌ Something went wrong!"
|
||||||
"noIpRecord" = "❗ No IP Record!"
|
"noIpRecord" = "❗ No IP Record!"
|
||||||
"noInbounds" = "❗ No inbound found!"
|
"noInbounds" = "❗ No inbound found!"
|
||||||
"unlimited" = "♾ Unlimited"
|
"unlimited" = "♾ Unlimited(Reset)"
|
||||||
"add" = "Add"
|
"add" = "Add"
|
||||||
"month" = "Month"
|
"month" = "Month"
|
||||||
"months" = "Months"
|
"months" = "Months"
|
||||||
@@ -476,7 +481,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 +526,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 +559,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."
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -57,12 +57,12 @@
|
|||||||
"dashboard" = "Estado del Sistema"
|
"dashboard" = "Estado del Sistema"
|
||||||
"inbounds" = "Entradas"
|
"inbounds" = "Entradas"
|
||||||
"settings" = "Configuraciones"
|
"settings" = "Configuraciones"
|
||||||
"xray" = "Configuración Xray"
|
"xray" = "Ajustes 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"
|
||||||
@@ -391,6 +392,10 @@
|
|||||||
"Routings" = "Reglas de enrutamiento"
|
"Routings" = "Reglas de enrutamiento"
|
||||||
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
||||||
"completeTemplate" = "Todos"
|
"completeTemplate" = "Todos"
|
||||||
|
"logLevel" = "Nivel de registro"
|
||||||
|
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
|
||||||
|
"accessLog" = "Registro de acceso"
|
||||||
|
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Primero"
|
"first" = "Primero"
|
||||||
|
|||||||
@@ -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" = "نامکاربری فعلی"
|
||||||
@@ -391,6 +392,10 @@
|
|||||||
"Routings" = "قوانین مسیریابی"
|
"Routings" = "قوانین مسیریابی"
|
||||||
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
||||||
"completeTemplate" = "کامل"
|
"completeTemplate" = "کامل"
|
||||||
|
"logLevel" = "سطح گزارش"
|
||||||
|
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
|
||||||
|
"accessLog" = "مسیر گزارش"
|
||||||
|
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "اولین"
|
"first" = "اولین"
|
||||||
@@ -451,7 +456,7 @@
|
|||||||
"wentWrong" = "❌ مشکلی رخ داده است!"
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||||
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
"unlimited" = "♾ نامحدود"
|
"unlimited" = "♾ - نامحدود(ریست)"
|
||||||
"add" = "اضافه کردن"
|
"add" = "اضافه کردن"
|
||||||
"month" = "ماه"
|
"month" = "ماه"
|
||||||
"months" = "ماهها"
|
"months" = "ماهها"
|
||||||
@@ -501,8 +506,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 +520,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 +542,6 @@
|
|||||||
"clientUsage" = "دریافت آمار کاربر"
|
"clientUsage" = "دریافت آمار کاربر"
|
||||||
"onlines" = "کاربران آنلاین"
|
"onlines" = "کاربران آنلاین"
|
||||||
"commands" = "دستورات"
|
"commands" = "دستورات"
|
||||||
|
|
||||||
"refresh" = "🔄 تازهسازی"
|
"refresh" = "🔄 تازهسازی"
|
||||||
"clearIPs" = "❌ پاکسازی آدرسها"
|
"clearIPs" = "❌ پاکسازی آدرسها"
|
||||||
"removeTGUser" = "❌ حذف کاربر تلگرام"
|
"removeTGUser" = "❌ حذف کاربر تلگرام"
|
||||||
|
|||||||
@@ -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" = "Текущее имя пользователя"
|
||||||
@@ -391,6 +392,10 @@
|
|||||||
"Routings" = "Правила маршрутизации"
|
"Routings" = "Правила маршрутизации"
|
||||||
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
||||||
"completeTemplate" = "Все"
|
"completeTemplate" = "Все"
|
||||||
|
"logLevel" = "Уровень журнала"
|
||||||
|
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
|
||||||
|
"accessLog" = "Журнал доступа"
|
||||||
|
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Первый"
|
"first" = "Первый"
|
||||||
@@ -503,7 +508,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"
|
||||||
|
|||||||
@@ -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 dụng 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"
|
||||||
@@ -391,6 +392,10 @@
|
|||||||
"Routings" = "Quy tắc định tuyến"
|
"Routings" = "Quy tắc định tuyến"
|
||||||
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
||||||
"completeTemplate" = "All"
|
"completeTemplate" = "All"
|
||||||
|
"logLevel" = "Mức đăng nhập"
|
||||||
|
"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại."
|
||||||
|
"accessLog" = "Nhật ký truy cập"
|
||||||
|
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Đầu tiên"
|
"first" = "Đầu tiên"
|
||||||
@@ -422,7 +427,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 +439,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 +465,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"
|
||||||
|
|
||||||
|
|||||||
@@ -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" = "原用户名"
|
||||||
@@ -391,6 +392,10 @@
|
|||||||
"Routings" = "路由规则"
|
"Routings" = "路由规则"
|
||||||
"RoutingsDesc" = "每条规则的优先级都很重要"
|
"RoutingsDesc" = "每条规则的优先级都很重要"
|
||||||
"completeTemplate" = "全部"
|
"completeTemplate" = "全部"
|
||||||
|
"logLevel" = "日志级别"
|
||||||
|
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
|
||||||
|
"accessLog" = "访问日志"
|
||||||
|
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "第一个"
|
"first" = "第一个"
|
||||||
@@ -503,7 +508,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
395
x-ui.sh
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isInbound := matchs[1] == "inbound"
|
isInbound := matchs[1] == "inbound"
|
||||||
|
isOutbound := matchs[1] == "outbound"
|
||||||
tag := matchs[2]
|
tag := matchs[2]
|
||||||
isDown := matchs[3] == "downlink"
|
isDown := matchs[3] == "downlink"
|
||||||
if tag == "api" {
|
if tag == "api" {
|
||||||
@@ -221,8 +222,9 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
traffic, ok := tagTrafficMap[tag]
|
traffic, ok := tagTrafficMap[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
traffic = &Traffic{
|
traffic = &Traffic{
|
||||||
IsInbound: isInbound,
|
IsInbound: isInbound,
|
||||||
Tag: tag,
|
IsOutbound: isOutbound,
|
||||||
|
Tag: tag,
|
||||||
}
|
}
|
||||||
tagTrafficMap[tag] = traffic
|
tagTrafficMap[tag] = traffic
|
||||||
traffics = append(traffics, traffic)
|
traffics = append(traffics, traffic)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
|||||||
// Find level in []
|
// Find level in []
|
||||||
startIndex := strings.Index(messageBody, "[")
|
startIndex := strings.Index(messageBody, "[")
|
||||||
endIndex := strings.Index(messageBody, "]")
|
endIndex := strings.Index(messageBody, "]")
|
||||||
if startIndex != -1 && endIndex != -1 {
|
if startIndex != -1 && endIndex != -1 && startIndex < endIndex {
|
||||||
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
|
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
|
||||||
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
|
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
IsInbound bool
|
IsInbound bool
|
||||||
Tag string
|
IsOutbound bool
|
||||||
Up int64
|
Tag string
|
||||||
Down int64
|
Up int64
|
||||||
|
Down int64
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user