Compare commits

...

28 commits
v2.6.5 ... main

Author SHA1 Message Date
mhsanaei
24a3411465
more list for public IP address
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
2025-08-21 14:24:25 +02:00
Alireza Ahmand
2198e7a28f
feat: Add remaining time to tgbot #3355 (#3360)
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
2025-08-17 13:43:25 +02:00
mhsanaei
6b23b416a7
minor changes 2025-08-17 13:37:49 +02:00
mhsanaei
16f53ce4c2
go v1.25 2025-08-17 12:27:21 +02:00
mhsanaei
27445b30e9
DNS outbound: Set "reject" as the default value for nonIPQuery 2025-08-17 12:22:33 +02:00
mhsanaei
3d0212c21d
fix: fail2ban on Debian 12 #1701
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
2025-08-15 13:34:02 +02:00
mhsanaei
978755960f
actions/checkout from 4 to 5
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
2025-08-14 18:41:53 +02:00
mhsanaei
9b51e9a5c5
Freedom: Add maxSplit fragment option; Add applyTo noises option 2025-08-14 18:38:56 +02:00
fgsfds
6879a8fbcb
Moved DB to same app folder on Windows (#3340)
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
* moved db to user folder on windows

* moved db to local appdata

* made getDBFolderPath func private

* added getWindowsDbPath() func

* fix

---------

Co-authored-by: mhsanaei <ho3ein.sanaei@gmail.com>
2025-08-13 23:19:59 +02:00
mhsanaei
7258841491
Update FUNDING.yml
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
2025-08-12 13:00:16 +02:00
mhsanaei
23dd80fbb0
remove unnecessary ant files 2025-08-12 12:57:02 +02:00
mhsanaei
6556884c7f
remove unnecessary vue files 2025-08-12 12:56:49 +02:00
Alireza Ahmadi
d5c532c64f fix saving sockopt
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
2025-08-09 16:07:33 +02:00
mhsanaei
ad5f774a1e
Axios v1.11.0 2025-08-09 13:46:28 +02:00
g0l4
aa285914fa
chore: update polygon token name (#3338) 2025-08-09 08:18:56 +02:00
mhsanaei
4d02756e1e
v2.6.6
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
2025-08-08 20:52:04 +02:00
Alireza Ahmadi
825d93d95f
upgrade telego (#3334) 2025-08-08 20:41:06 +02:00
mhsanaei
5ea6386815
better musl libc usage
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-08-08 19:55:24 +02:00
mhsanaei
d064e85ecd
update dependencies
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
2025-08-08 18:56:47 +02:00
mhsanaei
9fc03bd10a
remove ocspStapling 2025-08-08 18:55:52 +02:00
fgsfds
ae08a29cde
fix: Xray restarting after being manually stopped (#2896) (#3329)
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
2025-08-07 23:35:11 +05:00
mhsanaei
4f25eb230e
musl: new version 2025-08-06 23:35:20 +02:00
somebodywashere
ce72d53d1a
fix: inbounds slow loading (#3228) (#3322)
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
especially encountered on big amount of clients
2025-08-06 15:42:32 +02:00
fgsfds
5e641ff9e8
Added Update all geofiles button (#3318)
* added Update all geofiles button

* localized update all string
2025-08-06 11:20:07 +02:00
Sanaei
58898e5758
Merge pull request #3317 from MHSanaei/dekodemo_sockopt
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
add sockopt to dockodemo
2025-08-05 16:51:26 +02:00
Alireza Ahmadi
569550d5f6 add sockopt to dockodemo 2025-08-05 14:02:23 +02:00
fgsfds
419ea63dd0
Added filters to the xray logs viewer (#3314)
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
* added filters to xray logs viewer

* better freedom/blackhole tags handling

* better freedom/blackhole tags handling 2

* fix comments

* fix comments 2
2025-08-05 12:10:14 +02:00
mhsanaei
6a17285935
remove: glibc check
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
now you can install on all OS like ubuntu 20 or 18
2025-08-04 19:16:56 +02:00
66 changed files with 862 additions and 74428 deletions

2
.github/FUNDING.yml vendored
View file

@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: MHSanaei
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
submodules: true submodules: true

View file

@ -8,6 +8,7 @@ on:
branches: branches:
- main - main
paths: paths:
- '.github/workflows/release.yml'
- '**.js' - '**.js'
- '**.css' - '**.css'
- '**.html' - '**.html'
@ -34,7 +35,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
@ -42,51 +43,34 @@ jobs:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- name: Build 3x-ui - name: Build 3X-UI
run: | run: |
export CGO_ENABLED=1 export CGO_ENABLED=1
export GOOS=linux export GOOS=linux
export GOARCH=${{ matrix.platform }} export GOARCH=${{ matrix.platform }}
TOOLCHAIN_URL="" # Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
MUSL_CC_HOST="https://github.com/musl-cc/musl.cc/releases/download/v0.0.1" #http://musl.cc
case "${{ matrix.platform }}" in case "${{ matrix.platform }}" in
amd64) amd64) BOOTLIN_ARCH="x86-64" ;;
TOOLCHAIN_URL="$MUSL_CC_HOST/x86_64-linux-musl-cross.tgz" arm64) BOOTLIN_ARCH="aarch64" ;;
;; armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
arm64) armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
TOOLCHAIN_URL="$MUSL_CC_HOST/aarch64-linux-musl-cross.tgz" armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
;; 386) BOOTLIN_ARCH="x86-i686" ;;
armv7) s390x) BOOTLIN_ARCH="s390x-z13" ;;
TOOLCHAIN_URL="$MUSL_CC_HOST/armv7l-linux-musleabihf-cross.tgz"
export GOARCH=arm
export GOARM=7
;;
armv6)
TOOLCHAIN_URL="$MUSL_CC_HOST/armv6-linux-musleabihf-cross.tgz"
export GOARCH=arm
export GOARM=6
;;
armv5)
TOOLCHAIN_URL="$MUSL_CC_HOST/arm-linux-musleabi-cross.tgz"
export GOARCH=arm
export GOARM=5
;;
386)
TOOLCHAIN_URL="$MUSL_CC_HOST/i686-linux-musl-cross.tgz"
;;
s390x)
TOOLCHAIN_URL="$MUSL_CC_HOST/s390x-linux-musl-cross.tgz"
;;
esac esac
echo "Downloading musl toolchain for ${{ matrix.platform }}" echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
curl -LO "$TOOLCHAIN_URL" TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
tar -xf *.tgz TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "*-cross" | head -n1) [ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
TOOLCHAIN_DIR=$(realpath "$TOOLCHAIN_DIR") echo "Downloading: $TARBALL_URL"
export PATH="$TOOLCHAIN_DIR/bin:$PATH" cd /tmp
# Detect compiler curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
export CC=$(find $TOOLCHAIN_DIR/bin -name '*-gcc' | head -n1) tar -xf "$(basename "$TARBALL_URL")"
echo "Using CC=$CC" TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
cd -
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
file xui-release file xui-release
ldd xui-release || echo "Static binary confirmed" ldd xui-release || echo "Static binary confirmed"

4
.gitignore vendored
View file

@ -29,9 +29,9 @@ main
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Ignore Go specific files # Ignore Go build files
*.exe *.exe
*.exe~ x-ui.db
# Ignore Docker specific files # Ignore Docker specific files
docker-compose.override.yml docker-compose.override.yml

View file

@ -1,7 +1,7 @@
# ======================================================== # ========================================================
# Stage: Builder # Stage: Builder
# ======================================================== # ========================================================
FROM golang:1.24-alpine AS builder FROM golang:1.25-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH

View file

@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## النجوم عبر الزمن ## النجوم عبر الزمن

View file

@ -48,7 +48,7 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Estrellas a lo Largo del Tiempo ## Estrellas a lo Largo del Tiempo

View file

@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## ستاره‌ها در طول زمان ## ستاره‌ها در طول زمان

View file

@ -48,7 +48,7 @@ For full documentation, please visit the [project Wiki](https://github.com/MHSan
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Stargazers over Time ## Stargazers over Time

View file

@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Звезды с течением времени ## Звезды с течением времени

View file

@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## 随时间变化的星标数 ## 随时间变化的星标数

View file

@ -3,7 +3,10 @@ package config
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"runtime"
"strings" "strings"
) )
@ -54,12 +57,32 @@ func GetBinFolderPath() string {
return binFolderPath return binFolderPath
} }
func getBaseDir() string {
exePath, err := os.Executable()
if err != nil {
return "."
}
exeDir := filepath.Dir(exePath)
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
wd, err := os.Getwd()
if err != nil {
return "."
}
return wd
}
return exeDir
}
func GetDBFolderPath() string { func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER") dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" { if dbFolderPath != "" {
dbFolderPath = "/etc/x-ui" return dbFolderPath
} }
return dbFolderPath if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/etc/x-ui"
} }
func GetDBPath() string { func GetDBPath() string {
@ -68,8 +91,54 @@ func GetDBPath() string {
func GetLogFolder() string { func GetLogFolder() string {
logFolderPath := os.Getenv("XUI_LOG_FOLDER") logFolderPath := os.Getenv("XUI_LOG_FOLDER")
if logFolderPath == "" { if logFolderPath != "" {
logFolderPath = "/var/log" return logFolderPath
} }
return logFolderPath if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/var/log"
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Sync()
}
func init() {
if runtime.GOOS != "windows" {
return
}
if os.Getenv("XUI_DB_FOLDER") != "" {
return
}
oldDBFolder := "/etc/x-ui"
oldDBPath := fmt.Sprintf("%s/%s.db", oldDBFolder, GetName())
newDBFolder := GetDBFolderPath()
newDBPath := fmt.Sprintf("%s/%s.db", newDBFolder, GetName())
_, err := os.Stat(newDBPath)
if err == nil {
return // new exists
}
_, err = os.Stat(oldDBPath)
if os.IsNotExist(err) {
return // old does not exist
}
_ = copyFile(oldDBPath, newDBPath) // ignore error
} }

View file

@ -1 +1 @@
2.6.5 2.6.6

38
go.mod
View file

@ -1,6 +1,6 @@
module x-ui module x-ui
go 1.24.5 go 1.25.0
require ( require (
github.com/gin-contrib/gzip v1.2.3 github.com/gin-contrib/gzip v1.2.3
@ -9,18 +9,18 @@ require (
github.com/goccy/go-json v0.10.5 github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mymmrac/telego v0.32.0 github.com/mymmrac/telego v1.2.0
github.com/nicksnyder/go-i18n/v2 v2.6.0 github.com/nicksnyder/go-i18n/v2 v2.6.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.2.4 github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.25.7 github.com/shirou/gopsutil/v4 v4.25.7
github.com/valyala/fasthttp v1.64.0 github.com/valyala/fasthttp v1.65.0
github.com/xlzd/gotp v0.1.0 github.com/xlzd/gotp v0.1.0
github.com/xtls/xray-core v1.250803.0 github.com/xtls/xray-core v1.250803.0
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.40.0 golang.org/x/crypto v0.41.0
golang.org/x/text v0.27.0 golang.org/x/text v0.28.0
google.golang.org/grpc v1.74.2 google.golang.org/grpc v1.74.2
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.1 gorm.io/gorm v1.30.1
@ -31,10 +31,9 @@ require (
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/ebitengine/purego v0.8.4 // indirect github.com/ebitengine/purego v0.8.4 // indirect
github.com/fasthttp/router v1.5.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
@ -57,7 +56,7 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.30 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/miekg/dns v1.1.68 // indirect github.com/miekg/dns v1.1.68 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
@ -68,9 +67,8 @@ require (
github.com/refraction-networking/utls v1.8.0 // indirect github.com/refraction-networking/utls v1.8.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.6.6 // indirect github.com/sagernet/sing v0.7.5 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
@ -85,18 +83,18 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.2 // indirect go.uber.org/mock v0.5.2 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.19.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/mod v0.26.0 // indirect golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.42.0 // indirect golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.35.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.35.0 // indirect golang.org/x/tools v0.36.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
lukechampine.com/blake3 v1.4.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect
) )

78
go.sum
View file

@ -4,14 +4,12 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -21,8 +19,6 @@ github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mT
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8=
github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
@ -87,10 +83,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -101,8 +95,8 @@ github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr32
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -110,8 +104,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY= github.com/mymmrac/telego v1.2.0 h1:CHmR9eiugpTiF/ttppmK89E6mcu9d4DmNQS0tMpUs6M=
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M= github.com/mymmrac/telego v1.2.0/go.mod h1:OiCm4QjqB/ZY2E4VAmkVH8EeLLhM4QuFuO1KOCuvGoM=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ= github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE= github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
@ -138,12 +132,10 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE= github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc=
github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
@ -153,7 +145,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -171,8 +162,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og= github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA= github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
@ -207,14 +198,14 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -223,24 +214,24 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.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.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@ -254,8 +245,7 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h1:sfK5nHuG7lRFZ2FdTT3RimOqWBg8IrVm+/Vko1FVOsk= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View file

@ -7,7 +7,6 @@ yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
cur_dir=$(pwd) cur_dir=$(pwd)
show_ip_service_lists=("https://api.ipify.org" "https://4.ident.me")
# check root # check root
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 [[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
@ -40,19 +39,6 @@ arch() {
echo "Arch: $(arch)" echo "Arch: $(arch)"
check_glibc_version() {
glibc_version=$(ldd --version | head -n1 | awk '{print $NF}')
required_version="2.32"
if [[ "$(printf '%s\n' "$required_version" "$glibc_version" | sort -V | head -n1)" != "$required_version" ]]; then
echo -e "${red}GLIBC version $glibc_version is too old! Required: 2.32 or higher${plain}"
echo "Please upgrade to a newer version of your operating system to get a higher GLIBC version."
exit 1
fi
echo "GLIBC version: $glibc_version (meets requirement of 2.32+)"
}
check_glibc_version
install_base() { install_base() {
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
@ -71,7 +57,7 @@ install_base() {
zypper refresh && zypper -q install -y wget curl tar timezone zypper refresh && zypper -q install -y wget curl tar timezone
;; ;;
*) *)
apt-get update && apt install -y -q wget curl tar tzdata apt-get update && apt-get install -y -q wget curl tar tzdata
;; ;;
esac esac
} }
@ -86,10 +72,18 @@ config_after_install() {
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}') local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local URL_lists=(
for ip_service_addr in "${show_ip_service_lists[@]}"; do "https://api4.ipify.org"
local server_ip=$(curl -s --max-time 3 ${ip_service_addr} 2>/dev/null) "https://ipv4.icanhazip.com"
if [ -n "${server_ip}" ]; then "https://v4.api.ipinfo.io/ip"
"https://ipv4.myexternalip.com/raw"
"https://4.ident.me"
"https://check-host.net/ip"
)
local server_ip=""
for ip_address in "${URL_lists[@]}"; do
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
if [[ -n "${server_ip}" ]]; then
break break
fi fi
done done

View file

@ -209,9 +209,10 @@ func (s *SubJsonService) streamData(stream string) map[string]any {
var streamSettings map[string]any var streamSettings map[string]any
json.Unmarshal([]byte(stream), &streamSettings) json.Unmarshal([]byte(stream), &streamSettings)
security, _ := streamSettings["security"].(string) security, _ := streamSettings["security"].(string)
if security == "tls" { switch security {
case "tls":
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any)) streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
} else if security == "reality" { case "reality":
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any)) streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
} }
delete(streamSettings, "sockopt") delete(streamSettings, "sockopt")

File diff suppressed because one or more lines are too long

View file

@ -1,7 +0,0 @@
@import "../lib/style/index.less";
@import "../lib/style/components.less";
@green-6: #008771;
@primary-color: @green-6;
@border-radius-base: 1rem;
@progress-remaining-color: #EDEDED;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -641,7 +641,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
keyFile = '', keyFile = '',
certificate = '', certificate = '',
key = '', key = '',
ocspStapling = 0,
oneTimeLoading = false, oneTimeLoading = false,
usage = USAGE_OPTION.ENCIPHERMENT, usage = USAGE_OPTION.ENCIPHERMENT,
buildChain = false, buildChain = false,
@ -652,7 +651,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
this.keyFile = keyFile; this.keyFile = keyFile;
this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate; this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate;
this.key = Array.isArray(key) ? key.join('\n') : key; this.key = Array.isArray(key) ? key.join('\n') : key;
this.ocspStapling = ocspStapling;
this.oneTimeLoading = oneTimeLoading; this.oneTimeLoading = oneTimeLoading;
this.usage = usage; this.usage = usage;
this.buildChain = buildChain this.buildChain = buildChain
@ -664,7 +662,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
true, true,
json.certificateFile, json.certificateFile,
json.keyFile, '', '', json.keyFile, '', '',
json.ocspStapling,
json.oneTimeLoading, json.oneTimeLoading,
json.usage, json.usage,
json.buildChain, json.buildChain,
@ -674,7 +671,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
false, '', '', false, '', '',
json.certificate.join('\n'), json.certificate.join('\n'),
json.key.join('\n'), json.key.join('\n'),
json.ocspStapling,
json.oneTimeLoading, json.oneTimeLoading,
json.usage, json.usage,
json.buildChain, json.buildChain,
@ -687,7 +683,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
return { return {
certificateFile: this.certFile, certificateFile: this.certFile,
keyFile: this.keyFile, keyFile: this.keyFile,
ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading, oneTimeLoading: this.oneTimeLoading,
usage: this.usage, usage: this.usage,
buildChain: this.buildChain, buildChain: this.buildChain,
@ -696,7 +691,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
return { return {
certificate: this.cert.split('\n'), certificate: this.cert.split('\n'),
key: this.key.split('\n'), key: this.key.split('\n'),
ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading, oneTimeLoading: this.oneTimeLoading,
usage: this.usage, usage: this.usage,
buildChain: this.buildChain, buildChain: this.buildChain,
@ -1716,7 +1710,7 @@ class Inbound extends XrayCommonClass {
toJson() { toJson() {
let streamSettings; let streamSettings;
if (this.canEnableStream()) { if (this.canEnableStream() || this.stream?.sockopt) {
streamSettings = this.stream.toJson(); streamSettings = this.stream.toJson();
} }
return { return {

View file

@ -919,12 +919,14 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
constructor( constructor(
packets = '1-3', packets = '1-3',
length = '', length = '',
interval = '' interval = '',
maxSplit = ''
) { ) {
super(); super();
this.packets = packets; this.packets = packets;
this.length = length; this.length = length;
this.interval = interval; this.interval = interval;
this.maxSplit = maxSplit;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@ -932,6 +934,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
json.packets, json.packets,
json.length, json.length,
json.interval, json.interval,
json.maxSplit
); );
} }
}; };
@ -940,12 +943,14 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
constructor( constructor(
type = 'rand', type = 'rand',
packet = '10-20', packet = '10-20',
delay = '10-16' delay = '10-16',
applyTo = 'ip'
) { ) {
super(); super();
this.type = type; this.type = type;
this.packet = packet; this.packet = packet;
this.delay = delay; this.delay = delay;
this.applyTo = applyTo;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@ -953,6 +958,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
json.type, json.type,
json.packet, json.packet,
json.delay, json.delay,
json.applyTo
); );
} }
@ -961,6 +967,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
type: this.type, type: this.type,
packet: this.packet, packet: this.packet,
delay: this.delay, delay: this.delay,
applyTo: this.applyTo
}; };
} }
}; };
@ -988,7 +995,7 @@ Outbound.DNSSettings = class extends CommonClass {
network = 'udp', network = 'udp',
address = '', address = '',
port = 53, port = 53,
nonIPQuery = 'drop', nonIPQuery = 'reject',
blockTypes = [] blockTypes = []
) { ) {
super(); super();

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
if (process.env.NODE_ENV === 'production') {
module.exports = require('./vue.common.prod.js')
} else {
module.exports = require('./vue.common.dev.js')
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
if (process.env.NODE_ENV === 'production') {
module.exports = require('./vue.runtime.common.prod.js')
} else {
module.exports = require('./vue.runtime.common.dev.js')
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,76 +0,0 @@
import Vue from './vue.runtime.common.js'
export default Vue
// this should be kept in sync with src/v3/index.ts
export const {
version,
// refs
ref,
shallowRef,
isRef,
toRef,
toRefs,
unref,
proxyRefs,
customRef,
triggerRef,
computed,
// reactive
reactive,
isReactive,
isReadonly,
isShallow,
isProxy,
shallowReactive,
markRaw,
toRaw,
readonly,
shallowReadonly,
// watch
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
// effectScope
effectScope,
onScopeDispose,
getCurrentScope,
// provide / inject
provide,
inject,
// lifecycle
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated,
onServerPrefetch,
onRenderTracked,
onRenderTriggered,
// v2 only
set,
del,
// v3 compat
h,
getCurrentInstance,
useSlots,
useAttrs,
mergeDefaults,
nextTick,
useCssModule,
useCssVars,
defineComponent,
defineAsyncComponent
} = Vue

View file

@ -17,7 +17,8 @@ var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
type ServerController struct { type ServerController struct {
BaseController BaseController
serverService service.ServerService serverService service.ServerService
settingService service.SettingService
lastStatus *service.Status lastStatus *service.Status
lastGetStatusTime time.Time lastGetStatusTime time.Time
@ -44,6 +45,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/stopXrayService", a.stopXrayService) g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService) g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray) g.POST("/installXray/:version", a.installXray)
g.POST("/updateGeofile", a.updateGeofile)
g.POST("/updateGeofile/:fileName", a.updateGeofile) g.POST("/updateGeofile/:fileName", a.updateGeofile)
g.POST("/logs/:count", a.getLogs) g.POST("/logs/:count", a.getLogs)
g.POST("/xraylogs/:count", a.getXrayLogs) g.POST("/xraylogs/:count", a.getXrayLogs)
@ -137,7 +139,45 @@ func (a *ServerController) getLogs(c *gin.Context) {
func (a *ServerController) getXrayLogs(c *gin.Context) { func (a *ServerController) getXrayLogs(c *gin.Context) {
count := c.Param("count") count := c.Param("count")
logs := a.serverService.GetXrayLogs(count) filter := c.PostForm("filter")
showDirect := c.PostForm("showDirect")
showBlocked := c.PostForm("showBlocked")
showProxy := c.PostForm("showProxy")
var freedoms []string
var blackholes []string
//getting tags for freedom and blackhole outbounds
config, err := a.settingService.GetDefaultXrayConfig()
if err == nil && config != nil {
if cfgMap, ok := config.(map[string]interface{}); ok {
if outbounds, ok := cfgMap["outbounds"].([]interface{}); ok {
for _, outbound := range outbounds {
if obMap, ok := outbound.(map[string]interface{}); ok {
switch obMap["protocol"] {
case "freedom":
if tag, ok := obMap["tag"].(string); ok {
freedoms = append(freedoms, tag)
}
case "blackhole":
if tag, ok := obMap["tag"].(string); ok {
blackholes = append(blackholes, tag)
}
}
}
}
}
}
}
if len(freedoms) == 0 {
freedoms = []string{"direct"}
}
if len(blackholes) == 0 {
blackholes = []string{"blocked"}
}
logs := a.serverService.GetXrayLogs(count, filter, showDirect, showBlocked, showProxy, freedoms, blackholes)
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
} }

View file

@ -2,10 +2,10 @@ package entity
import ( import (
"crypto/tls" "crypto/tls"
"math"
"net" "net"
"strings" "strings"
"time" "time"
"math"
"x-ui/util/common" "x-ui/util/common"
) )
@ -39,8 +39,8 @@ type AllSetting struct {
TgCpu int `json:"tgCpu" form:"tgCpu"` TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"` TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"` TimeLocation string `json:"timeLocation" form:"timeLocation"`
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"` TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"` TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
SubEnable bool `json:"subEnable" form:"subEnable"` SubEnable bool `json:"subEnable" form:"subEnable"`
SubTitle string `json:"subTitle" form:"subTitle"` SubTitle string `json:"subTitle" form:"subTitle"`
SubListen string `json:"subListen" form:"subListen"` SubListen string `json:"subListen" form:"subListen"`

View file

@ -42,6 +42,9 @@
<a-form-item label='Interval'> <a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input> <a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Max Split'>
<a-input v-model.trim="outbound.settings.fragment.maxSplit"></a-input>
</a-form-item>
</template> </template>
<!-- Switch for Noises --> <!-- Switch for Noises -->
@ -75,6 +78,11 @@
<a-form-item label='Delay'> <a-form-item label='Delay'>
<a-input v-model.trim="noise.delay"></a-input> <a-input v-model.trim="noise.delay"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Apply To'>
<a-select v-model="noise.applyTo" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['ip','ipv4','ipv6']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</a-form> </a-form>
</template> </template>
</template> </template>
@ -97,7 +105,7 @@
</a-form-item> </a-form-item>
<a-form-item label='non-IP queries'> <a-form-item label='non-IP queries'>
<a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['drop','skip']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['reject','drop','skip']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' > <a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' >

View file

@ -30,4 +30,8 @@
<a-switch v-model="inbound.settings.followRedirect"></a-switch> <a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- sockopt -->
<template>
{{template "form/streamSockopt"}}
</template>
{{end}} {{end}}

View file

@ -91,9 +91,6 @@
<a-textarea v-model="cert.key"></a-textarea> <a-textarea v-model="cert.key"></a-textarea>
</a-form-item> </a-form-item>
</template> </template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="One Time Loading"> <a-form-item label="One Time Loading">
<a-switch v-model="cert.oneTimeLoading"></a-switch> <a-switch v-model="cert.oneTimeLoading"></a-switch>
</a-form-item> </a-form-item>

View file

@ -405,9 +405,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -419,9 +419,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -433,9 +433,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -447,9 +447,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -534,9 +534,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -548,9 +548,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -562,9 +562,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -576,9 +576,9 @@
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon> <a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -811,17 +811,6 @@
loading(spinning = true) { loading(spinning = true) {
this.loadingStates.spinning = spinning; this.loadingStates.spinning = spinning;
}, },
getClientWithComment(email, inboundId) {
const dbInbound = this.dbInbounds.find(inbound => inbound.id === inboundId);
if (!dbInbound) return { email, comment: '' };
const inboundSettings = JSON.parse(dbInbound.settings);
if (inboundSettings.clients) {
const client = inboundSettings.clients.find(c => c.email === email);
return client ? { email: client.email, comment: client.comment || '' } : { email, comment: '' };
}
return { email, comment: '' };
},
async getDBInbounds() { async getDBInbounds() {
this.refreshing = true; this.refreshing = true;
const msg = await HttpUtil.post('/panel/inbound/list'); const msg = await HttpUtil.post('/panel/inbound/list');
@ -893,7 +882,7 @@
} }
}, },
getClientCounts(dbInbound, inbound) { getClientCounts(dbInbound, inbound) {
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = []; let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [], comments = new Map();
clients = inbound.clients; clients = inbound.clients;
clientStats = dbInbound.clientStats clientStats = dbInbound.clientStats
now = new Date().getTime() now = new Date().getTime()
@ -901,6 +890,9 @@
clientCount = clients.length; clientCount = clients.length;
if (dbInbound.enable) { if (dbInbound.enable) {
clients.forEach(client => { clients.forEach(client => {
if (client.comment) {
comments.set(client.email, client.comment)
}
if (client.enable) { if (client.enable) {
active.push(client.email); active.push(client.email);
if (this.isClientOnline(client.email)) online.push(client.email); if (this.isClientOnline(client.email)) online.push(client.email);
@ -929,6 +921,7 @@
depleted: depleted, depleted: depleted,
expiring: expiring, expiring: expiring,
online: online, online: online,
comments: comments,
}; };
}, },
@ -1126,7 +1119,11 @@
protocol: inbound.protocol, protocol: inbound.protocol,
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()){
data.streamSettings = inbound.stream.toString();
} else if (inbound.stream?.sockopt) {
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
}
data.sniffing = inbound.sniffing.toString(); data.sniffing = inbound.sniffing.toString();
data.allocate = inbound.allocate.toString(); data.allocate = inbound.allocate.toString();
@ -1146,7 +1143,11 @@
protocol: inbound.protocol, protocol: inbound.protocol,
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()){
data.streamSettings = inbound.stream.toString();
} else if (inbound.stream?.sockopt) {
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
}
data.sniffing = inbound.sniffing.toString(); data.sniffing = inbound.sniffing.toString();
data.allocate = inbound.allocate.toString(); data.allocate = inbound.allocate.toString();

View file

@ -169,9 +169,6 @@
<a-col> <a-col>
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-icon> <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-icon>
</a-col> </a-col>
<a-col>
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openXrayLogs()"></a-icon>
</a-col>
</a-row> </a-row>
</span> </span>
<template slot="content"> <template slot="content">
@ -383,6 +380,9 @@
<a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/> <a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/>
</a-list-item> </a-list-item>
</a-list> </a-list>
<div style="margin-top: 5px; display: flex; justify-content: flex-end;">
<a-button @click="updateGeofile('')">{{ i18n "pages.index.geofilesUpdateAll" }}</a-button>
</div>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
</a-modal> </a-modal>
@ -457,6 +457,14 @@
</a-select> </a-select>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label="Filter:">
<a-input size="small" v-model="xraylogModal.filter" @keyup.enter="openXrayLogs()"></a-input>
</a-form-item>
<a-form-item>
<a-checkbox v-model="xraylogModal.showDirect" @change="openXrayLogs()">Direct</a-checkbox>
<a-checkbox v-model="xraylogModal.showBlocked" @change="openXrayLogs()">Blocked</a-checkbox>
<a-checkbox v-model="xraylogModal.showProxy" @change="openXrayLogs()">Proxy</a-checkbox>
</a-form-item>
<a-form-item :style="{ float: 'right' }"> <a-form-item :style="{ float: 'right' }">
<a-button type="primary" icon="download" @click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button> <a-button type="primary" icon="download" @click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button>
</a-form-item> </a-form-item>
@ -651,6 +659,9 @@
visible: false, visible: false,
logs: [], logs: [],
rows: 20, rows: 20,
showDirect: true,
showBlocked: true,
showProxy: true,
loading: false, loading: false,
show(logs) { show(logs) {
this.visible = true; this.visible = true;
@ -665,17 +676,17 @@
const parts = log.split(' '); const parts = log.split(' ');
if(parts.length === 9) { if(parts.length === 10) {
const dateTime = `<b>${parts[0]} ${parts[1]}</b>`; const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
const from = `<b>${parts[3]}</b>`; const from = `<b>${parts[3]}</b>`;
const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`; const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
let outboundColor = ''; let outboundColor = '';
if (parts[8].startsWith('blocked')) { if (parts[9] === "b") {
outboundColor = ' style="color: #e04141;"'; outboundColor = ' style="color: #e04141;"'; //red for blocked
} }
else if (!parts[8].startsWith('direct')) { else if (parts[9] === "p") {
outboundColor = ' style="color: #3c89e8;"'; outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
} }
formattedLogs += `<span${outboundColor}> formattedLogs += `<span${outboundColor}>
@ -684,10 +695,10 @@ ${dateTime}
${from} ${from}
${parts[4]} ${parts[4]}
${to} ${to}
${parts.slice(6).join(' ')} ${parts.slice(6, 9).join(' ')}
</span>`; </span>`;
} else { } else {
formattedLogs += `<span>${parts.join(' ')}</span>`; formattedLogs += `<span>${log}</span>`;
} }
}); });
@ -775,16 +786,22 @@ ${dateTime}
}); });
}, },
updateGeofile(fileName) { updateGeofile(fileName) {
const isSingleFile = !!fileName;
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.index.geofileUpdateDialog" }}', title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
content: '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName), content: isSingleFile
? '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName)
: '{{ i18n "pages.index.geofilesUpdateDialogDesc" }}',
okText: '{{ i18n "confirm"}}', okText: '{{ i18n "confirm"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
cancelText: '{{ i18n "cancel"}}', cancelText: '{{ i18n "cancel"}}',
onOk: async () => { onOk: async () => {
versionModal.hide(); versionModal.hide();
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}'); this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
await HttpUtil.post(`/server/updateGeofile/${fileName}`); const url = isSingleFile
? `/server/updateGeofile/${fileName}`
: `/server/updateGeofile`;
await HttpUtil.post(url);
this.loading(false); this.loading(false);
}, },
}); });
@ -817,7 +834,7 @@ ${dateTime}
}, },
async openXrayLogs(){ async openXrayLogs(){
xraylogModal.loading = true; xraylogModal.loading = true;
const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows); const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy});
if (!msg.success) { if (!msg.success) {
return; return;
} }

View file

@ -207,7 +207,7 @@
settings: { settings: {
domainStrategy: "AsIs", domainStrategy: "AsIs",
noises: [ noises: [
{ type: "rand", packet: "10-20", delay: "10-16" }, { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" },
], ],
}, },
}, },
@ -397,7 +397,7 @@
} }
}, },
addNoise() { addNoise() {
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" }; const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
this.noisesArray = [...this.noisesArray, newNoise]; this.noisesArray = [...this.noisesArray, newNoise];
}, },
removeNoise(index) { removeNoise(index) {
@ -420,6 +420,11 @@
updatedNoises[index] = { ...updatedNoises[index], delay: value }; updatedNoises[index] = { ...updatedNoises[index], delay: value };
this.noisesArray = updatedNoises; this.noisesArray = updatedNoises;
}, },
updateNoiseApplyTo(index, value) {
const updatedNoises = [...this.noisesArray];
updatedNoises[index] = { ...updatedNoises[index], applyTo: value };
this.noisesArray = updatedNoises;
},
}, },
computed: { computed: {
fragment: { fragment: {

View file

@ -90,6 +90,18 @@
placeholder="10-20"></a-input> placeholder="10-20"></a-input>
</template> </template>
</a-setting-list-item> </a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>ApplyTo</template>
<template #control>
<a-select :value="noise.applyTo" :style="{ width: '100%' }"
:dropdown-class-name="themeSwitcher.currentTheme"
@change="(value) => updateNoiseApplyTo(index, value)">
<a-select-option :value="p" :label="p" v-for="p in ['ip', 'ipv4', 'ipv6']" :key="p">
<span>[[ p ]]</span>
</a-select-option>
</a-select>
</template>
</a-setting-list-item>
<a-space direction="horizontal" :style="{ padding: '10px 20px' }"> <a-space direction="horizontal" :style="{ padding: '10px 20px' }">
<a-button v-if="noisesArray.length > 1" type="danger" <a-button v-if="noisesArray.length > 1" type="danger"
@click="removeNoise(index)">Remove</a-button> @click="removeNoise(index)">Remove</a-button>

View file

@ -11,7 +11,6 @@ import (
"sort" "sort"
"time" "time"
"slices"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@ -58,21 +57,21 @@ func (j *CheckClientIpJob) Run() {
func (j *CheckClientIpJob) clearAccessLog() { func (j *CheckClientIpJob) clearAccessLog() {
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
j.checkError(err) j.checkError(err)
defer logAccessP.Close()
accessLogPath, err := xray.GetAccessLogPath() accessLogPath, err := xray.GetAccessLogPath()
j.checkError(err) j.checkError(err)
file, err := os.Open(accessLogPath) file, err := os.Open(accessLogPath)
j.checkError(err) j.checkError(err)
defer file.Close()
_, err = io.Copy(logAccessP, file) _, err = io.Copy(logAccessP, file)
j.checkError(err) j.checkError(err)
logAccessP.Close()
file.Close()
err = os.Truncate(accessLogPath, 0) err = os.Truncate(accessLogPath, 0)
j.checkError(err) j.checkError(err)
j.lastClear = time.Now().Unix() j.lastClear = time.Now().Unix()
} }
@ -193,10 +192,6 @@ func (j *CheckClientIpJob) checkError(e error) {
} }
} }
func (j *CheckClientIpJob) contains(s []string, str string) bool {
return slices.Contains(s, str)
}
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) { func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
db := database.GetDB() db := database.GetDB()
InboundClientIps := &model.InboundClientIps{} InboundClientIps := &model.InboundClientIps{}

View file

@ -16,7 +16,7 @@ func NewCheckXrayRunningJob() *CheckXrayRunningJob {
} }
func (j *CheckXrayRunningJob) Run() { func (j *CheckXrayRunningJob) Run() {
if j.xrayService.IsXrayRunning() { if !j.xrayService.DidXrayCrash() {
j.checkTime = 0 j.checkTime = 0
} else { } else {
j.checkTime++ j.checkTime++

View file

@ -177,15 +177,16 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
// Secure client ID // Secure client ID
for _, client := range clients { for _, client := range clients {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
if client.Password == "" { if client.Password == "" {
return inbound, false, common.NewError("empty client ID") return inbound, false, common.NewError("empty client ID")
} }
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
if client.Email == "" { if client.Email == "" {
return inbound, false, common.NewError("empty client ID") return inbound, false, common.NewError("empty client ID")
} }
} else { default:
if client.ID == "" { if client.ID == "" {
return inbound, false, common.NewError("empty client ID") return inbound, false, common.NewError("empty client ID")
} }
@ -436,15 +437,16 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
// Secure client ID // Secure client ID
for _, client := range clients { for _, client := range clients {
if oldInbound.Protocol == "trojan" { switch oldInbound.Protocol {
case "trojan":
if client.Password == "" { if client.Password == "" {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }
} else if oldInbound.Protocol == "shadowsocks" { case "shadowsocks":
if client.Email == "" { if client.Email == "" {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }
} else { default:
if client.ID == "" { if client.ID == "" {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }
@ -631,13 +633,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
clientIndex := -1 clientIndex := -1
for index, oldClient := range oldClients { for index, oldClient := range oldClients {
oldClientId := "" oldClientId := ""
if oldInbound.Protocol == "trojan" { switch oldInbound.Protocol {
case "trojan":
oldClientId = oldClient.Password oldClientId = oldClient.Password
newClientId = clients[0].Password newClientId = clients[0].Password
} else if oldInbound.Protocol == "shadowsocks" { case "shadowsocks":
oldClientId = oldClient.Email oldClientId = oldClient.Email
newClientId = clients[0].Email newClientId = clients[0].Email
} else { default:
oldClientId = oldClient.ID oldClientId = oldClient.ID
newClientId = clients[0].ID newClientId = clients[0].ID
} }
@ -1244,11 +1247,12 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@ -1328,11 +1332,12 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
clientOldEnabled = oldClient.Enable clientOldEnabled = oldClient.Enable
@ -1391,11 +1396,12 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@ -1448,11 +1454,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@ -1508,11 +1515,12 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break

View file

@ -235,8 +235,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
} }
// IP fetching with caching // IP fetching with caching
showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"} showIp4ServiceLists := []string{
showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"} "https://api4.ipify.org",
"https://ipv4.icanhazip.com",
"https://v4.api.ipinfo.io/ip",
"https://ipv4.myexternalip.com/raw",
"https://4.ident.me",
"https://check-host.net/ip",
}
showIp6ServiceLists := []string{
"https://api6.ipify.org",
"https://ipv6.icanhazip.com",
"https://v6.api.ipinfo.io/ip",
"https://ipv6.myexternalip.com/raw",
"https://6.ident.me",
}
if s.cachedIPv4 == "" { if s.cachedIPv4 == "" {
for _, ip4Service := range showIp4ServiceLists { for _, ip4Service := range showIp4ServiceLists {
@ -347,7 +360,6 @@ func (s *ServerService) StopXrayService() error {
} }
func (s *ServerService) RestartXrayService() error { func (s *ServerService) RestartXrayService() error {
s.xrayService.StopXray()
err := s.xrayService.RestartXray(true) err := s.xrayService.RestartXray(true)
if err != nil { if err != nil {
logger.Error("start xray failed:", err) logger.Error("start xray failed:", err)
@ -482,8 +494,16 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
return lines return lines
} }
func (s *ServerService) GetXrayLogs(count string) []string { func (s *ServerService) GetXrayLogs(
c, _ := strconv.Atoi(count) count string,
filter string,
showDirect string,
showBlocked string,
showProxy string,
freedoms []string,
blackholes []string) []string {
countInt, _ := strconv.Atoi(count)
var lines []string var lines []string
pathToAccessLog, err := xray.GetAccessLogPath() pathToAccessLog, err := xray.GetAccessLogPath()
@ -498,21 +518,57 @@ func (s *ServerService) GetXrayLogs(count string) []string {
defer file.Close() defer file.Close()
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := strings.TrimSpace(scanner.Text())
if strings.TrimSpace(line) == "" || strings.Contains(line, "api -> api") {
if line == "" || strings.Contains(line, "api -> api") {
//skipping empty lines and api calls
continue continue
} }
if filter != "" && !strings.Contains(line, filter) {
//applying filter if it's not empty
continue
}
//adding suffixes to further distinguish entries by outbound
if hasSuffix(line, freedoms) {
if showDirect == "false" {
continue
}
line = line + " f"
} else if hasSuffix(line, blackholes) {
if showBlocked == "false" {
continue
}
line = line + " b"
} else {
if showProxy == "false" {
continue
}
line = line + " p"
}
lines = append(lines, line) lines = append(lines, line)
} }
if len(lines) > c { if len(lines) > countInt {
lines = lines[len(lines)-c:] lines = lines[len(lines)-countInt:]
} }
return lines return lines
} }
func hasSuffix(line string, suffixes []string) bool {
for _, sfx := range suffixes {
if strings.HasSuffix(line, sfx+"]") {
return true
}
}
return false
}
func (s *ServerService) GetConfigJson() (any, error) { func (s *ServerService) GetConfigJson() (any, error) {
config, err := s.xrayService.GetXrayConfig() config, err := s.xrayService.GetXrayConfig()
if err != nil { if err != nil {
@ -698,27 +754,43 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
return nil return nil
} }
var fileURL string var errorMessages []string
for _, file := range files {
if file.FileName == fileName { if fileName == "" {
fileURL = file.URL for _, file := range files {
break destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), file.FileName)
if err := downloadFile(file.URL, destPath); err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", file.FileName, err))
}
} }
} } else {
destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), fileName)
if fileURL == "" { var fileURL string
return common.NewErrorf("File '%s' not found in the list of Geofiles", fileName) for _, file := range files {
} if file.FileName == fileName {
fileURL = file.URL
break
}
}
destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), fileName) if fileURL == "" {
errorMessages = append(errorMessages, fmt.Sprintf("File '%s' not found in the list of Geofiles", fileName))
}
if err := downloadFile(fileURL, destPath); err != nil { if err := downloadFile(fileURL, destPath); err != nil {
return common.NewErrorf("Error downloading Geofile '%s': %v", fileName, err) errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", fileName, err))
}
} }
err := s.RestartXrayService() err := s.RestartXrayService()
if err != nil { if err != nil {
return common.NewErrorf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err) errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
}
if len(errorMessages) > 0 {
return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
} }
return nil return nil

View file

@ -1,6 +1,7 @@
package service package service
import ( import (
"context"
"crypto/rand" "crypto/rand"
"embed" "embed"
"encoding/base64" "encoding/base64"
@ -39,7 +40,6 @@ var (
isRunning bool isRunning bool
hostname string hostname string
hashStorage *global.HashStorage hashStorage *global.HashStorage
handler *th.Handler
// clients data to adding new client // clients data to adding new client
receiver_inbound_ID int receiver_inbound_ID int
@ -148,7 +148,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
} }
// After bot initialization, set up bot commands with localized descriptions // After bot initialization, set up bot commands with localized descriptions
err = bot.SetMyCommands(&telego.SetMyCommandsParams{ err = bot.SetMyCommands(context.Background(), &telego.SetMyCommandsParams{
Commands: []telego.BotCommand{ Commands: []telego.BotCommand{
{Command: "start", Description: t.I18nBot("tgbot.commands.startDesc")}, {Command: "start", Description: t.I18nBot("tgbot.commands.startDesc")},
{Command: "help", Description: t.I18nBot("tgbot.commands.helpDesc")}, {Command: "help", Description: t.I18nBot("tgbot.commands.helpDesc")},
@ -221,8 +221,9 @@ func (t *Tgbot) SetHostname() {
} }
func (t *Tgbot) Stop() { func (t *Tgbot) Stop() {
botHandler.Stop() if botHandler != nil {
bot.StopLongPolling() botHandler.Stop()
}
logger.Info("Stop Telegram receiver ...") logger.Info("Stop Telegram receiver ...")
isRunning = false isRunning = false
adminIds = nil adminIds = nil
@ -255,26 +256,29 @@ func (t *Tgbot) OnReceive() {
Timeout: 10, Timeout: 10,
} }
updates, _ := bot.UpdatesViaLongPolling(&params) updates, _ := bot.UpdatesViaLongPolling(context.Background(), &params)
botHandler, _ = th.NewBotHandler(bot, updates) botHandler, _ = th.NewBotHandler(bot, updates)
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
delete(userStates, message.Chat.ID) delete(userStates, message.Chat.ID)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove()) t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
return nil
}, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard"))) }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
delete(userStates, message.Chat.ID) delete(userStates, message.Chat.ID)
t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID)) t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
return nil
}, th.AnyCommand()) }, th.AnyCommand())
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) { botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
delete(userStates, query.Message.GetChat().ID) delete(userStates, query.Message.GetChat().ID)
t.answerCallback(&query, checkAdmin(query.From.ID)) t.answerCallback(&query, checkAdmin(query.From.ID))
return nil
}, th.AnyCallbackQueryWithMessage()) }, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
if userState, exists := userStates[message.Chat.ID]; exists { if userState, exists := userStates[message.Chat.ID]; exists {
switch userState { switch userState {
case "awaiting_id": case "awaiting_id":
@ -284,7 +288,7 @@ func (t *Tgbot) OnReceive() {
inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID)
message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(message.Chat.ID, message_text) t.addClient(message.Chat.ID, message_text)
return return nil
} }
client_Id = strings.TrimSpace(message.Text) client_Id = strings.TrimSpace(message.Text)
@ -309,7 +313,7 @@ func (t *Tgbot) OnReceive() {
if client_TrPassword == strings.TrimSpace(message.Text) { if client_TrPassword == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID) delete(userStates, message.Chat.ID)
return return nil
} }
client_TrPassword = strings.TrimSpace(message.Text) client_TrPassword = strings.TrimSpace(message.Text)
@ -334,7 +338,7 @@ func (t *Tgbot) OnReceive() {
if client_ShPassword == strings.TrimSpace(message.Text) { if client_ShPassword == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID) delete(userStates, message.Chat.ID)
return return nil
} }
client_ShPassword = strings.TrimSpace(message.Text) client_ShPassword = strings.TrimSpace(message.Text)
@ -359,7 +363,7 @@ func (t *Tgbot) OnReceive() {
if client_Email == strings.TrimSpace(message.Text) { if client_Email == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID) delete(userStates, message.Chat.ID)
return return nil
} }
client_Email = strings.TrimSpace(message.Text) client_Email = strings.TrimSpace(message.Text)
@ -384,7 +388,7 @@ func (t *Tgbot) OnReceive() {
if client_Comment == strings.TrimSpace(message.Text) { if client_Comment == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID) delete(userStates, message.Chat.ID)
return return nil
} }
client_Comment = strings.TrimSpace(message.Text) client_Comment = strings.TrimSpace(message.Text)
@ -417,6 +421,7 @@ func (t *Tgbot) OnReceive() {
} }
} }
} }
return nil
}, th.AnyMessage()) }, th.AnyMessage())
botHandler.Start() botHandler.Start()
@ -635,13 +640,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 { if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3]) num, err := strconv.Atoi(dataArray[3])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@ -698,8 +704,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
case "add_client_limit_traffic_in": case "add_client_limit_traffic_in":
if len(dataArray) >= 2 { if len(dataArray) >= 2 {
@ -709,13 +719,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 { if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2]) num, err := strconv.Atoi(dataArray[2])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@ -838,13 +849,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 { if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3]) num, err := strconv.Atoi(dataArray[3])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@ -913,8 +925,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
case "add_client_reset_exp_in": case "add_client_reset_exp_in":
if len(dataArray) >= 2 { if len(dataArray) >= 2 {
@ -924,13 +940,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 { if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2]) num, err := strconv.Atoi(dataArray[2])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@ -1029,13 +1046,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 { if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3]) num, err := strconv.Atoi(dataArray[3])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@ -1095,8 +1113,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
case "add_client_ip_limit_in": case "add_client_ip_limit_in":
if len(dataArray) >= 2 { if len(dataArray) >= 2 {
@ -1106,13 +1128,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 { if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2]) num, err := strconv.Atoi(dataArray[2])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@ -1157,8 +1180,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
} }
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "clear_ips": case "clear_ips":
inlineKeyboard := tu.InlineKeyboard( inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow( tu.InlineKeyboardRow(
@ -1284,8 +1305,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text) t.addClient(callbackQuery.Message.GetChat().ID, message_text)
} }
return return
} else { } else {
@ -1520,6 +1545,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email)) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_default_ip_limit": case "add_client_default_ip_limit":
@ -1530,6 +1559,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email)) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_submit_disable": case "add_client_submit_disable":
@ -1594,6 +1627,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails) valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
if err != nil {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
return
}
for _, valid_emails := range valid_emails { for _, valid_emails := range valid_emails {
traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails) traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
@ -1756,6 +1793,10 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
} }
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol) jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
if err != nil {
logger.Warning("BuildJSONForProtocol run failed:", err)
return false, errors.New("failed to build JSON for protocol")
}
newInbound := &model.Inbound{ newInbound := &model.Inbound{
Id: receiver_inbound_ID, Id: receiver_inbound_ID,
@ -1859,7 +1900,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
if len(replyMarkup) > 0 && n == (len(allMessages)-1) { if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
params.ReplyMarkup = replyMarkup[0] params.ReplyMarkup = replyMarkup[0]
} }
_, err := bot.SendMessage(&params) _, err := bot.SendMessage(context.Background(), &params)
if err != nil { if err != nil {
logger.Warning("Error sending telegram message :", err) logger.Warning("Error sending telegram message :", err)
} }
@ -2004,10 +2045,11 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
} }
msg := "" msg := ""
if status == LoginSuccess { switch status {
case LoginSuccess:
msg += t.I18nBot("tgbot.messages.loginSuccess") msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
} else if status == LoginFail { case LoginFail:
msg += t.I18nBot("tgbot.messages.loginFailed") msg += t.I18nBot("tgbot.messages.loginFailed")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.password", "Password=="+password) msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
@ -2167,6 +2209,22 @@ func (t *Tgbot) clientInfoMsg(
expiryTime = t.I18nBot("tgbot.unlimited") expiryTime = t.I18nBot("tgbot.unlimited")
} else if diff > 172800 || !traffic.Enable { } else if diff > 172800 || !traffic.Enable {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
if diff > 0 {
days := diff / 86400
hours := (diff % 86400) / 3600
minutes := (diff % 3600) / 60
remainingTime := ""
if days > 0 {
remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
}
if hours > 0 {
remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
}
if minutes > 0 {
remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
}
expiryTime += fmt.Sprintf(" (%s)", remainingTime)
}
} else if traffic.ExpiryTime < 0 { } else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
flag = true flag = true
@ -2765,7 +2823,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
tu.ID(chatId), tu.ID(chatId),
tu.File(file), tu.File(file),
) )
_, err = bot.SendDocument(document) _, err = bot.SendDocument(context.Background(), document)
if err != nil { if err != nil {
logger.Error("Error in uploading backup: ", err) logger.Error("Error in uploading backup: ", err)
} }
@ -2779,7 +2837,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
tu.ID(chatId), tu.ID(chatId),
tu.File(file), tu.File(file),
) )
_, err = bot.SendDocument(document) _, err = bot.SendDocument(context.Background(), document)
if err != nil { if err != nil {
logger.Error("Error in uploading config.json: ", err) logger.Error("Error in uploading config.json: ", err)
} }
@ -2803,7 +2861,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
tu.ID(chatId), tu.ID(chatId),
tu.File(file), tu.File(file),
) )
_, err = bot.SendDocument(document) _, err = bot.SendDocument(context.Background(), document)
if err != nil { if err != nil {
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err) logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
} }
@ -2824,7 +2882,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
tu.ID(chatId), tu.ID(chatId),
tu.File(file), tu.File(file),
) )
_, err = bot.SendDocument(document) _, err = bot.SendDocument(context.Background(), document)
if err != nil { if err != nil {
logger.Error("Error in uploading IPLimitBannedLog: ", err) logger.Error("Error in uploading IPLimitBannedLog: ", err)
} }
@ -2842,7 +2900,7 @@ func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
CallbackQueryID: id, CallbackQueryID: id,
Text: message, Text: message,
} }
if err := bot.AnswerCallbackQuery(&params); err != nil { if err := bot.AnswerCallbackQuery(context.Background(), &params); err != nil {
logger.Warning(err) logger.Warning(err)
} }
} }
@ -2853,7 +2911,7 @@ func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyb
MessageID: messageID, MessageID: messageID,
ReplyMarkup: inlineKeyboard, ReplyMarkup: inlineKeyboard,
} }
if _, err := bot.EditMessageReplyMarkup(&params); err != nil { if _, err := bot.EditMessageReplyMarkup(context.Background(), &params); err != nil {
logger.Warning(err) logger.Warning(err)
} }
} }
@ -2868,7 +2926,7 @@ func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlin
if len(inlineKeyboard) > 0 { if len(inlineKeyboard) > 0 {
params.ReplyMarkup = inlineKeyboard[0] params.ReplyMarkup = inlineKeyboard[0]
} }
if _, err := bot.EditMessageText(&params); err != nil { if _, err := bot.EditMessageText(context.Background(), &params); err != nil {
logger.Warning(err) logger.Warning(err)
} }
} }
@ -2881,7 +2939,7 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon
} }
// Send the message // Send the message
sentMsg, err := bot.SendMessage(&telego.SendMessageParams{ sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
ChatID: tu.ID(chatId), ChatID: tu.ID(chatId),
Text: msg, Text: msg,
ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
@ -2904,7 +2962,7 @@ func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
ChatID: tu.ID(chatId), ChatID: tu.ID(chatId),
MessageID: messageID, MessageID: messageID,
} }
if err := bot.DeleteMessage(&params); err != nil { if err := bot.DeleteMessage(context.Background(), &params); err != nil {
logger.Warning("Failed to delete message:", err) logger.Warning("Failed to delete message:", err)
} else { } else {
logger.Info("Message deleted successfully") logger.Info("Message deleted successfully")

View file

@ -3,6 +3,7 @@ package service
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"runtime"
"sync" "sync"
"x-ui/logger" "x-ui/logger"
@ -14,7 +15,8 @@ import (
var ( var (
p *xray.Process p *xray.Process
lock sync.Mutex lock sync.Mutex
isNeedXrayRestart atomic.Bool isNeedXrayRestart atomic.Bool // Indicates that restart was requested for Xray
isManuallyStopped atomic.Bool // Indicates that Xray was stopped manually from the panel
result string result string
) )
@ -32,7 +34,16 @@ func (s *XrayService) GetXrayErr() error {
if p == nil { if p == nil {
return nil return nil
} }
return p.GetErr()
err := p.GetErr()
if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return nil
}
return err
} }
func (s *XrayService) GetXrayResult() string { func (s *XrayService) GetXrayResult() string {
@ -45,7 +56,15 @@ func (s *XrayService) GetXrayResult() string {
if p == nil { if p == nil {
return "" return ""
} }
result = p.GetResult() result = p.GetResult()
if runtime.GOOS == "windows" && result == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return ""
}
return result return result
} }
@ -184,7 +203,8 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
func (s *XrayService) RestartXray(isForce bool) error { func (s *XrayService) RestartXray(isForce bool) error {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
logger.Debug("restart xray, force:", isForce) logger.Debug("restart Xray, force:", isForce)
isManuallyStopped.Store(false)
xrayConfig, err := s.GetXrayConfig() xrayConfig, err := s.GetXrayConfig()
if err != nil { if err != nil {
@ -192,8 +212,8 @@ func (s *XrayService) RestartXray(isForce bool) error {
} }
if s.IsXrayRunning() { if s.IsXrayRunning() {
if !isForce && p.GetConfig().Equals(xrayConfig) { if !isForce && p.GetConfig().Equals(xrayConfig) && !isNeedXrayRestart.Load() {
logger.Debug("It does not need to restart xray") logger.Debug("It does not need to restart Xray")
return nil return nil
} }
p.Stop() p.Stop()
@ -205,12 +225,14 @@ func (s *XrayService) RestartXray(isForce bool) error {
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (s *XrayService) StopXray() error { func (s *XrayService) StopXray() error {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
isManuallyStopped.Store(true)
logger.Debug("Attempting to stop Xray...") logger.Debug("Attempting to stop Xray...")
if s.IsXrayRunning() { if s.IsXrayRunning() {
return p.Stop() return p.Stop()
@ -225,3 +247,8 @@ func (s *XrayService) SetToNeedRestart() {
func (s *XrayService) IsNeedRestartAndSetFalse() bool { func (s *XrayService) IsNeedRestartAndSetFalse() bool {
return isNeedXrayRestart.CompareAndSwap(true, false) return isNeedXrayRestart.CompareAndSwap(true, false)
} }
// Check if Xray is not running and wasn't stopped manually, i.e. crashed
func (s *XrayService) DidXrayCrash() bool {
return !s.IsXrayRunning() && !isManuallyStopped.Load()
}

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "تم تحديث Xray بنجاح" "xraySwitchVersionPopover" = "تم تحديث Xray بنجاح"
"geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟" "geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟"
"geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#." "geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#."
"geofilesUpdateDialogDesc" = "سيؤدي هذا إلى تحديث كافة الملفات."
"geofilesUpdateAll" = "تحديث الكل"
"geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح" "geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح"
"dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة" "dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة"
"logs" = "السجلات" "logs" = "السجلات"
@ -559,24 +561,25 @@
"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة" "resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة"
[tgbot] [tgbot]
"keyboardClosed" = "❌ الكيبورد المخصص اتقفلت!" "keyboardClosed" = "❌ لوحة المفاتيح مغلقة!"
"noResult" = "❗ مفيش نتيجة!" "noResult" = "❗ لا يوجد نتائج!"
"noQuery" = "❌ مش لاقي السؤال! استخدم الأمر تاني!" "noQuery" = "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!"
"wentWrong" = "❌ حصل خطأ!" "wentWrong" = "❌ حدث خطأ ما!"
"noIpRecord" = "❗ مفيش سجل IP!" "noIpRecord" = "❗ لا يوجد سجل IP!"
"noInbounds" = "❗ مفيش إدخال متواجد!" "noInbounds" = "❗ لم يتم العثور على أي وارد!"
"unlimited" = "♾ غير محدود (إعادة ضبط)" "unlimited" = "♾ غير محدود (إعادة تعيين)"
"add" = "أضف" "add" = "إضافة"
"month" = "شهر" "month" = "شهر"
"months" = "شهور" "months" = "أشهر"
"day" = "يوم" "day" = "يوم"
"days" = "أيام" "days" = "أيام"
"hours" = "ساعات" "hours" = "ساعات"
"unknown" = "مش معروف" "minutes" = "دقائق"
"inbounds" = "الإدخالات" "unknown" = "غير معروف"
"inbounds" = "الواردات"
"clients" = "العملاء" "clients" = "العملاء"
"offline" = "🔴 أوفلاين" "offline" = "🔴 غير متصل"
"online" = "🟢 أونلاين" "online" = "🟢 متصل"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ أمر مش معروف." "unknown" = "❗ أمر مش معروف."

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray updated successfully" "xraySwitchVersionPopover" = "Xray updated successfully"
"geofileUpdateDialog" = "Do you really want to update the geofile?" "geofileUpdateDialog" = "Do you really want to update the geofile?"
"geofileUpdateDialogDesc" = "This will update the #filename# file." "geofileUpdateDialogDesc" = "This will update the #filename# file."
"geofilesUpdateDialogDesc" = "This will update all geofiles."
"geofilesUpdateAll" = "Update all"
"geofileUpdatePopover" = "Geofile updated successfully" "geofileUpdatePopover" = "Geofile updated successfully"
"dontRefresh" = "Installation is in progress, please do not refresh this page" "dontRefresh" = "Installation is in progress, please do not refresh this page"
"logs" = "Logs" "logs" = "Logs"
@ -572,6 +574,7 @@
"day" = "Day" "day" = "Day"
"days" = "Days" "days" = "Days"
"hours" = "Hours" "hours" = "Hours"
"minutes" = "Minutes"
"unknown" = "Unknown" "unknown" = "Unknown"
"inbounds" = "Inbounds" "inbounds" = "Inbounds"
"clients" = "Clients" "clients" = "Clients"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray se actualizó correctamente" "xraySwitchVersionPopover" = "Xray se actualizó correctamente"
"geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?" "geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?"
"geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#." "geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#."
"geofilesUpdateDialogDesc" = "Esto actualizará todos los archivos."
"geofilesUpdateAll" = "Actualizar todo"
"geofileUpdatePopover" = "Geofichero actualizado correctamente" "geofileUpdatePopover" = "Geofichero actualizado correctamente"
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página." "dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
"logs" = "Registros" "logs" = "Registros"
@ -559,23 +561,24 @@
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente" "resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
[tgbot] [tgbot]
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!" "keyboardClosed" = "❌ Teclado cerrado!"
"noResult" = "❗ ¡Sin resultados!" "noResult" = "❗ ¡No hay resultados!"
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!" "noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando de nuevo!"
"wentWrong" = "❌ ¡Algo salió mal!" "wentWrong" = "❌ ¡Algo salió mal!"
"noIpRecord" = "❗ ¡Sin Registro de IP!" "noIpRecord" = "❗ ¡No hay registro de IP!"
"noInbounds" = "❗ ¡No se encontraron entradas!" "noInbounds" = "❗ ¡No se encontraron entradas!"
"unlimited" = "♾ Ilimitado" "unlimited" = "♾ Ilimitado (Restablecer)"
"add" = "Agregar" "add" = "Añadir"
"month" = "Mes" "month" = "Mes"
"months" = "Meses" "months" = "Meses"
"day" = "Día" "day" = "Día"
"days" = "Días" "days" = "Días"
"hours" = "Horas" "hours" = "Horas"
"minutes" = "Minutos"
"unknown" = "Desconocido" "unknown" = "Desconocido"
"inbounds" = "Entradas" "inbounds" = "Entradas"
"clients" = "Clientes" "clients" = "Clientes"
"offline" = "🔴 Sin conexión" "offline" = "🔴 Desconectado"
"online" = "🟢 En línea" "online" = "🟢 En línea"
[tgbot.commands] [tgbot.commands]

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray با موفقیت به‌روز شد" "xraySwitchVersionPopover" = "Xray با موفقیت به‌روز شد"
"geofileUpdateDialog" = "آیا واقعاً می‌خواهید فایل جغرافیایی را به‌روز کنید؟" "geofileUpdateDialog" = "آیا واقعاً می‌خواهید فایل جغرافیایی را به‌روز کنید؟"
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را به‌روز می‌کند." "geofileUpdateDialogDesc" = "این عمل فایل #filename# را به‌روز می‌کند."
"geofilesUpdateDialogDesc" = "با این کار همه فایل‌ها به‌روزرسانی می‌شوند."
"geofilesUpdateAll" = "همه را به‌روزرسانی کنید"
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت به‌روز شد" "geofileUpdatePopover" = "فایل جغرافیایی با موفقیت به‌روز شد"
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید" "dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
"logs" = "گزارش‌ها" "logs" = "گزارش‌ها"
@ -559,22 +561,23 @@
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی" "resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
[tgbot] [tgbot]
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!" "keyboardClosed" = "❌ صفحه کلید بسته شد!"
"noResult" = "❗ نتیجهای یافت نشد!" "noResult" = "❗ نتیجه ای یافت نشد!"
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!" "noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!"
"wentWrong" = "❌ مشکلی رخ داده است!" "wentWrong" = "❌ مشکلی پیش آمد!"
"noIpRecord" = "❗ رکورد IP یافت نشد!" "noIpRecord" = "❗ رکورد آی پی وجود ندارد!"
"noInbounds" = "❗ هیچ ورودی یافت نشد!" "noInbounds" = "❗ هیچ ورودی یافت نشد!"
"unlimited" = "♾ - نامحدود(ریست)" "unlimited" = "♾ نامحدود(ریست)"
"add" = "اضافه کردن" "add" = "افزودن"
"month" = "ماه" "month" = "ماه"
"months" = "ماه" "months" = "ماه"
"day" = "روز" "day" = "روز"
"days" = "روز" "days" = "روز"
"hours" = "ساعت‌" "hours" = "ساعت"
"minutes" = "دقیقه"
"unknown" = "نامشخص" "unknown" = "نامشخص"
"inbounds" = "ورودیها" "inbounds" = "ورودی ها"
"clients" = لاینت‌ها" "clients" = اربران"
"offline" = "🔴 آفلاین" "offline" = "🔴 آفلاین"
"online" = "🟢 آنلاین" "online" = "🟢 آنلاین"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray berhasil diperbarui" "xraySwitchVersionPopover" = "Xray berhasil diperbarui"
"geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?" "geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?"
"geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#." "geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#."
"geofilesUpdateDialogDesc" = "Ini akan memperbarui semua berkas."
"geofilesUpdateAll" = "Perbarui semua"
"geofileUpdatePopover" = "Geofile berhasil diperbarui" "geofileUpdatePopover" = "Geofile berhasil diperbarui"
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini" "dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
"logs" = "Log" "logs" = "Log"
@ -559,21 +561,22 @@
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar" "resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Papan ketik kustom ditutup!" "keyboardClosed" = "❌ Keyboard ditutup!"
"noResult" = "❗ Tidak ada hasil!" "noResult" = "❗ Tidak ada hasil!"
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!" "noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!"
"wentWrong" = "❌ Ada yang salah!" "wentWrong" = "❌ Terjadi kesalahan!"
"noIpRecord" = "❗ Tidak ada Catatan IP!" "noIpRecord" = "❗ Tidak ada Catatan IP!"
"noInbounds" = "❗ Tidak ada masuk ditemukan!" "noInbounds" = "❗ Tidak ada inbound yang ditemukan!"
"unlimited" = "♾ Tak terbatas" "unlimited" = "♾ Tidak terbatas (Reset)"
"add" = "Tambah" "add" = "Tambah"
"month" = "Bulan" "month" = "Bulan"
"months" = "Bulan" "months" = "Bulan"
"day" = "Hari" "day" = "Hari"
"days" = "Hari" "days" = "Hari"
"hours" = "Jam" "hours" = "Jam"
"minutes" = "Menit"
"unknown" = "Tidak diketahui" "unknown" = "Tidak diketahui"
"inbounds" = "Masuk" "inbounds" = "Inbound"
"clients" = "Klien" "clients" = "Klien"
"offline" = "🔴 Offline" "offline" = "🔴 Offline"
"online" = "🟢 Online" "online" = "🟢 Online"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xrayの更新が成功しました" "xraySwitchVersionPopover" = "Xrayの更新が成功しました"
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?" "geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。" "geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
"geofilesUpdateDialogDesc" = "これにより、すべてのファイルが更新されます。"
"geofilesUpdateAll" = "すべて更新"
"geofileUpdatePopover" = "ジオファイルの更新が成功しました" "geofileUpdatePopover" = "ジオファイルの更新が成功しました"
"dontRefresh" = "インストール中、このページをリロードしないでください" "dontRefresh" = "インストール中、このページをリロードしないでください"
"logs" = "ログ" "logs" = "ログ"
@ -559,21 +561,22 @@
"resetOutboundTrafficError" = "送信トラフィックのリセットエラー" "resetOutboundTrafficError" = "送信トラフィックのリセットエラー"
[tgbot] [tgbot]
"keyboardClosed" = "❌ カスタムキーボードが閉じられました!" "keyboardClosed" = "❌ キーボードを閉じました!"
"noResult" = "❗ 結果がありません!" "noResult" = "❗ 結果がありません!"
"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!" "noQuery" = "❌ クエリが見つかりません!コマンドを再利用してください!"
"wentWrong" = "❌ 問題が発生しました!" "wentWrong" = "❌ 何かがうまくいかなかった!"
"noIpRecord" = "❗ IP記録がありません!" "noIpRecord" = "❗ IPレコードがありません!"
"noInbounds" = "❗ インバウンド接続が見つかりません!" "noInbounds" = "❗ インバウンドが見つかりません!"
"unlimited" = "♾ 無制限" "unlimited" = "♾ 無制限(リセット)"
"add" = "追加" "add" = "追加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "日" "day" = "日"
"days" = "日" "days" = "日"
"hours" = "時間" "hours" = "時間"
"minutes" = "分"
"unknown" = "不明" "unknown" = "不明"
"inbounds" = "インバウンド接続" "inbounds" = "インバウンド"
"clients" = "クライアント" "clients" = "クライアント"
"offline" = "🔴 オフライン" "offline" = "🔴 オフライン"
"online" = "🟢 オンライン" "online" = "🟢 オンライン"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray atualizado com sucesso" "xraySwitchVersionPopover" = "Xray atualizado com sucesso"
"geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?" "geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?"
"geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#." "geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#."
"geofilesUpdateDialogDesc" = "Isso atualizará todos os arquivos."
"geofilesUpdateAll" = "Atualizar tudo"
"geofileUpdatePopover" = "Geofile atualizado com sucesso" "geofileUpdatePopover" = "Geofile atualizado com sucesso"
"dontRefresh" = "Instalação em andamento, por favor não atualize a página" "dontRefresh" = "Instalação em andamento, por favor não atualize a página"
"logs" = "Logs" "logs" = "Logs"
@ -559,21 +561,22 @@
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída" "resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Teclado personalizado fechado!" "keyboardClosed" = "❌ Teclado fechado!"
"noResult" = "❗ Nenhum resultado!" "noResult" = "❗ Nenhum resultado!"
"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!" "noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!"
"wentWrong" = "❌ Algo deu errado!" "wentWrong" = "❌ Algo deu errado!"
"noIpRecord" = "❗ Nenhum registro de IP!" "noIpRecord" = "❗ Nenhum registro de IP!"
"noInbounds" = "❗ Nenhuma entrada encontrada!" "noInbounds" = "❗ Nenhum inbound encontrado!"
"unlimited" = "♾ Ilimitado (Reiniciar)" "unlimited" = "♾ Ilimitado (Reset)"
"add" = "Adicionar" "add" = "Adicionar"
"month" = "Mês" "month" = "Mês"
"months" = "Meses" "months" = "Meses"
"day" = "Dia" "day" = "Dia"
"days" = "Dias" "days" = "Dias"
"hours" = "Horas" "hours" = "Horas"
"minutes" = "Minutos"
"unknown" = "Desconhecido" "unknown" = "Desconhecido"
"inbounds" = "Entradas" "inbounds" = "Inbounds"
"clients" = "Clientes" "clients" = "Clientes"
"offline" = "🔴 Offline" "offline" = "🔴 Offline"
"online" = "🟢 Online" "online" = "🟢 Online"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray успешно обновлён" "xraySwitchVersionPopover" = "Xray успешно обновлён"
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?" "geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
"geofileUpdateDialogDesc" = "Это обновит файл #filename#." "geofileUpdateDialogDesc" = "Это обновит файл #filename#."
"geofilesUpdateDialogDesc" = "Это обновит все геофайлы."
"geofilesUpdateAll" = "Обновить все"
"geofileUpdatePopover" = "Геофайл успешно обновлён" "geofileUpdatePopover" = "Геофайл успешно обновлён"
"dontRefresh" = "Установка в процессе. Не обновляйте страницу" "dontRefresh" = "Установка в процессе. Не обновляйте страницу"
"logs" = "Журнал" "logs" = "Журнал"
@ -572,6 +574,7 @@
"day" = "День" "day" = "День"
"days" = "Дней" "days" = "Дней"
"hours" = "Часов" "hours" = "Часов"
"minutes" = "Минуты"
"unknown" = "Неизвестно" "unknown" = "Неизвестно"
"inbounds" = "Инбаунды" "inbounds" = "Инбаунды"
"clients" = "Клиенты" "clients" = "Клиенты"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray başarıyla güncellendi" "xraySwitchVersionPopover" = "Xray başarıyla güncellendi"
"geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?" "geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?"
"geofileUpdateDialogDesc" = "Bu işlem #filename# dosyasını güncelleyecektir." "geofileUpdateDialogDesc" = "Bu işlem #filename# dosyasını güncelleyecektir."
"geofilesUpdateDialogDesc" = "Bu, tüm dosyaları güncelleyecektir."
"geofilesUpdateAll" = "Tümünü güncelle"
"geofileUpdatePopover" = "Geofile başarıyla güncellendi" "geofileUpdatePopover" = "Geofile başarıyla güncellendi"
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin" "dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
"logs" = "Günlükler" "logs" = "Günlükler"
@ -559,22 +561,23 @@
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata" "resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Özel klavye kapalı!" "keyboardClosed" = "❌ Klavye kapatıldı!"
"noResult" = "❗ Sonuç yok!" "noResult" = "❗ Sonuç yok!"
"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!" "noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!"
"wentWrong" = "❌ Bir şeyler yanlış gitti!" "wentWrong" = "❌ Bir şeyler yanlış gitti!"
"noIpRecord" = "❗ IP Kaydı yok!" "noIpRecord" = "❗ IP Kaydı Yok!"
"noInbounds" = "❗ Gelen bulunamadı!" "noInbounds" = "❗ Gelen bağlantı bulunamadı!"
"unlimited" = "♾ Sınırsız(Sıfırla)" "unlimited" = "♾ Sınırsız (Sıfırla)"
"add" = "Ekle" "add" = "Ekle"
"month" = "Ay" "month" = "Ay"
"months" = "Aylar" "months" = "Aylar"
"day" = "Gün" "day" = "Gün"
"days" = "Günler" "days" = "Günler"
"hours" = "Saatler" "hours" = "Saatler"
"unknown" = "Bilinmiyor" "minutes" = "Dakika"
"unknown" = "Bilinmeyen"
"inbounds" = "Gelenler" "inbounds" = "Gelenler"
"clients" = "Müşteriler" "clients" = "İstemciler"
"offline" = "🔴 Çevrimdışı" "offline" = "🔴 Çevrimdışı"
"online" = "🟢 Çevrimiçi" "online" = "🟢 Çevrimiçi"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray успішно оновлено" "xraySwitchVersionPopover" = "Xray успішно оновлено"
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?" "geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
"geofileUpdateDialogDesc" = "Це оновить файл #filename#." "geofileUpdateDialogDesc" = "Це оновить файл #filename#."
"geofilesUpdateDialogDesc" = "Це оновить усі геофайли."
"geofilesUpdateAll" = "Оновити все"
"geofileUpdatePopover" = "Геофайл успішно оновлено" "geofileUpdatePopover" = "Геофайл успішно оновлено"
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку" "dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
"logs" = "Журнали" "logs" = "Журнали"
@ -559,19 +561,20 @@
"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку" "resetOutboundTrafficError" = "Помилка скидання вихідного трафіку"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Спеціальна клавіатура закрита!" "keyboardClosed" = "❌ Клавіатуру закрито!"
"noResult" = "❗ Немає результату!" "noResult" = "❗ Немає результату!"
"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!" "noQuery" = "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!"
"wentWrong" = "❌ Щось пішло не так!" "wentWrong" = "❌ Щось пішло не так!"
"noIpRecord" = "❗ Немає IP-запису!" "noIpRecord" = "❗ Немає запису IP!"
"noInbounds" = "❗ Вхідних не знайдено!" "noInbounds" = "❗ Вхідні не знайдені!"
"unlimited" = "♾ Необмежений (скинути)" "unlimited" = "♾ Необмежено (Скинути)"
"add" = "Додати" "add" = "Додати"
"month" = "Місяць" "month" = "Місяць"
"months" = "Місяці" "months" = "Місяці"
"day" = "День" "day" = "День"
"days" = "Дні" "days" = "Дні"
"hours" = "Годинник" "hours" = "Години"
"minutes" = "Хвилини"
"unknown" = "Невідомо" "unknown" = "Невідомо"
"inbounds" = "Вхідні" "inbounds" = "Вхідні"
"clients" = "Клієнти" "clients" = "Клієнти"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray đã được cập nhật thành công" "xraySwitchVersionPopover" = "Xray đã được cập nhật thành công"
"geofileUpdateDialog" = "Bạn có chắc chắn muốn cập nhật geofile không?" "geofileUpdateDialog" = "Bạn có chắc chắn muốn cập nhật geofile không?"
"geofileUpdateDialogDesc" = "Hành động này sẽ cập nhật tệp #filename#." "geofileUpdateDialogDesc" = "Hành động này sẽ cập nhật tệp #filename#."
"geofilesUpdateDialogDesc" = "Thao tác này sẽ cập nhật tất cả các tập tin."
"geofilesUpdateAll" = "Cập nhật tất cả"
"geofileUpdatePopover" = "Geofile đã được cập nhật thành công" "geofileUpdatePopover" = "Geofile đã được cập nhật thành công"
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này." "dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
"logs" = "Nhật ký" "logs" = "Nhật ký"
@ -559,22 +561,23 @@
"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi" "resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!" "keyboardClosed" = "❌ Bàn phím đã đóng!"
"noResult" = "❗ Không có kết quả!" "noResult" = "❗ Không có kết quả!"
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!" "noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!"
"wentWrong" = "❌ Đã xảy ra lỗi!" "wentWrong" = "❌ Đã xảy ra lỗi!"
"noIpRecord" = "❗ Không có bản ghi IP!" "noIpRecord" = "❗ Không có bản ghi IP!"
"noInbounds" = "❗ Không tìm thấy inbound!" "noInbounds" = "❗ Không tìm thấy inbound!"
"unlimited" = "♾ Không giới hạn" "unlimited" = "♾ Không giới hạn (Đặt lại)"
"add" = "Thêm" "add" = "Thêm"
"month" = "Tháng" "month" = "Tháng"
"months" = "Tháng" "months" = "Tháng"
"day" = "Ngày" "day" = "Ngày"
"days" = "Ngày" "days" = "Ngày"
"hours" = "Giờ" "hours" = "Giờ"
"unknown" = "Không rõ" "minutes" = "Phút"
"inbounds" = "Vào" "unknown" = "Không xác định"
"clients" = "Các người dùng" "inbounds" = "Inbound"
"clients" = "Client"
"offline" = "🔴 Ngoại tuyến" "offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến" "online" = "🟢 Trực tuyến"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray 更新成功" "xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您确定要更新地理文件吗?" "geofileUpdateDialog" = "您确定要更新地理文件吗?"
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。" "geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
"geofilesUpdateDialogDesc" = "这将更新所有文件。"
"geofilesUpdateAll" = "全部更新"
"geofileUpdatePopover" = "地理文件更新成功" "geofileUpdatePopover" = "地理文件更新成功"
"dontRefresh" = "安装中,请勿刷新此页面" "dontRefresh" = "安装中,请勿刷新此页面"
"logs" = "日志" "logs" = "日志"
@ -561,19 +563,20 @@
[tgbot] [tgbot]
"keyboardClosed" = "❌ 自定义键盘已关闭!" "keyboardClosed" = "❌ 自定义键盘已关闭!"
"noResult" = "❗ 没有结果!" "noResult" = "❗ 没有结果!"
"noQuery" = "❌ 未找到查询!请重新使用命令!" "noQuery" = "❌ 未找到查询!请再次使用该命令!"
"wentWrong" = "❌ 出了点问题!" "wentWrong" = "❌ 出了点问题!"
"noIpRecord" = "❗ 没有 IP 记录!" "noIpRecord" = "❗ 没有IP记录"
"noInbounds" = "❗ 没有找到入站连接" "noInbounds" = "❗ 未找到入站"
"unlimited" = "♾ 无限" "unlimited" = "♾ 无限(重置)"
"add" = "添加" "add" = "添加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "天" "day" = "天"
"days" = "天" "days" = "天"
"hours" = "小时" "hours" = "小时"
"minutes" = "分钟"
"unknown" = "未知" "unknown" = "未知"
"inbounds" = "入站连接" "inbounds" = "入站"
"clients" = "客户端" "clients" = "客户端"
"offline" = "🔴 离线" "offline" = "🔴 离线"
"online" = "🟢 在线" "online" = "🟢 在线"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray 更新成功" "xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?" "geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。" "geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
"geofilesUpdateDialogDesc" = "這將更新所有文件。"
"geofilesUpdateAll" = "全部更新"
"geofileUpdatePopover" = "地理檔案更新成功" "geofileUpdatePopover" = "地理檔案更新成功"
"dontRefresh" = "安裝中,請勿重新整理此頁面" "dontRefresh" = "安裝中,請勿重新整理此頁面"
"logs" = "日誌" "logs" = "日誌"
@ -561,22 +563,23 @@
[tgbot] [tgbot]
"keyboardClosed" = "❌ 自定義鍵盤已關閉!" "keyboardClosed" = "❌ 自定義鍵盤已關閉!"
"noResult" = "❗ 沒有結果!" "noResult" = "❗ 沒有結果!"
"noQuery" = "❌ 未找到查詢!請重新使用命令!" "noQuery" = "❌ 未找到查詢!請再次使用該命令!"
"wentWrong" = "❌ 出了點問題!" "wentWrong" = "❌ 出了點問題!"
"noIpRecord" = "❗ 沒有 IP 記錄!" "noIpRecord" = "❗ 沒有IP記錄"
"noInbounds" = "❗ 沒有找到入站連線" "noInbounds" = "❗ 未找到入站"
"unlimited" = "♾ 無限" "unlimited" = "♾ 無限(重置)"
"add" = "新增" "add" = "添加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "天" "day" = "天"
"days" = "天" "days" = "天"
"hours" = "小時" "hours" = "小時"
"minutes" = "分鐘"
"unknown" = "未知" "unknown" = "未知"
"inbounds" = "入站連線" "inbounds" = "入站"
"clients" = "客戶端" "clients" = "客戶端"
"offline" = "🔴 離線" "offline" = "🔴 離線"
"online" = "🟢 " "online" = "🟢 線"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ 未知命令" "unknown" = "❗ 未知命令"

249
x-ui.sh
View file

@ -398,37 +398,6 @@ show_log() {
esac esac
} }
show_banlog() {
local system_log="/var/log/fail2ban.log"
echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1
fi
if [[ -f "$system_log" ]]; then
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
echo ""
fi
if [[ -f "${iplimit_banned_log_path}" ]]; then
echo -e "${green}3X-IPL ban log entries:${plain}"
if [[ -s "${iplimit_banned_log_path}" ]]; then
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
else
echo -e "${yellow}Ban log file is empty${plain}"
fi
else
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
fi
echo -e "\n${green}Current jail status:${plain}"
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
}
bbr_menu() { bbr_menu() {
echo -e "${green}\t1.${plain} Enable BBR" echo -e "${green}\t1.${plain} Enable BBR"
echo -e "${green}\t2.${plain} Disable BBR" echo -e "${green}\t2.${plain} Disable BBR"
@ -1005,7 +974,7 @@ ssl_cert_issue() {
# 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-get update && apt-get install socat -y
;; ;;
centos | rhel | almalinux | rocky | ol) centos | rhel | almalinux | rocky | ol)
yum -y update && yum -y install socat yum -y update && yum -y install socat
@ -1330,81 +1299,7 @@ run_speedtest() {
speedtest speedtest
} }
create_iplimit_jails() {
# Use default bantime if not passed => 30 minutes
local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
# On Debian 12+ fail2ban's default backend should be changed to systemd
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
fi
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl]
enabled=true
backend=auto
filter=3x-ipl
action=3x-ipl
logpath=${iplimit_log_path}
maxretry=2
findtime=32
bantime=${bantime}m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
ignoreregex =
EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-allports.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
[Init]
name = default
protocol = tcp
chain = INPUT
EOF
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
}
iplimit_remove_conflicts() {
local jail_files=(
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
)
for file in "${jail_files[@]}"; do
# Check for [3x-ipl] config in jail file then remove it
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
fi
done
}
ip_validation() { ip_validation() {
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
@ -1514,14 +1409,22 @@ install_iplimit() {
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu) ubuntu)
apt-get update
if [[ "${os_version}" -ge 24 ]]; then if [[ "${os_version}" -ge 24 ]]; then
apt update && apt install python3-pip -y apt-get install python3-pip -y
python3 -m pip install pyasynchat --break-system-packages python3 -m pip install pyasynchat --break-system-packages
fi fi
apt update && apt install fail2ban -y apt-get install fail2ban -y
;; ;;
debian | armbian) debian)
apt update && apt install fail2ban -y apt-get update
if [ "$os_version" -ge 12 ]; then
apt-get install -y python3-systemd
fi
apt-get install -y fail2ban
;;
armbian)
apt-get update && apt-get install fail2ban -y
;; ;;
centos | rhel | almalinux | rocky | ol) centos | rhel | almalinux | rocky | ol)
yum update -y && yum install epel-release -y yum update -y && yum install epel-release -y
@ -1632,11 +1535,129 @@ remove_iplimit() {
esac esac
} }
SSH_port_forwarding() { show_banlog() {
local server_ip=$(curl -s --max-time 3 https://api.ipify.org) local system_log="/var/log/fail2ban.log"
if [ -z "$server_ip" ]; then
server_ip=$(curl -s --max-time 3 https://4.ident.me) echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1
fi fi
if [[ -f "$system_log" ]]; then
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
echo ""
fi
if [[ -f "${iplimit_banned_log_path}" ]]; then
echo -e "${green}3X-IPL ban log entries:${plain}"
if [[ -s "${iplimit_banned_log_path}" ]]; then
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
else
echo -e "${yellow}Ban log file is empty${plain}"
fi
else
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
fi
echo -e "\n${green}Current jail status:${plain}"
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
}
create_iplimit_jails() {
# Use default bantime if not passed => 30 minutes
local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
# On Debian 12+ fail2ban's default backend should be changed to systemd
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
fi
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl]
enabled=true
backend=auto
filter=3x-ipl
action=3x-ipl
logpath=${iplimit_log_path}
maxretry=2
findtime=32
bantime=${bantime}m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
ignoreregex =
EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-allports.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
[Init]
name = default
protocol = tcp
chain = INPUT
EOF
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
}
iplimit_remove_conflicts() {
local jail_files=(
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
)
for file in "${jail_files[@]}"; do
# Check for [3x-ipl] config in jail file then remove it
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
fi
done
}
SSH_port_forwarding() {
local URL_lists=(
"https://api4.ipify.org"
"https://ipv4.icanhazip.com"
"https://v4.api.ipinfo.io/ip"
"https://ipv4.myexternalip.com/raw"
"https://4.ident.me"
"https://check-host.net/ip"
)
local server_ip=""
for ip_address in "${URL_lists[@]}"; do
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
if [[ -n "${server_ip}" ]]; then
break
fi
done
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}') local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')