Compare commits

...

33 commits

Author SHA1 Message Date
Sanaei
3299d15f28
Merge branch 'main' into feature/multi-server-support 2025-08-14 18:06:16 +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
mhsanaei
4b03e9d919
v2.6.5 2025-08-04 19:12:37 +02:00
mhsanaei
7e9c3bdbaf
fix: sub enable warning 2025-08-04 19:09:01 +02:00
fgsfds
957f3dbb54
Added xray access log viewer (#3309)
* added xray access log viewer

* made modal window width adaptive

* hide logs button if xray logs are disabled
2025-08-04 18:47:48 +02:00
mhsanaei
05e60af283
fix: IPLimitlog display 2025-08-04 18:23:37 +02:00
mhsanaei
5e40458116
fix: simplify error handling 2025-08-04 18:01:32 +02:00
Sanaei
baf6fdd29d
fix portMap json (#3312)
Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2025-08-04 17:16:11 +02:00
Sanaei
45f78d3521
Merge pull request #3311 from MHSanaei/dokodemo_portMap
add dokodemo port mapping
2025-08-04 17:01:57 +02:00
Alireza Ahmadi
01f984e054 add dokodemo port mapping 2025-08-04 16:45:09 +02:00
Sanaei
e4ba5ba53a
add ech support (#3310)
Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2025-08-04 16:27:57 +02:00
mhsanaei
6ff555c8bb
runs-on: ubuntu-latest 2025-08-04 14:39:12 +02:00
elseif
3c1634ca7c
use musl libc toolchains for build statically linked binaries (#3191)
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-04 11:50:39 +02:00
mhsanaei
561c4810be
default Max Age to 360min 2025-08-04 11:38:23 +02:00
65 changed files with 710 additions and 74153 deletions

2
.github/FUNDING.yml vendored
View file

@ -1,6 +1,6 @@
# 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
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

View file

@ -7,7 +7,7 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View file

@ -8,6 +8,7 @@ on:
branches:
- main
paths:
- '.github/workflows/release.yml'
- '**.js'
- '**.css'
- '**.html'
@ -31,7 +32,7 @@ jobs:
- 386
- armv5
- s390x
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
@ -42,51 +43,37 @@ jobs:
go-version-file: go.mod
check-latest: true
- name: Install dependencies
run: |
sudo apt-get update
if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "armv6" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
elif [ "${{ matrix.platform }}" == "armv5" ]; then
sudo apt install gcc-arm-linux-gnueabi
elif [ "${{ matrix.platform }}" == "s390x" ]; then
sudo apt install gcc-s390x-linux-gnu
fi
- name: Build 3x-ui
- name: Build 3X-UI
run: |
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "armv6" ]; then
export GOARCH=arm
export GOARM=6
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv5" ]; then
export GOARCH=arm
export GOARM=5
export CC=arm-linux-gnueabi-gcc
elif [ "${{ matrix.platform }}" == "s390x" ]; then
export GOARCH=s390x
export CC=s390x-linux-gnu-gcc
fi
go build -ldflags "-w -s" -o xui-release -v main.go
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
case "${{ matrix.platform }}" in
amd64) BOOTLIN_ARCH="x86-64" ;;
arm64) BOOTLIN_ARCH="aarch64" ;;
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
386) BOOTLIN_ARCH="x86-i686" ;;
s390x) BOOTLIN_ARCH="s390x-z13" ;;
esac
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
echo "Downloading: $TARBALL_URL"
cd /tmp
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
tar -xf "$(basename "$TARBALL_URL")"
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
file xui-release
ldd xui-release || echo "Static binary confirmed"
mkdir x-ui
cp xui-release x-ui/

4
.gitignore vendored
View file

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

View file

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

View file

@ -48,7 +48,7 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M
</p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## 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>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## ستاره‌ها در طول زمان

View file

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

View file

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

View file

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

View file

@ -3,7 +3,10 @@ package config
import (
_ "embed"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
)
@ -54,12 +57,32 @@ func GetBinFolderPath() string {
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 {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" {
dbFolderPath = "/etc/x-ui"
if dbFolderPath != "" {
return dbFolderPath
}
return dbFolderPath
if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/etc/x-ui"
}
func GetDBPath() string {
@ -68,8 +91,54 @@ func GetDBPath() string {
func GetLogFolder() string {
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
if logFolderPath == "" {
logFolderPath = "/var/log"
if logFolderPath != "" {
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.3
2.6.6

33
go.mod
View file

@ -9,7 +9,7 @@ require (
github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0
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/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.4
@ -19,8 +19,8 @@ require (
github.com/xlzd/gotp v0.1.0
github.com/xtls/xray-core v1.250803.0
go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.40.0
golang.org/x/text v0.27.0
golang.org/x/crypto v0.41.0
golang.org/x/text v0.28.0
google.golang.org/grpc v1.74.2
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.1
@ -31,11 +31,9 @@ require (
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // 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/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
@ -70,9 +68,8 @@ require (
github.com/refraction-networking/utls v1.8.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.6.6 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
github.com/sagernet/sing v0.7.5 // indirect
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
@ -87,18 +84,18 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.2 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.19.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.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/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/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/protobuf v1.36.7 // 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
)

70
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/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
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/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
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.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
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.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/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
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/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
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/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/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/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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
github.com/mymmrac/telego v1.2.0 h1:CHmR9eiugpTiF/ttppmK89E6mcu9d4DmNQS0tMpUs6M=
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/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
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.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc=
github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/shirou/gopsutil/v4 v4.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/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.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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -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=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
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/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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.6.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.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
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/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
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/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
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/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
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-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
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/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View file

@ -40,19 +40,6 @@ 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() {
case "${release}" in
ubuntu | debian | armbian)

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

@ -560,6 +560,8 @@ class TlsStreamSettings extends XrayCommonClass {
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
echServerKeys = '',
echForceQuery = 'none',
settings = new TlsStreamSettings.Settings()
) {
super();
@ -573,6 +575,8 @@ class TlsStreamSettings extends XrayCommonClass {
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
this.alpn = alpn;
this.echServerKeys = echServerKeys;
this.echForceQuery = echForceQuery;
this.settings = settings;
}
@ -592,7 +596,7 @@ class TlsStreamSettings extends XrayCommonClass {
}
if (!ObjectUtil.isEmpty(json.settings)) {
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.serverName, json.settings.domains);
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.echConfigList);
}
return new TlsStreamSettings(
json.serverName,
@ -605,6 +609,8 @@ class TlsStreamSettings extends XrayCommonClass {
json.enableSessionResumption,
certs,
json.alpn,
json.echServerKeys,
json.echForceQuery,
settings,
);
}
@ -621,6 +627,8 @@ class TlsStreamSettings extends XrayCommonClass {
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn,
echServerKeys: this.echServerKeys,
echForceQuery: this.echForceQuery,
settings: this.settings,
};
}
@ -633,7 +641,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
keyFile = '',
certificate = '',
key = '',
ocspStapling = 0,
oneTimeLoading = false,
usage = USAGE_OPTION.ENCIPHERMENT,
buildChain = false,
@ -644,7 +651,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
this.keyFile = keyFile;
this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate;
this.key = Array.isArray(key) ? key.join('\n') : key;
this.ocspStapling = ocspStapling;
this.oneTimeLoading = oneTimeLoading;
this.usage = usage;
this.buildChain = buildChain
@ -656,7 +662,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
true,
json.certificateFile,
json.keyFile, '', '',
json.ocspStapling,
json.oneTimeLoading,
json.usage,
json.buildChain,
@ -666,7 +671,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
false, '', '',
json.certificate.join('\n'),
json.key.join('\n'),
json.ocspStapling,
json.oneTimeLoading,
json.usage,
json.buildChain,
@ -679,7 +683,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
return {
certificateFile: this.certFile,
keyFile: this.keyFile,
ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading,
usage: this.usage,
buildChain: this.buildChain,
@ -688,7 +691,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
return {
certificate: this.cert.split('\n'),
key: this.key.split('\n'),
ocspStapling: this.ocspStapling,
oneTimeLoading: this.oneTimeLoading,
usage: this.usage,
buildChain: this.buildChain,
@ -701,21 +703,25 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(
allowInsecure = false,
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
echConfigList = '',
) {
super();
this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint;
this.echConfigList = echConfigList;
}
static fromJson(json = {}) {
return new TlsStreamSettings.Settings(
json.allowInsecure,
json.fingerprint,
json.echConfigList,
);
}
toJson() {
return {
allowInsecure: this.allowInsecure,
fingerprint: this.fingerprint,
echConfigList: this.echConfigList
};
}
};
@ -1375,6 +1381,9 @@ class Inbound extends XrayCommonClass {
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
params.set("sni", this.stream.tls.sni);
}
if (this.stream.tls.settings.echConfigList?.length > 0) {
params.set("ech", this.stream.tls.settings.echConfigList);
}
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
params.set("flow", flow);
}
@ -1474,6 +1483,9 @@ class Inbound extends XrayCommonClass {
if (this.stream.tls.settings.allowInsecure) {
params.set("allowInsecure", "1");
}
if (this.stream.tls.settings.echConfigList?.length > 0) {
params.set("ech", this.stream.tls.settings.echConfigList);
}
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
params.set("sni", this.stream.tls.sni);
}
@ -1552,6 +1564,9 @@ class Inbound extends XrayCommonClass {
if (this.stream.tls.settings.allowInsecure) {
params.set("allowInsecure", "1");
}
if (this.stream.tls.settings.echConfigList?.length > 0) {
params.set("ech", this.stream.tls.settings.echConfigList);
}
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
params.set("sni", this.stream.tls.sni);
}
@ -1695,7 +1710,7 @@ class Inbound extends XrayCommonClass {
toJson() {
let streamSettings;
if (this.canEnableStream()) {
if (this.canEnableStream() || this.stream?.sockopt) {
streamSettings = this.stream.toJson();
}
return {
@ -2291,12 +2306,14 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
protocol,
address,
port,
portMap = [],
network = 'tcp,udp',
followRedirect = false
) {
super(protocol);
this.address = address;
this.port = port;
this.portMap = portMap;
this.network = network;
this.followRedirect = followRedirect;
}
@ -2306,6 +2323,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
Protocols.DOKODEMO,
json.address,
json.port,
XrayCommonClass.toHeaders(json.portMap),
json.network,
json.followRedirect,
);
@ -2315,6 +2333,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
return {
address: this.address,
port: this.port,
portMap: XrayCommonClass.toV2Headers(this.portMap, false),
network: this.network,
followRedirect: this.followRedirect,
};

View file

@ -354,13 +354,15 @@ class TlsStreamSettings extends CommonClass {
serverName = '',
alpn = [],
fingerprint = '',
allowInsecure = false
allowInsecure = false,
echConfigList = '',
) {
super();
this.serverName = serverName;
this.alpn = alpn;
this.fingerprint = fingerprint;
this.allowInsecure = allowInsecure;
this.echConfigList = echConfigList;
}
static fromJson(json = {}) {
@ -369,6 +371,7 @@ class TlsStreamSettings extends CommonClass {
json.alpn,
json.fingerprint,
json.allowInsecure,
json.echConfigList,
);
}
@ -378,6 +381,7 @@ class TlsStreamSettings extends CommonClass {
alpn: this.alpn,
fingerprint: this.fingerprint,
allowInsecure: this.allowInsecure,
echConfigList: this.echConfigList
};
}
}
@ -782,7 +786,8 @@ class Outbound extends CommonClass {
let alpn = url.searchParams.get('alpn');
let allowInsecure = url.searchParams.get('allowInsecure');
let sni = url.searchParams.get('sni') ?? '';
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
let ech = url.searchParams.get('ech') ?? '';
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1, ech);
}
if (security == 'reality') {

View file

@ -7,7 +7,7 @@ class AllSetting {
this.webCertFile = "";
this.webKeyFile = "";
this.webBasePath = "/";
this.sessionMaxAge = 60;
this.sessionMaxAge = 360;
this.pageSize = 50;
this.expireDiff = 0;
this.trafficDiff = 0;

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

@ -120,8 +120,8 @@ func (a *InboundController) addInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err)
if err == nil && needRestart {
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
}
@ -138,8 +138,8 @@ func (a *InboundController) delInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, err)
if err == nil && needRestart {
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
}
@ -164,8 +164,8 @@ func (a *InboundController) updateInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, err)
if err == nil && needRestart {
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
}

View file

@ -17,7 +17,8 @@ var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
type ServerController struct {
BaseController
serverService service.ServerService
serverService service.ServerService
settingService service.SettingService
lastStatus *service.Status
lastGetStatusTime time.Time
@ -44,13 +45,16 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray)
g.POST("/updateGeofile", a.updateGeofile)
g.POST("/updateGeofile/:fileName", a.updateGeofile)
g.POST("/logs/:count", a.getLogs)
g.POST("/xraylogs/:count", a.getXrayLogs)
g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
g.POST("/importDB", a.importDB)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
g.POST("/getNewmldsa65", a.getNewmldsa65)
g.POST("/getNewEchCert", a.getNewEchCert)
}
func (a *ServerController) refreshStatus() {
@ -133,6 +137,50 @@ func (a *ServerController) getLogs(c *gin.Context) {
jsonObj(c, logs, nil)
}
func (a *ServerController) getXrayLogs(c *gin.Context) {
count := c.Param("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)
}
func (a *ServerController) getConfigJson(c *gin.Context) {
configJson, err := a.serverService.GetConfigJson()
if err != nil {
@ -208,3 +256,13 @@ func (a *ServerController) getNewmldsa65(c *gin.Context) {
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewEchCert(c *gin.Context) {
sni := c.PostForm("sni")
cert, err := a.serverService.GetNewEchCert(sni)
if err != nil {
jsonMsg(c, "get ech certificate", err)
return
}
jsonObj(c, cert, nil)
}

View file

@ -44,7 +44,7 @@
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="client.email && app.subSettings.enable">
<a-form-item v-if="client.email && app.subSettings?.enable">
<template slot="label">
<a-tooltip>
<template slot="title">

View file

@ -6,6 +6,19 @@
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.portMap"}}'>
<a-button size="small" @click="inbound.settings.portMap.push({name: '', value: ''})">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(pm, index) in inbound.settings.portMap">
<a-input style="width: 50%" v-model.trim="pm.name" placeholder='{{ i18n "pages.inbounds.port"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="pm.value" placeholder='{{ i18n "pages.inbounds.targetAddress" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.settings.portMap.splice(index,1)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
@ -17,4 +30,8 @@
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</a-form>
<!-- sockopt -->
<template>
{{template "form/streamSockopt"}}
</template>
{{end}}

View file

@ -91,9 +91,6 @@
<a-textarea v-model="cert.key"></a-textarea>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="One Time Loading">
<a-switch v-model="cert.oneTimeLoading"></a-switch>
</a-form-item>
@ -106,6 +103,21 @@
<a-switch v-model="cert.buildChain"></a-switch>
</a-form-item>
</template>
<a-form-item label='ECH key'>
<a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
</a-form-item>
<a-form-item label='ECH config'>
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
</a-form-item>
<a-form-item label='ECH force query'>
<a-select v-model="inbound.stream.tls.echForceQuery"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
</a-form-item>
</template>
<!-- reality settings -->

View file

@ -405,9 +405,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -419,9 +419,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -433,9 +433,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -447,9 +447,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -534,9 +534,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -548,9 +548,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -562,9 +562,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -576,9 +576,9 @@
<span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</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>
</div>
</template>
@ -811,17 +811,6 @@
loading(spinning = true) {
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() {
this.refreshing = true;
const msg = await HttpUtil.post('/panel/inbound/list');
@ -893,7 +882,7 @@
}
},
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;
clientStats = dbInbound.clientStats
now = new Date().getTime()
@ -901,6 +890,9 @@
clientCount = clients.length;
if (dbInbound.enable) {
clients.forEach(client => {
if (client.comment) {
comments.set(client.email, client.comment)
}
if (client.enable) {
active.push(client.email);
if (this.isClientOnline(client.email)) online.push(client.email);
@ -929,6 +921,7 @@
depleted: depleted,
expiring: expiring,
online: online,
comments: comments,
};
},
@ -1126,7 +1119,11 @@
protocol: inbound.protocol,
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.allocate = inbound.allocate.toString();
@ -1146,7 +1143,11 @@
protocol: inbound.protocol,
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.allocate = inbound.allocate.toString();

View file

@ -167,7 +167,7 @@
<span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
</a-col>
<a-col>
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag>
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-icon>
</a-col>
</a-row>
</span>
@ -179,6 +179,10 @@
</template>
</template>
<template #actions>
<a-space v-if="app.ipLimitEnable" direction="horizontal" @click="openXrayLogs()" :style="{ justifyContent: 'center' }">
<a-icon type="bars"></a-icon>
<span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span>
</a-space>
<a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
<a-icon type="poweroff"></a-icon>
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
@ -376,6 +380,9 @@
<a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/>
</a-list-item>
</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>
</a-modal>
@ -422,6 +429,48 @@
</a-form>
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
</a-modal>
<a-modal id="xraylog-modal"
v-model="xraylogModal.visible"
:closable="true" @cancel="() => xraylogModal.visible = false"
:class="themeSwitcher.currentTheme"
width="80vw"
footer="">
<template slot="title">
{{ i18n "pages.index.logs" }}
<a-icon :spin="xraylogModal.loading"
type="sync"
:style="{ verticalAlign: 'middle', marginLeft: '10px' }"
:disabled="xraylogModal.loading"
@click="openXrayLogs()">
</a-icon>
</template>
<a-form layout="inline">
<a-form-item :style="{ marginRight: '0.5rem' }">
<a-input-group compact>
<a-select size="small" v-model="xraylogModal.rows" :style="{ width: '70px' }"
@change="openXrayLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="10">10</a-select-option>
<a-select-option value="20">20</a-select-option>
<a-select-option value="50">50</a-select-option>
<a-select-option value="100">100</a-select-option>
<a-select-option value="500">500</a-select-option>
</a-select>
</a-input-group>
</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-button type="primary" icon="download" @click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button>
</a-form-item>
</a-form>
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="xraylogModal.formattedLogs"></div>
</a-modal>
<a-modal id="backup-modal"
v-model="backupModal.visible"
title='{{ i18n "pages.index.backupTitle" }}'
@ -606,6 +655,60 @@
},
};
const xraylogModal = {
visible: false,
logs: [],
rows: 20,
showDirect: true,
showBlocked: true,
showProxy: true,
loading: false,
show(logs) {
this.visible = true;
this.logs = logs;
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
},
formatLogs(logs) {
let formattedLogs = '';
logs.forEach((log, index) => {
if(index > 0) formattedLogs += '<br>';
const parts = log.split(' ');
if(parts.length === 10) {
const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
const from = `<b>${parts[3]}</b>`;
const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
let outboundColor = '';
if (parts[9] === "b") {
outboundColor = ' style="color: #e04141;"'; //red for blocked
}
else if (parts[9] === "p") {
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
}
formattedLogs += `<span${outboundColor}>
${dateTime}
${parts[2]}
${from}
${parts[4]}
${to}
${parts.slice(6, 9).join(' ')}
</span>`;
} else {
formattedLogs += `<span>${log}</span>`;
}
});
return formattedLogs;
},
hide() {
this.visible = false;
},
};
const backupModal = {
visible: false,
show() {
@ -629,10 +732,12 @@
status: new Status(),
versionModal,
logModal,
xraylogModal,
backupModal,
loadingTip: '{{ i18n "loading"}}',
showAlert: false,
showIp: false
showIp: false,
ipLimitEnable: false,
},
methods: {
loading(spinning, tip = '{{ i18n "loading"}}') {
@ -681,16 +786,22 @@
});
},
updateGeofile(fileName) {
const isSingleFile = !!fileName;
this.$confirm({
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"}}',
class: themeSwitcher.currentTheme,
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {
versionModal.hide();
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);
},
});
@ -721,6 +832,16 @@
await PromiseUtil.sleep(500);
logModal.loading = false;
},
async openXrayLogs(){
xraylogModal.loading = true;
const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy});
if (!msg.success) {
return;
}
xraylogModal.show(msg.obj);
await PromiseUtil.sleep(500);
xraylogModal.loading = false;
},
async openConfig() {
this.loading(true);
const msg = await HttpUtil.post('server/getConfigJson');
@ -773,6 +894,12 @@
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
if (msg.success) {
this.ipLimitEnable = msg.obj.ipLimitEnable;
}
while (true) {
try {
await this.getStatus();

View file

@ -39,7 +39,7 @@
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="app.subSettings.enable">
<a-form-item v-if="app.subSettings?.enable">
<template slot="label">
<a-tooltip>
<template slot="title">

View file

@ -199,7 +199,7 @@
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
</td>
</tr>
<tr v-if="app.ipLimitEnable">
<tr v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0">
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
<td>
<a-tag>[[ infoModal.clientIps ]]</a-tag>

View file

@ -152,6 +152,16 @@
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
},
async getNewEchCert() {
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
},
},
});

View file

@ -21,7 +21,7 @@
</a-space>
</template>
<tr-qr-modal class="qr-modal">
<template v-if="app.subSettings.enable && qrModal.subId">
<template v-if="app.subSettings?.enable && qrModal.subId">
<tr-qr-box class="qr-box">
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
<tr-qr-bg class="qr-bg-sub">

View file

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

View file

@ -2,6 +2,7 @@ package service
import (
"archive/zip"
"bufio"
"bytes"
"encoding/json"
"fmt"
@ -329,7 +330,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
continue
}
if major > 25 || (major == 25 && minor > 7) || (major == 25 && minor == 7 && patch >= 26) {
if major > 25 || (major == 25 && minor > 8) || (major == 25 && minor == 8 && patch >= 3) {
versions = append(versions, release.TagName)
}
}
@ -346,7 +347,6 @@ func (s *ServerService) StopXrayService() error {
}
func (s *ServerService) RestartXrayService() error {
s.xrayService.StopXray()
err := s.xrayService.RestartXray(true)
if err != nil {
logger.Error("start xray failed:", err)
@ -481,6 +481,81 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
return lines
}
func (s *ServerService) GetXrayLogs(
count string,
filter string,
showDirect string,
showBlocked string,
showProxy string,
freedoms []string,
blackholes []string) []string {
countInt, _ := strconv.Atoi(count)
var lines []string
pathToAccessLog, err := xray.GetAccessLogPath()
if err != nil {
return lines
}
file, err := os.Open(pathToAccessLog)
if err != nil {
return lines
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.Contains(line, "api -> api") {
//skipping empty lines and api calls
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)
}
if len(lines) > countInt {
lines = lines[len(lines)-countInt:]
}
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) {
config, err := s.xrayService.GetXrayConfig()
if err != nil {
@ -666,27 +741,43 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
return nil
}
var fileURL string
for _, file := range files {
if file.FileName == fileName {
fileURL = file.URL
break
var errorMessages []string
if fileName == "" {
for _, file := range files {
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 == "" {
return common.NewErrorf("File '%s' not found in the list of Geofiles", fileName)
}
var fileURL string
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 {
return common.NewErrorf("Error downloading Geofile '%s': %v", fileName, err)
if err := downloadFile(fileURL, destPath); err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", fileName, err))
}
}
err := s.RestartXrayService()
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
@ -743,3 +834,27 @@ func (s *ServerService) GetNewmldsa65() (any, error) {
return keyPair, nil
}
func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
if len(lines) < 4 {
return nil, common.NewError("invalid ech cert")
}
configList := lines[1]
serverKeys := lines[3]
return map[string]interface{}{
"echServerKeys": serverKeys,
"echConfigList": configList,
}, nil
}

View file

@ -32,7 +32,7 @@ var defaultValueMap = map[string]string{
"webKeyFile": "",
"secret": random.Seq(32),
"webBasePath": "/",
"sessionMaxAge": "60",
"sessionMaxAge": "360",
"pageSize": "50",
"expireDiff": "0",
"trafficDiff": "0",

View file

@ -1,6 +1,7 @@
package service
import (
"context"
"crypto/rand"
"embed"
"encoding/base64"
@ -148,7 +149,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
}
// 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{
{Command: "start", Description: t.I18nBot("tgbot.commands.startDesc")},
{Command: "help", Description: t.I18nBot("tgbot.commands.helpDesc")},
@ -221,8 +222,9 @@ func (t *Tgbot) SetHostname() {
}
func (t *Tgbot) Stop() {
botHandler.Stop()
bot.StopLongPolling()
if botHandler != nil {
botHandler.Stop()
}
logger.Info("Stop Telegram receiver ...")
isRunning = false
adminIds = nil
@ -255,26 +257,29 @@ func (t *Tgbot) OnReceive() {
Timeout: 10,
}
updates, _ := bot.UpdatesViaLongPolling(&params)
updates, _ := bot.UpdatesViaLongPolling(context.Background(), &params)
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)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
return nil
}, 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)
t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
return nil
}, 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)
t.answerCallback(&query, checkAdmin(query.From.ID))
return nil
}, 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 {
switch userState {
case "awaiting_id":
@ -284,7 +289,7 @@ func (t *Tgbot) OnReceive() {
inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID)
message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(message.Chat.ID, message_text)
return
return nil
}
client_Id = strings.TrimSpace(message.Text)
@ -309,7 +314,7 @@ func (t *Tgbot) OnReceive() {
if client_TrPassword == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID)
return
return nil
}
client_TrPassword = strings.TrimSpace(message.Text)
@ -334,7 +339,7 @@ func (t *Tgbot) OnReceive() {
if client_ShPassword == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID)
return
return nil
}
client_ShPassword = strings.TrimSpace(message.Text)
@ -359,7 +364,7 @@ func (t *Tgbot) OnReceive() {
if client_Email == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID)
return
return nil
}
client_Email = strings.TrimSpace(message.Text)
@ -384,7 +389,7 @@ func (t *Tgbot) OnReceive() {
if client_Comment == strings.TrimSpace(message.Text) {
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
delete(userStates, message.Chat.ID)
return
return nil
}
client_Comment = strings.TrimSpace(message.Text)
@ -417,6 +422,7 @@ func (t *Tgbot) OnReceive() {
}
}
}
return nil
}, th.AnyMessage())
botHandler.Start()
@ -699,7 +705,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(chatId, message_text, messageId)
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
case "add_client_limit_traffic_in":
if len(dataArray) >= 2 {
@ -914,7 +920,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(chatId, message_text, messageId)
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
case "add_client_reset_exp_in":
if len(dataArray) >= 2 {
@ -1096,7 +1102,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(chatId, message_text, messageId)
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
case "add_client_ip_limit_in":
if len(dataArray) >= 2 {
@ -1157,8 +1163,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "clear_ips":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@ -1285,7 +1289,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(chatId, message_text)
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
}
return
} else {
@ -1859,7 +1863,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
params.ReplyMarkup = replyMarkup[0]
}
_, err := bot.SendMessage(&params)
_, err := bot.SendMessage(context.Background(), &params)
if err != nil {
logger.Warning("Error sending telegram message :", err)
}
@ -2765,7 +2769,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
tu.ID(chatId),
tu.File(file),
)
_, err = bot.SendDocument(document)
_, err = bot.SendDocument(context.Background(), document)
if err != nil {
logger.Error("Error in uploading backup: ", err)
}
@ -2779,7 +2783,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
tu.ID(chatId),
tu.File(file),
)
_, err = bot.SendDocument(document)
_, err = bot.SendDocument(context.Background(), document)
if err != nil {
logger.Error("Error in uploading config.json: ", err)
}
@ -2803,7 +2807,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
tu.ID(chatId),
tu.File(file),
)
_, err = bot.SendDocument(document)
_, err = bot.SendDocument(context.Background(), document)
if err != nil {
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
}
@ -2824,7 +2828,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
tu.ID(chatId),
tu.File(file),
)
_, err = bot.SendDocument(document)
_, err = bot.SendDocument(context.Background(), document)
if err != nil {
logger.Error("Error in uploading IPLimitBannedLog: ", err)
}
@ -2842,7 +2846,7 @@ func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
CallbackQueryID: id,
Text: message,
}
if err := bot.AnswerCallbackQuery(&params); err != nil {
if err := bot.AnswerCallbackQuery(context.Background(), &params); err != nil {
logger.Warning(err)
}
}
@ -2853,7 +2857,7 @@ func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyb
MessageID: messageID,
ReplyMarkup: inlineKeyboard,
}
if _, err := bot.EditMessageReplyMarkup(&params); err != nil {
if _, err := bot.EditMessageReplyMarkup(context.Background(), &params); err != nil {
logger.Warning(err)
}
}
@ -2868,7 +2872,7 @@ func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlin
if len(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)
}
}
@ -2881,7 +2885,7 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon
}
// Send the message
sentMsg, err := bot.SendMessage(&telego.SendMessageParams{
sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
ChatID: tu.ID(chatId),
Text: msg,
ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
@ -2904,7 +2908,7 @@ func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
ChatID: tu.ID(chatId),
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)
} else {
logger.Info("Message deleted successfully")

View file

@ -3,6 +3,7 @@ package service
import (
"encoding/json"
"errors"
"runtime"
"sync"
"x-ui/logger"
@ -14,7 +15,8 @@ import (
var (
p *xray.Process
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
)
@ -32,7 +34,16 @@ func (s *XrayService) GetXrayErr() error {
if p == 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 {
@ -45,7 +56,15 @@ func (s *XrayService) GetXrayResult() string {
if p == nil {
return ""
}
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
}
@ -184,7 +203,8 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
func (s *XrayService) RestartXray(isForce bool) error {
lock.Lock()
defer lock.Unlock()
logger.Debug("restart xray, force:", isForce)
logger.Debug("restart Xray, force:", isForce)
isManuallyStopped.Store(false)
xrayConfig, err := s.GetXrayConfig()
if err != nil {
@ -192,8 +212,8 @@ func (s *XrayService) RestartXray(isForce bool) error {
}
if s.IsXrayRunning() {
if !isForce && p.GetConfig().Equals(xrayConfig) {
logger.Debug("It does not need to restart xray")
if !isForce && p.GetConfig().Equals(xrayConfig) && !isNeedXrayRestart.Load() {
logger.Debug("It does not need to restart Xray")
return nil
}
p.Stop()
@ -205,12 +225,14 @@ func (s *XrayService) RestartXray(isForce bool) error {
if err != nil {
return err
}
return nil
}
func (s *XrayService) StopXray() error {
lock.Lock()
defer lock.Unlock()
isManuallyStopped.Store(true)
logger.Debug("Attempting to stop Xray...")
if s.IsXrayRunning() {
return p.Stop()
@ -225,3 +247,8 @@ func (s *XrayService) SetToNeedRestart() {
func (s *XrayService) IsNeedRestartAndSetFalse() bool {
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 بنجاح"
"geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟"
"geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#."
"geofilesUpdateDialogDesc" = "سيؤدي هذا إلى تحديث كافة الملفات."
"geofilesUpdateAll" = "تحديث الكل"
"geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح"
"dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة"
"logs" = "السجلات"
@ -158,6 +160,7 @@
"remark" = "ملاحظة"
"protocol" = "بروتوكول"
"port" = "بورت"
"portMap" = "خريطة البورت"
"traffic" = "الترافيك"
"details" = "تفاصيل"
"transportConfig" = "نقل"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray updated successfully"
"geofileUpdateDialog" = "Do you really want to update the geofile?"
"geofileUpdateDialogDesc" = "This will update the #filename# file."
"geofilesUpdateDialogDesc" = "This will update all geofiles."
"geofilesUpdateAll" = "Update all"
"geofileUpdatePopover" = "Geofile updated successfully"
"dontRefresh" = "Installation is in progress, please do not refresh this page"
"logs" = "Logs"
@ -158,6 +160,7 @@
"remark" = "Remark"
"protocol" = "Protocol"
"port" = "Port"
"portMap" = "Port Mapping"
"traffic" = "Traffic"
"details" = "Details"
"transportConfig" = "Transport"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray se actualizó correctamente"
"geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?"
"geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#."
"geofilesUpdateDialogDesc" = "Esto actualizará todos los archivos."
"geofilesUpdateAll" = "Actualizar todo"
"geofileUpdatePopover" = "Geofichero actualizado correctamente"
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
"logs" = "Registros"
@ -158,6 +160,7 @@
"remark" = "Notas"
"protocol" = "Protocolo"
"port" = "Puerto"
"portMap" = "Puertos de Destino"
"traffic" = "Tráfico"
"details" = "Detalles"
"transportConfig" = "Transporte"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray با موفقیت به‌روز شد"
"geofileUpdateDialog" = "آیا واقعاً می‌خواهید فایل جغرافیایی را به‌روز کنید؟"
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را به‌روز می‌کند."
"geofilesUpdateDialogDesc" = "با این کار همه فایل‌ها به‌روزرسانی می‌شوند."
"geofilesUpdateAll" = "همه را به‌روزرسانی کنید"
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت به‌روز شد"
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
"logs" = "گزارش‌ها"
@ -158,6 +160,7 @@
"remark" = "نام"
"protocol" = "پروتکل"
"port" = "پورت"
"portMap" = "پورت‌های نظیر"
"traffic" = "ترافیک"
"details" = "توضیحات"
"transportConfig" = "نحوه اتصال"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray berhasil diperbarui"
"geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?"
"geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#."
"geofilesUpdateDialogDesc" = "Ini akan memperbarui semua berkas."
"geofilesUpdateAll" = "Perbarui semua"
"geofileUpdatePopover" = "Geofile berhasil diperbarui"
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
"logs" = "Log"
@ -158,6 +160,7 @@
"remark" = "Catatan"
"protocol" = "Protokol"
"port" = "Port"
"portMap" = "Port Mapping"
"traffic" = "Traffic"
"details" = "Rincian"
"transportConfig" = "Transport"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xrayの更新が成功しました"
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
"geofilesUpdateDialogDesc" = "これにより、すべてのファイルが更新されます。"
"geofilesUpdateAll" = "すべて更新"
"geofileUpdatePopover" = "ジオファイルの更新が成功しました"
"dontRefresh" = "インストール中、このページをリロードしないでください"
"logs" = "ログ"
@ -158,6 +160,7 @@
"remark" = "備考"
"protocol" = "プロトコル"
"port" = "ポート"
"portMap" = "ポートマッピング"
"traffic" = "トラフィック"
"details" = "詳細情報"
"transportConfig" = "トランスポート設定"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray atualizado com sucesso"
"geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?"
"geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#."
"geofilesUpdateDialogDesc" = "Isso atualizará todos os arquivos."
"geofilesUpdateAll" = "Atualizar tudo"
"geofileUpdatePopover" = "Geofile atualizado com sucesso"
"dontRefresh" = "Instalação em andamento, por favor não atualize a página"
"logs" = "Logs"
@ -158,6 +160,7 @@
"remark" = "Observação"
"protocol" = "Protocolo"
"port" = "Porta"
"portMap" = "Porta Mapeada"
"traffic" = "Tráfego"
"details" = "Detalhes"
"transportConfig" = "Transporte"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray успешно обновлён"
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
"geofilesUpdateDialogDesc" = "Это обновит все геофайлы."
"geofilesUpdateAll" = "Обновить все"
"geofileUpdatePopover" = "Геофайл успешно обновлён"
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
"logs" = "Журнал"
@ -158,6 +160,7 @@
"remark" = "Примечание"
"protocol" = "Протокол"
"port" = "Порт"
"portMap" = "Порт-маппинг"
"traffic" = "Трафик"
"details" = "Подробнее"
"transportConfig" = "Транспорт"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray başarıyla güncellendi"
"geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?"
"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"
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
"logs" = "Günlükler"
@ -158,6 +160,7 @@
"remark" = "Açıklama"
"protocol" = "Protokol"
"port" = "Port"
"portMap" = "Port Atama"
"traffic" = "Trafik"
"details" = "Detaylar"
"transportConfig" = "Taşıma"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray успішно оновлено"
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
"geofileUpdateDialogDesc" = "Це оновить файл #filename#."
"geofilesUpdateDialogDesc" = "Це оновить усі геофайли."
"geofilesUpdateAll" = "Оновити все"
"geofileUpdatePopover" = "Геофайл успішно оновлено"
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
"logs" = "Журнали"
@ -158,6 +160,7 @@
"remark" = "Примітка"
"protocol" = "Протокол"
"port" = "Порт"
"portMap" = "Порт-перехід"
"traffic" = "Трафік"
"details" = "Деталі"
"transportConfig" = "Транспорт"

View file

@ -132,6 +132,8 @@
"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?"
"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"
"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ý"
@ -158,6 +160,7 @@
"remark" = "Chú thích"
"protocol" = "Giao thức"
"port" = "Cổng"
"portMap" = "Cổng tạo"
"traffic" = "Lưu lượng"
"details" = "Chi tiết"
"transportConfig" = "Giao vận"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您确定要更新地理文件吗?"
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
"geofilesUpdateDialogDesc" = "这将更新所有文件。"
"geofilesUpdateAll" = "全部更新"
"geofileUpdatePopover" = "地理文件更新成功"
"dontRefresh" = "安装中,请勿刷新此页面"
"logs" = "日志"
@ -158,6 +160,7 @@
"remark" = "备注"
"protocol" = "协议"
"port" = "端口"
"portMap" = "端口映射"
"traffic" = "流量"
"details" = "详细信息"
"transportConfig" = "传输配置"

View file

@ -132,6 +132,8 @@
"xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
"geofilesUpdateDialogDesc" = "這將更新所有文件。"
"geofilesUpdateAll" = "全部更新"
"geofileUpdatePopover" = "地理檔案更新成功"
"dontRefresh" = "安裝中,請勿重新整理此頁面"
"logs" = "日誌"
@ -158,6 +160,7 @@
"remark" = "備註"
"protocol" = "協議"
"port" = "埠"
"portMap" = "埠映射"
"traffic" = "流量"
"details" = "詳細資訊"
"transportConfig" = "傳輸配置"