mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-08-23 19:36:54 +00:00
Compare commits
No commits in common. "main" and "v2.6.6" have entirely different histories.
59 changed files with 74214 additions and 528 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: MHSanaei
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
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
|
||||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -29,9 +29,9 @@ main
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Ignore Go build files
|
# Ignore Go specific files
|
||||||
*.exe
|
*.exe
|
||||||
x-ui.db
|
*.exe~
|
||||||
|
|
||||||
# Ignore Docker specific files
|
# Ignore Docker specific files
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# ========================================================
|
# ========================================================
|
||||||
# Stage: Builder
|
# Stage: Builder
|
||||||
# ========================================================
|
# ========================================================
|
||||||
FROM golang:1.25-alpine AS builder
|
FROM golang:1.24-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## النجوم عبر الزمن
|
## النجوم عبر الزمن
|
||||||
|
|
|
@ -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`
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## Estrellas a lo Largo del Tiempo
|
## Estrellas a lo Largo del Tiempo
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## ستارهها در طول زمان
|
## ستارهها در طول زمان
|
||||||
|
|
|
@ -48,7 +48,7 @@ For full documentation, please visit the [project Wiki](https://github.com/MHSan
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## Stargazers over Time
|
## Stargazers over Time
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## Звезды с течением времени
|
## Звезды с течением времени
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## 随时间变化的星标数
|
## 随时间变化的星标数
|
||||||
|
|
|
@ -3,10 +3,7 @@ package config
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,32 +54,12 @@ 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 == "" {
|
||||||
return dbFolderPath
|
dbFolderPath = "/etc/x-ui"
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
return dbFolderPath
|
||||||
return getBaseDir()
|
|
||||||
}
|
|
||||||
return "/etc/x-ui"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
|
@ -91,54 +68,8 @@ 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 == "" {
|
||||||
return logFolderPath
|
logFolderPath = "/var/log"
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
return logFolderPath
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.25.0
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.3
|
github.com/gin-contrib/gzip v1.2.3
|
||||||
|
@ -15,7 +15,7 @@ require (
|
||||||
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.65.0
|
github.com/valyala/fasthttp v1.64.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
|
||||||
|
@ -56,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.32 // indirect
|
github.com/mattn/go-sqlite3 v1.14.30 // 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
|
||||||
|
@ -92,7 +92,7 @@ require (
|
||||||
golang.org/x/tools v0.36.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-20250521234502-f333402bd9cb // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||||
google.golang.org/protobuf v1.36.7 // 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-20250503011706-39ed1f5ac29c // indirect
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -95,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.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.30/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=
|
||||||
|
@ -162,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.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
|
||||||
github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
|
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
|
||||||
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=
|
||||||
|
@ -226,8 +226,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||||
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-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
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=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
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-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
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 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.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||||
|
|
19
install.sh
19
install.sh
|
@ -7,6 +7,7 @@ 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
|
||||||
|
@ -57,7 +58,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-get install -y -q wget curl tar tzdata
|
apt-get update && apt install -y -q wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
@ -72,18 +73,10 @@ 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=(
|
|
||||||
"https://api4.ipify.org"
|
for ip_service_addr in "${show_ip_service_lists[@]}"; do
|
||||||
"https://ipv4.icanhazip.com"
|
local server_ip=$(curl -s --max-time 3 ${ip_service_addr} 2>/dev/null)
|
||||||
"https://v4.api.ipinfo.io/ip"
|
if [ -n "${server_ip}" ]; then
|
||||||
"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
|
||||||
|
|
|
@ -209,10 +209,9 @@ 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)
|
||||||
switch security {
|
if security == "tls" {
|
||||||
case "tls":
|
|
||||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
||||||
case "reality":
|
} else if security == "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")
|
||||||
|
|
3
web/assets/ant-design-vue/antd-with-locales.min.js
vendored
Normal file
3
web/assets/ant-design-vue/antd-with-locales.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
web/assets/ant-design-vue/antd.less
Normal file
7
web/assets/ant-design-vue/antd.less
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@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;
|
1
web/assets/ant-design-vue/antd.min.js.map
Normal file
1
web/assets/ant-design-vue/antd.min.js.map
Normal file
File diff suppressed because one or more lines are too long
4
web/assets/axios/axios.min.js
vendored
4
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1710,7 +1710,7 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
let streamSettings;
|
let streamSettings;
|
||||||
if (this.canEnableStream() || this.stream?.sockopt) {
|
if (this.canEnableStream()) {
|
||||||
streamSettings = this.stream.toJson();
|
streamSettings = this.stream.toJson();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -919,14 +919,12 @@ 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 = {}) {
|
||||||
|
@ -934,7 +932,6 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||||
json.packets,
|
json.packets,
|
||||||
json.length,
|
json.length,
|
||||||
json.interval,
|
json.interval,
|
||||||
json.maxSplit
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -943,14 +940,12 @@ 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 = {}) {
|
||||||
|
@ -958,7 +953,6 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
||||||
json.type,
|
json.type,
|
||||||
json.packet,
|
json.packet,
|
||||||
json.delay,
|
json.delay,
|
||||||
json.applyTo
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -967,7 +961,6 @@ 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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -995,7 +988,7 @@ Outbound.DNSSettings = class extends CommonClass {
|
||||||
network = 'udp',
|
network = 'udp',
|
||||||
address = '',
|
address = '',
|
||||||
port = 53,
|
port = 53,
|
||||||
nonIPQuery = 'reject',
|
nonIPQuery = 'drop',
|
||||||
blockTypes = []
|
blockTypes = []
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
11818
web/assets/vue/vue.common.dev.js
Normal file
11818
web/assets/vue/vue.common.dev.js
Normal file
File diff suppressed because it is too large
Load diff
5
web/assets/vue/vue.common.js
Normal file
5
web/assets/vue/vue.common.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./vue.common.prod.js')
|
||||||
|
} else {
|
||||||
|
module.exports = require('./vue.common.dev.js')
|
||||||
|
}
|
6
web/assets/vue/vue.common.prod.js
Normal file
6
web/assets/vue/vue.common.prod.js
Normal file
File diff suppressed because one or more lines are too long
11731
web/assets/vue/vue.esm.browser.js
Normal file
11731
web/assets/vue/vue.esm.browser.js
Normal file
File diff suppressed because it is too large
Load diff
6
web/assets/vue/vue.esm.browser.min.js
vendored
Normal file
6
web/assets/vue/vue.esm.browser.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11918
web/assets/vue/vue.esm.js
Normal file
11918
web/assets/vue/vue.esm.js
Normal file
File diff suppressed because it is too large
Load diff
11932
web/assets/vue/vue.js
Normal file
11932
web/assets/vue/vue.js
Normal file
File diff suppressed because it is too large
Load diff
8736
web/assets/vue/vue.runtime.common.dev.js
Normal file
8736
web/assets/vue/vue.runtime.common.dev.js
Normal file
File diff suppressed because it is too large
Load diff
5
web/assets/vue/vue.runtime.common.js
Normal file
5
web/assets/vue/vue.runtime.common.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./vue.runtime.common.prod.js')
|
||||||
|
} else {
|
||||||
|
module.exports = require('./vue.runtime.common.dev.js')
|
||||||
|
}
|
6
web/assets/vue/vue.runtime.common.prod.js
Normal file
6
web/assets/vue/vue.runtime.common.prod.js
Normal file
File diff suppressed because one or more lines are too long
8825
web/assets/vue/vue.runtime.esm.js
Normal file
8825
web/assets/vue/vue.runtime.esm.js
Normal file
File diff suppressed because it is too large
Load diff
8826
web/assets/vue/vue.runtime.js
Normal file
8826
web/assets/vue/vue.runtime.js
Normal file
File diff suppressed because it is too large
Load diff
6
web/assets/vue/vue.runtime.min.js
vendored
Normal file
6
web/assets/vue/vue.runtime.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
76
web/assets/vue/vue.runtime.mjs
Normal file
76
web/assets/vue/vue.runtime.mjs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
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
|
|
@ -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"`
|
||||||
|
|
|
@ -42,9 +42,6 @@
|
||||||
<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 -->
|
||||||
|
@ -78,11 +75,6 @@
|
||||||
<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>
|
||||||
|
@ -105,7 +97,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 ['reject','drop','skip']" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in ['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' >
|
||||||
|
|
|
@ -1119,11 +1119,7 @@
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()){
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
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();
|
||||||
|
|
||||||
|
@ -1143,11 +1139,7 @@
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()){
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
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();
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@
|
||||||
settings: {
|
settings: {
|
||||||
domainStrategy: "AsIs",
|
domainStrategy: "AsIs",
|
||||||
noises: [
|
noises: [
|
||||||
{ type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" },
|
{ type: "rand", packet: "10-20", delay: "10-16" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -397,7 +397,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNoise() {
|
addNoise() {
|
||||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
|
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
|
||||||
this.noisesArray = [...this.noisesArray, newNoise];
|
this.noisesArray = [...this.noisesArray, newNoise];
|
||||||
},
|
},
|
||||||
removeNoise(index) {
|
removeNoise(index) {
|
||||||
|
@ -420,11 +420,6 @@
|
||||||
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: {
|
||||||
|
|
|
@ -90,18 +90,6 @@
|
||||||
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>
|
||||||
|
|
|
@ -11,6 +11,7 @@ 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"
|
||||||
|
@ -57,21 +58,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +193,10 @@ 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{}
|
||||||
|
|
|
@ -177,16 +177,15 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||||
|
|
||||||
// Secure client ID
|
// Secure client ID
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
switch inbound.Protocol {
|
if inbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
if client.Password == "" {
|
if client.Password == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
case "shadowsocks":
|
} else if inbound.Protocol == "shadowsocks" {
|
||||||
if client.Email == "" {
|
if client.Email == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
if client.ID == "" {
|
if client.ID == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
|
@ -437,16 +436,15 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||||
|
|
||||||
// Secure client ID
|
// Secure client ID
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
switch oldInbound.Protocol {
|
if oldInbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
if client.Password == "" {
|
if client.Password == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
case "shadowsocks":
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
if client.Email == "" {
|
if client.Email == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
if client.ID == "" {
|
if client.ID == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
|
@ -633,14 +631,13 @@ 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 := ""
|
||||||
switch oldInbound.Protocol {
|
if oldInbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
newClientId = clients[0].Password
|
newClientId = clients[0].Password
|
||||||
case "shadowsocks":
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
oldClientId = oldClient.Email
|
oldClientId = oldClient.Email
|
||||||
newClientId = clients[0].Email
|
newClientId = clients[0].Email
|
||||||
default:
|
} else {
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
newClientId = clients[0].ID
|
newClientId = clients[0].ID
|
||||||
}
|
}
|
||||||
|
@ -1247,12 +1244,11 @@ 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 {
|
||||||
switch inbound.Protocol {
|
if inbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
case "shadowsocks":
|
} else if inbound.Protocol == "shadowsocks" {
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
default:
|
} else {
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1332,12 +1328,11 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
switch inbound.Protocol {
|
if inbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
case "shadowsocks":
|
} else if inbound.Protocol == "shadowsocks" {
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
default:
|
} else {
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
clientOldEnabled = oldClient.Enable
|
clientOldEnabled = oldClient.Enable
|
||||||
|
@ -1396,12 +1391,11 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
switch inbound.Protocol {
|
if inbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
case "shadowsocks":
|
} else if inbound.Protocol == "shadowsocks" {
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
default:
|
} else {
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1454,12 +1448,11 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
switch inbound.Protocol {
|
if inbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
case "shadowsocks":
|
} else if inbound.Protocol == "shadowsocks" {
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
default:
|
} else {
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1515,12 +1508,11 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
switch inbound.Protocol {
|
if inbound.Protocol == "trojan" {
|
||||||
case "trojan":
|
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
case "shadowsocks":
|
} else if inbound.Protocol == "shadowsocks" {
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
default:
|
} else {
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -235,21 +235,8 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP fetching with caching
|
// IP fetching with caching
|
||||||
showIp4ServiceLists := []string{
|
showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"}
|
||||||
"https://api4.ipify.org",
|
showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"}
|
||||||
"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 {
|
||||||
|
|
|
@ -40,6 +40,7 @@ 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
|
||||||
|
@ -640,14 +641,13 @@ 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 {
|
||||||
switch num {
|
if num == -2 {
|
||||||
case -2:
|
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
case -1:
|
} else if num == -1 {
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -704,10 +704,6 @@ 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(callbackQuery.Message.GetChat().ID, 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"))
|
||||||
|
@ -719,14 +715,13 @@ 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 {
|
||||||
switch num {
|
if num == -2 {
|
||||||
case -2:
|
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
case -1:
|
} else if num == -1 {
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -849,14 +844,13 @@ 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 {
|
||||||
switch num {
|
if num == -2 {
|
||||||
case -2:
|
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
case -1:
|
} else if num == -1 {
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -925,10 +919,6 @@ 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(callbackQuery.Message.GetChat().ID, 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"))
|
||||||
|
@ -940,14 +930,13 @@ 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 {
|
||||||
switch num {
|
if num == -2 {
|
||||||
case -2:
|
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
case -1:
|
} else if num == -1 {
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1046,14 +1035,13 @@ 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 {
|
||||||
switch num {
|
if num == -2 {
|
||||||
case -2:
|
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
case -1:
|
} else if num == -1 {
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1113,10 +1101,6 @@ 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(callbackQuery.Message.GetChat().ID, 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"))
|
||||||
|
@ -1128,14 +1112,13 @@ 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 {
|
||||||
switch num {
|
if num == -2 {
|
||||||
case -2:
|
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
case -1:
|
} else if num == -1 {
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1305,10 +1288,6 @@ 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(callbackQuery.Message.GetChat().ID, message_text)
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
|
||||||
}
|
}
|
||||||
|
@ -1545,10 +1524,6 @@ 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":
|
||||||
|
@ -1559,10 +1534,6 @@ 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":
|
||||||
|
@ -1627,10 +1598,6 @@ 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)
|
||||||
|
@ -1793,10 +1760,6 @@ 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,
|
||||||
|
@ -2045,11 +2008,10 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := ""
|
msg := ""
|
||||||
switch status {
|
if status == LoginSuccess {
|
||||||
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)
|
||||||
case LoginFail:
|
} else if status == 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)
|
||||||
|
@ -2209,22 +2171,6 @@ 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
|
||||||
|
|
|
@ -561,25 +561,24 @@
|
||||||
"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" = "🟢 أونلاين"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
"unknown" = "❗ أمر مش معروف."
|
"unknown" = "❗ أمر مش معروف."
|
||||||
|
|
|
@ -574,7 +574,6 @@
|
||||||
"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"
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
"invalidFormData" = "El formato de los datos de entrada es inválido."
|
"invalidFormData" = "El formato de los datos de entrada es inválido."
|
||||||
"emptyUsername" = "Por favor ingresa el nombre de usuario."
|
"emptyUsername" = "Por favor ingresa el nombre de usuario."
|
||||||
"emptyPassword" = "Por favor ingresa la contraseña."
|
"emptyPassword" = "Por favor ingresa la contraseña."
|
||||||
"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
|
"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
|
||||||
"successLogin" = "Has iniciado sesión en tu cuenta correctamente."
|
"successLogin" = "Has iniciado sesión en tu cuenta correctamente."
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
|
@ -535,9 +535,9 @@
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Credenciales de administrador"
|
"admin" = "Credenciales de administrador"
|
||||||
"twoFactor" = "Autenticación de dos factores"
|
"twoFactor" = "Autenticación de dos factores"
|
||||||
"twoFactorEnable" = "Habilitar 2FA"
|
"twoFactorEnable" = "Habilitar 2FA"
|
||||||
"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
|
"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
|
||||||
"twoFactorModalSetTitle" = "Activar autenticación de dos factores"
|
"twoFactorModalSetTitle" = "Activar autenticación de dos factores"
|
||||||
"twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores"
|
"twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores"
|
||||||
"twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:"
|
"twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:"
|
||||||
|
@ -561,24 +561,23 @@
|
||||||
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
|
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Teclado cerrado!"
|
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"
|
||||||
"noResult" = "❗ ¡No hay resultados!"
|
"noResult" = "❗ ¡Sin resultados!"
|
||||||
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando de nuevo!"
|
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!"
|
||||||
"wentWrong" = "❌ ¡Algo salió mal!"
|
"wentWrong" = "❌ ¡Algo salió mal!"
|
||||||
"noIpRecord" = "❗ ¡No hay registro de IP!"
|
"noIpRecord" = "❗ ¡Sin Registro de IP!"
|
||||||
"noInbounds" = "❗ ¡No se encontraron entradas!"
|
"noInbounds" = "❗ ¡No se encontraron entradas!"
|
||||||
"unlimited" = "♾ Ilimitado (Restablecer)"
|
"unlimited" = "♾ Ilimitado"
|
||||||
"add" = "Añadir"
|
"add" = "Agregar"
|
||||||
"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" = "🔴 Desconectado"
|
"offline" = "🔴 Sin conexión"
|
||||||
"online" = "🟢 En línea"
|
"online" = "🟢 En línea"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
|
|
|
@ -561,23 +561,22 @@
|
||||||
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
|
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ صفحه کلید بسته شد!"
|
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
|
||||||
"noResult" = "❗ نتیجه ای یافت نشد!"
|
"noResult" = "❗ نتیجهای یافت نشد!"
|
||||||
"noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!"
|
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
|
||||||
"wentWrong" = "❌ مشکلی پیش آمد!"
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
"noIpRecord" = "❗ رکورد آی پی وجود ندارد!"
|
"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" = "🟢 آنلاین"
|
||||||
|
|
||||||
|
|
|
@ -561,22 +561,21 @@
|
||||||
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
|
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Keyboard ditutup!"
|
"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
|
||||||
"noResult" = "❗ Tidak ada hasil!"
|
"noResult" = "❗ Tidak ada hasil!"
|
||||||
"noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!"
|
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
|
||||||
"wentWrong" = "❌ Terjadi kesalahan!"
|
"wentWrong" = "❌ Ada yang salah!"
|
||||||
"noIpRecord" = "❗ Tidak ada Catatan IP!"
|
"noIpRecord" = "❗ Tidak ada Catatan IP!"
|
||||||
"noInbounds" = "❗ Tidak ada inbound yang ditemukan!"
|
"noInbounds" = "❗ Tidak ada masuk ditemukan!"
|
||||||
"unlimited" = "♾ Tidak terbatas (Reset)"
|
"unlimited" = "♾ Tak terbatas"
|
||||||
"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" = "Inbound"
|
"inbounds" = "Masuk"
|
||||||
"clients" = "Klien"
|
"clients" = "Klien"
|
||||||
"offline" = "🔴 Offline"
|
"offline" = "🔴 Offline"
|
||||||
"online" = "🟢 Online"
|
"online" = "🟢 Online"
|
||||||
|
|
|
@ -561,22 +561,21 @@
|
||||||
"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" = "🟢 オンライン"
|
||||||
|
|
|
@ -561,22 +561,21 @@
|
||||||
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
|
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Teclado fechado!"
|
"keyboardClosed" = "❌ Teclado personalizado 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" = "❗ Nenhum inbound encontrado!"
|
"noInbounds" = "❗ Nenhuma entrada encontrada!"
|
||||||
"unlimited" = "♾ Ilimitado (Reset)"
|
"unlimited" = "♾ Ilimitado (Reiniciar)"
|
||||||
"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" = "Inbounds"
|
"inbounds" = "Entradas"
|
||||||
"clients" = "Clientes"
|
"clients" = "Clientes"
|
||||||
"offline" = "🔴 Offline"
|
"offline" = "🔴 Offline"
|
||||||
"online" = "🟢 Online"
|
"online" = "🟢 Online"
|
||||||
|
|
|
@ -574,7 +574,6 @@
|
||||||
"day" = "День"
|
"day" = "День"
|
||||||
"days" = "Дней"
|
"days" = "Дней"
|
||||||
"hours" = "Часов"
|
"hours" = "Часов"
|
||||||
"minutes" = "Минуты"
|
|
||||||
"unknown" = "Неизвестно"
|
"unknown" = "Неизвестно"
|
||||||
"inbounds" = "Инбаунды"
|
"inbounds" = "Инбаунды"
|
||||||
"clients" = "Клиенты"
|
"clients" = "Клиенты"
|
||||||
|
|
|
@ -561,23 +561,22 @@
|
||||||
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
|
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Klavye kapatıldı!"
|
"keyboardClosed" = "❌ Özel klavye kapalı!"
|
||||||
"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 bağlantı bulunamadı!"
|
"noInbounds" = "❗ Gelen 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"
|
||||||
"minutes" = "Dakika"
|
"unknown" = "Bilinmiyor"
|
||||||
"unknown" = "Bilinmeyen"
|
|
||||||
"inbounds" = "Gelenler"
|
"inbounds" = "Gelenler"
|
||||||
"clients" = "İstemciler"
|
"clients" = "Müşteriler"
|
||||||
"offline" = "🔴 Çevrimdışı"
|
"offline" = "🔴 Çevrimdışı"
|
||||||
"online" = "🟢 Çevrimiçi"
|
"online" = "🟢 Çevrimiçi"
|
||||||
|
|
||||||
|
|
|
@ -561,20 +561,19 @@
|
||||||
"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" = "Клієнти"
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
|
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
|
||||||
"emptyUsername" = "Vui lòng nhập tên người dùng."
|
"emptyUsername" = "Vui lòng nhập tên người dùng."
|
||||||
"emptyPassword" = "Vui lòng nhập mật khẩu."
|
"emptyPassword" = "Vui lòng nhập mật khẩu."
|
||||||
"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
|
"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
|
||||||
"successLogin" = "Bạn đã đăng nhập vào tài khoản thành công."
|
"successLogin" = "Bạn đã đăng nhập vào tài khoản thành công."
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
|
@ -535,9 +535,9 @@
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Thông tin đăng nhập quản trị viên"
|
"admin" = "Thông tin đăng nhập quản trị viên"
|
||||||
"twoFactor" = "Xác thực hai yếu tố"
|
"twoFactor" = "Xác thực hai yếu tố"
|
||||||
"twoFactorEnable" = "Bật 2FA"
|
"twoFactorEnable" = "Bật 2FA"
|
||||||
"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
|
"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
|
||||||
"twoFactorModalSetTitle" = "Bật xác thực hai yếu tố"
|
"twoFactorModalSetTitle" = "Bật xác thực hai yếu tố"
|
||||||
"twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố"
|
"twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố"
|
||||||
"twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:"
|
"twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:"
|
||||||
|
@ -561,23 +561,22 @@
|
||||||
"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 đã đóng!"
|
"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đó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ại lệnh!"
|
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!"
|
||||||
"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 (Đặt lại)"
|
"unlimited" = "♾ Không giới hạn"
|
||||||
"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ờ"
|
||||||
"minutes" = "Phút"
|
"unknown" = "Không rõ"
|
||||||
"unknown" = "Không xác định"
|
"inbounds" = "Vào"
|
||||||
"inbounds" = "Inbound"
|
"clients" = "Các người dùng"
|
||||||
"clients" = "Client"
|
|
||||||
"offline" = "🔴 Ngoại tuyến"
|
"offline" = "🔴 Ngoại tuyến"
|
||||||
"online" = "🟢 Trực tuyến"
|
"online" = "🟢 Trực tuyến"
|
||||||
|
|
||||||
|
|
|
@ -563,20 +563,19 @@
|
||||||
[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" = "🟢 在线"
|
||||||
|
|
|
@ -563,23 +563,22 @@
|
||||||
[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
249
x-ui.sh
|
@ -398,6 +398,37 @@ 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"
|
||||||
|
@ -974,7 +1005,7 @@ ssl_cert_issue() {
|
||||||
# install socat second
|
# install socat second
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
apt-get update && apt-get install socat -y
|
apt update && apt 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
|
||||||
|
@ -1299,7 +1330,81 @@ 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]))$"
|
||||||
|
@ -1409,22 +1514,14 @@ 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-get install python3-pip -y
|
apt update && apt install python3-pip -y
|
||||||
python3 -m pip install pyasynchat --break-system-packages
|
python3 -m pip install pyasynchat --break-system-packages
|
||||||
fi
|
fi
|
||||||
apt-get install fail2ban -y
|
apt update && apt install fail2ban -y
|
||||||
;;
|
;;
|
||||||
debian)
|
debian | armbian)
|
||||||
apt-get update
|
apt update && apt install fail2ban -y
|
||||||
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
|
||||||
|
@ -1535,129 +1632,11 @@ remove_iplimit() {
|
||||||
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}"
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
SSH_port_forwarding() {
|
||||||
local URL_lists=(
|
local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
|
||||||
"https://api4.ipify.org"
|
if [ -z "$server_ip" ]; then
|
||||||
"https://ipv4.icanhazip.com"
|
server_ip=$(curl -s --max-time 3 https://4.ident.me)
|
||||||
"https://v4.api.ipinfo.io/ip"
|
fi
|
||||||
"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}')
|
||||||
|
|
Loading…
Reference in a new issue