Compare commits

..

15 Commits

Author SHA1 Message Date
mhsanaei
96e43fa195 v2.3.8 2024-07-08 23:48:01 +02:00
mhsanaei
f1500a5d31 improved - message logs 2024-07-08 23:47:49 +02:00
mhsanaei
c9a218d060 axios v1.7.2 2024-07-08 21:03:20 +02:00
mhsanaei
6d18a15c4e Expand arch support in downloadXRay 2024-07-08 18:24:07 +02:00
dependabot[bot]
c0f86d2f38 Bump github.com/mymmrac/telego from 0.30.2 to 0.31.0 (#2432)
Bumps [github.com/mymmrac/telego](https://github.com/mymmrac/telego) from 0.30.2 to 0.31.0.
- [Release notes](https://github.com/mymmrac/telego/releases)
- [Commits](https://github.com/mymmrac/telego/compare/v0.30.2...v0.31.0)

---
updated-dependencies:
- dependency-name: github.com/mymmrac/telego
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 15:48:14 +02:00
mhsanaei
5227fefaeb update dependencies 2024-07-07 12:10:47 +02:00
mhsanaei
7a51d2f2cc Typo fixed 2024-07-07 12:10:24 +02:00
mhsanaei
02ae61fe6b change session name 2024-07-05 14:33:04 +02:00
mhsanaei
24b9e5bfa3 some changes 2024-07-04 15:04:04 +02:00
mhsanaei
9ff7f14b6e unnecessary log 2024-07-04 00:28:37 +02:00
mhsanaei
c3b42b8ea4 typo 2024-07-04 00:17:44 +02:00
mhsanaei
5afb8d85fc Optimize XrayAPI functionality and structure 2024-07-04 00:17:28 +02:00
dependabot[bot]
767ee4ec2b Bump google.golang.org/grpc from 1.64.0 to 1.65.0 (#2431)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 23:55:09 +02:00
mhsanaei
21b64beb96 tgbot - login notify (show password for failed login) 2024-07-03 21:53:45 +02:00
mhsanaei
b84e3ef338 improve bash menu 2024-07-02 00:34:25 +02:00
37 changed files with 365 additions and 290 deletions

View File

@@ -1 +1 @@
2.3.7
2.3.8

14
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.3
github.com/mymmrac/telego v0.30.2
github.com/mymmrac/telego v0.31.0
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.2
@@ -17,7 +17,7 @@ require (
github.com/xtls/xray-core v1.8.16
go.uber.org/atomic v1.11.0
golang.org/x/text v0.16.0
google.golang.org/grpc v1.64.0
google.golang.org/grpc v1.65.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.10
)
@@ -30,7 +30,7 @@ require (
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/fasthttp/router v1.5.1 // indirect
github.com/fasthttp/router v1.5.2 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
@@ -66,7 +66,7 @@ require (
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.4.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
@@ -83,11 +83,11 @@ require (
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect

28
go.sum
View File

@@ -37,8 +37,8 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8=
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs=
github.com/fasthttp/router v1.5.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
@@ -157,8 +157,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.30.2 h1:CqGlqX0hkgz9qMwdA3q+aZtSonqMOKQQrFLn/oUOTaw=
github.com/mymmrac/telego v0.30.2/go.mod h1:U6cWJBgRCzGt+s0q77x/Dh2+i+u56VTAAYKlMenhuFc=
github.com/mymmrac/telego v0.31.0 h1:vsN+JCNkh7Z9vfL/2/AHZ2xBsRk2GCMj3zydjCxkgIc=
github.com/mymmrac/telego v0.31.0/go.mod h1:MuqgVf2xXnIOWZs0prvsp3f4Yss80kCSjVEj4CRl7Ig=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
@@ -200,8 +200,8 @@ github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -294,8 +294,8 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@@ -312,8 +312,8 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -339,8 +339,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
@@ -377,8 +377,8 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -130,7 +130,7 @@ gen_random_string() {
# This function will be called when user installed x-ui out of security
config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
read -p "Do you want to continue with the modification [y/n]?": config_confirm
read -p "Would you like to customize the panel settings? (If not, random settings will be applied) [y/n]: " config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up your username: " config_account
echo -e "${yellow}Your username will be: ${config_account}${plain}"
@@ -160,9 +160,9 @@ config_after_install() {
echo -e "${green}Password: ${passwordTemp}${plain}"
echo -e "${green}WebBasePath: ${webBasePathTemp}${plain}"
echo -e "###############################################"
echo -e "${red}If you forgot your login info, you can type x-ui and then type 8 to check after installation${plain}"
echo -e "${yellow}If you forgot your login info, you can type "x-ui settings" to check after installation${plain}"
else
echo -e "${red}This is your upgrade, will keep old settings. If you forgot your login info, you can type x-ui and then type 8 to check${plain}"
echo -e "${yellow}This is your upgrade, will keep old settings. If you forgot your login info, you can type "x-ui settings" to check${plain}"
fi
fi
/usr/local/x-ui/x-ui migrate

98
main.go
View File

@@ -21,7 +21,7 @@ import (
)
func runWebServer() {
log.Printf("%v %v", config.GetName(), config.GetVersion())
log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
switch config.GetLogLevel() {
case config.Debug:
@@ -35,31 +35,29 @@ func runWebServer() {
case config.Error:
logger.InitLogger(logging.ERROR)
default:
log.Fatal("unknown log level:", config.GetLogLevel())
log.Fatalf("Unknown log level: %v", config.GetLogLevel())
}
err := database.InitDB(config.GetDBPath())
if err != nil {
log.Fatal(err)
log.Fatalf("Error initializing database: %v", err)
}
var server *web.Server
server = web.NewServer()
global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error starting web server: %v", err)
return
}
var subServer *sub.Server
subServer = sub.NewServer()
global.SetSubServer(subServer)
err = subServer.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error starting sub server: %v", err)
return
}
@@ -71,34 +69,39 @@ func runWebServer() {
switch sig {
case syscall.SIGHUP:
logger.Info("Received SIGHUP signal. Restarting servers...")
err := server.Stop()
if err != nil {
logger.Warning("stop server err:", err)
logger.Warning("Error stopping web server:", err)
}
err = subServer.Stop()
if err != nil {
logger.Warning("stop server err:", err)
logger.Warning("Error stopping sub server:", err)
}
server = web.NewServer()
global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error restarting web server: %v", err)
return
}
log.Println("Web server restarted successfully.")
subServer = sub.NewServer()
global.SetSubServer(subServer)
err = subServer.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error restarting sub server: %v", err)
return
}
log.Println("Sub server restarted successfully.")
default:
server.Stop()
subServer.Stop()
log.Println("Shutting down servers.")
return
}
}
@@ -107,16 +110,16 @@ func runWebServer() {
func resetSetting() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Failed to initialize database:", err)
return
}
settingService := service.SettingService{}
err = settingService.ResetSettings()
if err != nil {
fmt.Println("reset setting failed:", err)
fmt.Println("Failed to reset settings:", err)
} else {
fmt.Println("reset setting success")
fmt.Println("Settings successfully reset.")
}
}
@@ -159,19 +162,19 @@ func showSetting(show bool) {
func updateTgbotEnableSts(status bool) {
settingService := service.SettingService{}
currentTgSts, err := settingService.GetTgbotenabled()
currentTgSts, err := settingService.GetTgbotEnabled()
if err != nil {
fmt.Println(err)
return
}
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
if currentTgSts != status {
err := settingService.SetTgbotenabled(status)
err := settingService.SetTgbotEnabled(status)
if err != nil {
fmt.Println(err)
return
} else {
logger.Infof("SetTgbotenabled[%v] success", status)
logger.Infof("SetTgbotEnabled[%v] success", status)
}
}
}
@@ -179,7 +182,7 @@ func updateTgbotEnableSts(status bool) {
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Error initializing database:", err)
return
}
@@ -188,68 +191,65 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
if tgBotToken != "" {
err := settingService.SetTgBotToken(tgBotToken)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot token: %v\n", err)
return
} else {
logger.Info("updateTgbotSetting tgBotToken success")
}
logger.Info("Successfully updated Telegram bot token.")
}
if tgBotRuntime != "" {
err := settingService.SetTgbotRuntime(tgBotRuntime)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
return
} else {
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
}
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
}
if tgBotChatid != "" {
err := settingService.SetTgBotChatId(tgBotChatid)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
return
} else {
logger.Info("updateTgbotSetting tgBotChatid success")
}
logger.Info("Successfully updated Telegram bot chat ID.")
}
}
func updateSetting(port int, username string, password string, webBasePath string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Database initialization failed:", err)
return
}
settingService := service.SettingService{}
userService := service.UserService{}
if port > 0 {
err := settingService.SetPort(port)
if err != nil {
fmt.Println("set port failed:", err)
fmt.Println("Failed to set port:", err)
} else {
fmt.Printf("set port %v success", port)
fmt.Printf("Port set successfully: %v\n", port)
}
}
if username != "" || password != "" {
userService := service.UserService{}
err := userService.UpdateFirstUser(username, password)
if err != nil {
fmt.Println("set username and password failed:", err)
fmt.Println("Failed to update username and password:", err)
} else {
fmt.Println("set username and password success")
fmt.Println("Username and password updated successfully")
}
}
if webBasePath != "" {
err := settingService.SetBasePath(webBasePath)
if err != nil {
fmt.Println("set base URI path failed:", err)
fmt.Println("Failed to set base URI path:", err)
} else {
fmt.Println("set base URI path success")
fmt.Println("Base URI path set successfully")
}
}
}
@@ -348,19 +348,19 @@ func main() {
var reset bool
var show bool
var remove_secret bool
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
settingCmd.BoolVar(&show, "show", false, "show current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "remove secret")
settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "set web base path")
settingCmd.StringVar(&webCertFile, "webCert", "", "set web public key path")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "set web private key path")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Display current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
oldUsage := flag.Usage
flag.Usage = func() {

View File

@@ -163,13 +163,13 @@ func (s *Server) Start() (err error) {
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("sub server run https on", listener.Addr())
logger.Info("Sub server running HTTPS on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("sub server run http on", listener.Addr())
logger.Error("Error loading certificates:", err)
logger.Info("Sub server running HTTP on", listener.Addr())
}
} else {
logger.Info("sub server run http on", listener.Addr())
logger.Info("Sub server running HTTP on", listener.Addr())
}
s.listener = listener

File diff suppressed because one or more lines are too long

View File

@@ -232,14 +232,12 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
}
email := c.Param("email")
needRestart := true
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}
jsonMsg(c, "traffic reseted", nil)
jsonMsg(c, "Traffic has been reset", nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
@@ -253,7 +251,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
} else {
a.xrayService.SetToNeedRestart()
}
jsonMsg(c, "All traffics reseted", nil)
jsonMsg(c, "all traffic has been reset", nil)
}
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
@@ -270,7 +268,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
} else {
a.xrayService.SetToNeedRestart()
}
jsonMsg(c, "All traffics of client reseted", nil)
jsonMsg(c, "All traffic from the client has been reset.", nil)
}
func (a *InboundController) importInbound(c *gin.Context) {
@@ -313,9 +311,9 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
jsonMsg(c, "Something went wrong!", err)
return
}
jsonMsg(c, "All delpeted clients are deleted", nil)
jsonMsg(c, "All depleted clients are deleted", nil)
}
func (a *InboundController) onlines(c *gin.Context) {
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
jsonObj(c, a.inboundService.GetOnlineClients(), nil)
}

View File

@@ -65,36 +65,36 @@ func (a *IndexController) login(c *gin.Context) {
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
timeStr := time.Now().Format("2006-01-02 15:04:05")
if user == nil {
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
logger.Warningf("wrong username or password or secret: \"%s\" \"%s\" \"%s\"", form.Username, form.Password, form.LoginSecret)
a.tgbot.UserLoginNotify(form.Username, form.Password, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return
} else {
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
logger.Infof("%s logged in successfully, Ip Address: %s\n", form.Username, getRemoteIp(c))
a.tgbot.UserLoginNotify(form.Username, ``, getRemoteIp(c), timeStr, 1)
}
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
if err != nil {
logger.Warningf("Unable to get session's max age from DB")
logger.Warning("Unable to get session's max age from DB")
}
if sessionMaxAge > 0 {
err = session.SetMaxAge(c, sessionMaxAge*60)
if err != nil {
logger.Warningf("Unable to set session's max age")
logger.Warning("Unable to set session's max age")
}
}
err = session.SetLoginUser(c, user)
logger.Info("user", user.Id, "login success")
logger.Infof("%s logged in successfully", user.Username)
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
}
func (a *IndexController) logout(c *gin.Context) {
user := session.GetLoginUser(c)
if user != nil {
logger.Info("user", user.Id, "logout")
logger.Infof("%s logged out successfully", user.Username)
}
session.ClearSession(c)
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))

View File

@@ -105,7 +105,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
jsonMsg(c, "", err)
return
}
jsonMsg(c, "Xray stoped", err)
jsonMsg(c, "Xray stopped", err)
}
func (a *ServerController) restartXrayService(c *gin.Context) {

View File

@@ -81,7 +81,7 @@ func (a *XraySettingController) warp(c *gin.Context) {
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license)
resp, err = a.XraySettingService.SetWarpLicense(license)
}
jsonObj(c, resp, err)

View File

@@ -79,8 +79,8 @@
qrModal: qrModal,
},
methods: {
copyToClipboard(elmentId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
copyToClipboard(elementId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.qrModal.clipboard.on('success', () => {
@@ -88,9 +88,9 @@
this.qrModal.clipboard.destroy();
});
},
setQrCode(elmentId, content) {
setQrCode(elementId, content) {
new QRious({
element: document.querySelector('#' + elmentId),
element: document.querySelector('#' + elementId),
size: 400,
value: content,
background: 'white',

View File

@@ -494,8 +494,8 @@
},
},
methods: {
copyToClipboard(elmentId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
copyToClipboard(elementId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.infoModal.clipboard.on('success', () => {

View File

@@ -503,6 +503,7 @@
this.loading(false);
if (msg.success) {
this.user = {};
window.location.replace(basePath + "logout");
}
},
async restartPanel() {

View File

@@ -252,46 +252,55 @@ func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string)
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
jsonIps, err := json.Marshal(ips)
j.checkError(err)
if err != nil {
logger.Error("failed to marshal IPs to JSON:", err)
return false
}
inboundClientIps.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps)
// check inbound limitation
// Fetch inbound settings by client email
inbound, err := j.getInboundByEmail(clientEmail)
j.checkError(err)
if inbound.Settings == "" {
logger.Debug("wrong data ", inbound)
if err != nil {
logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err)
return false
}
if inbound.Settings == "" {
logger.Debug("wrong data:", inbound)
return false
}
// Unmarshal settings to get client limits
settings := map[string][]model.Client{}
json.Unmarshal([]byte(inbound.Settings), &settings)
clients := settings["clients"]
shouldCleanLog := false
j.disAllowedIps = []string{}
// create iplimit log file channel
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
// Open log file for IP limits
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
logger.Errorf("failed to create or open ip limit log file: %s", err)
logger.Errorf("failed to open IP limit log file: %s", err)
return false
}
defer logIpFile.Close()
log.SetOutput(logIpFile)
log.SetFlags(log.LstdFlags)
// Check client IP limits
for _, client := range clients {
if client.Email == clientEmail {
limitIp := client.LimitIP
if limitIp != 0 {
if limitIp > 0 && inbound.Enable {
shouldCleanLog = true
if limitIp < len(ips) && inbound.Enable {
if limitIp < len(ips) {
j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
for i := limitIp; i < len(ips); i++ {
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
logger.Debugf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
}
}
}
@@ -301,12 +310,15 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
sort.Strings(j.disAllowedIps)
if len(j.disAllowedIps) > 0 {
logger.Debug("disAllowedIps ", j.disAllowedIps)
logger.Debug("disAllowedIps:", j.disAllowedIps)
}
db := database.GetDB()
err = db.Save(inboundClientIps).Error
j.checkError(err)
if err != nil {
logger.Error("failed to save inboundClientIps:", err)
return false
}
return shouldCleanLog
}

View File

@@ -19,10 +19,8 @@ func (j *XrayTrafficJob) Run() {
if !j.xrayService.IsXrayRunning() {
return
}
traffics, clientTraffics, err := j.xrayService.GetXrayTraffic()
if err != nil {
logger.Warning("get xray traffic failed:", err)
return
}
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)

View File

@@ -595,7 +595,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
inerfaceClients := settings["clients"].([]interface{})
interfaceClients := settings["clients"].([]interface{})
oldInbound, err := s.GetInbound(data.Id)
if err != nil {
@@ -650,7 +650,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
settingsClients := oldSettings["clients"].([]interface{})
settingsClients[clientIndex] = inerfaceClients[0]
settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
@@ -1134,7 +1134,6 @@ func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
}
func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error {
logger.Warning(email)
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
}
@@ -1143,7 +1142,7 @@ func (s *InboundService) GetClientInboundByTrafficID(trafficId int) (traffic *xr
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warningf("Error retrieving ClientTraffic with trafficId %d: %v", trafficId, err)
return nil, nil, err
}
if len(traffics) > 0 {
@@ -1158,7 +1157,7 @@ func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.Cl
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, nil, err
}
if len(traffics) > 0 {
@@ -1699,15 +1698,20 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
db := database.GetDB()
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
// Retrieve inbounds where settings contain the given tgId
err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound {
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
return nil, err
}
var emails []string
for _, inbound := range inbounds {
clients, err := s.GetClients(inbound)
if err != nil {
logger.Error("Unable to get clients from inbound")
logger.Errorf("Error retrieving clients for inbound %d: %v", inbound.Id, err)
continue
}
for _, client := range clients {
if client.TgID == tgId {
@@ -1715,15 +1719,19 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
}
}
}
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
logger.Warning(err)
return nil, err
logger.Warning("No ClientTraffic records found for emails:", emails)
return nil, nil
}
logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", emails, err)
return nil, err
}
return traffics, err
return traffics, nil
}
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
@@ -1732,7 +1740,7 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, err
}
if len(traffics) > 0 {
@@ -1747,38 +1755,51 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
inbound := &model.Inbound{}
traffic = &xray.ClientTraffic{}
err = db.Model(model.Inbound{}).Where("settings like ?", "%\""+query+"\"%").First(inbound).Error
// Search for inbound settings that contain the query
err = db.Model(model.Inbound{}).Where("settings LIKE ?", "%\""+query+"\"%").First(inbound).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
logger.Warning(err)
logger.Warningf("Inbound settings containing query %s not found: %v", query, err)
return nil, err
}
logger.Errorf("Error searching for inbound settings with query %s: %v", query, err)
return nil, err
}
traffic.InboundId = inbound.Id
// get settings clients
// Unmarshal settings to get clients
settings := map[string][]model.Client{}
json.Unmarshal([]byte(inbound.Settings), &settings)
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
logger.Errorf("Error unmarshalling inbound settings for inbound ID %d: %v", inbound.Id, err)
return nil, err
}
clients := settings["clients"]
for _, client := range clients {
if client.ID == query && client.Email != "" {
traffic.Email = client.Email
break
}
if client.Password == query && client.Email != "" {
if (client.ID == query || client.Password == query) && client.Email != "" {
traffic.Email = client.Email
break
}
}
if traffic.Email == "" {
return nil, err
logger.Warningf("No client found with query %s in inbound ID %d", query, inbound.Id)
return nil, gorm.ErrRecordNotFound
}
// Retrieve ClientTraffic based on the found email
err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
if err != nil {
logger.Warning(err)
if err == gorm.ErrRecordNotFound {
logger.Warningf("ClientTraffic for email %s not found: %v", traffic.Email, err)
return nil, err
}
logger.Errorf("Error retrieving ClientTraffic for email %s: %v", traffic.Email, err)
return nil, err
}
return traffic, err
return traffic, nil
}
func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
@@ -1948,6 +1969,6 @@ func (s *InboundService) MigrateDB() {
s.MigrationRemoveOrphanedTraffics()
}
func (s *InboundService) GetOnlineClinets() []string {
func (s *InboundService) GetOnlineClients() []string {
return p.GetOnlineClients()
}

View File

@@ -70,7 +70,7 @@ func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, erro
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warning("Error retrieving OutboundTraffics: ", err)
return nil, err
}

View File

@@ -19,7 +19,7 @@ func (s *PanelService) RestartPanel(delay time.Duration) error {
time.Sleep(delay)
err := p.Signal(syscall.SIGHUP)
if err != nil {
logger.Error("send signal SIGHUP failed:", err)
logger.Error("failed to send SIGHUP signal:", err)
}
}()
return nil

View File

@@ -312,6 +312,16 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
arch = "64"
case "arm64":
arch = "arm64-v8a"
case "armv7":
arch = "arm32-v7a"
case "armv6":
arch = "arm32-v6"
case "armv5":
arch = "arm32-v5"
case "386":
arch = "32"
case "s390x":
arch = "s390x"
}
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)

View File

@@ -269,11 +269,11 @@ func (s *SettingService) SetTgBotChatId(chatIds string) error {
return s.setString("tgBotChatId", chatIds)
}
func (s *SettingService) GetTgbotenabled() (bool, error) {
func (s *SettingService) GetTgbotEnabled() (bool, error) {
return s.getBool("tgBotEnable")
}
func (s *SettingService) SetTgbotenabled(value bool) error {
func (s *SettingService) SetTgbotEnabled(value bool) error {
return s.setBool("tgBotEnable", value)
}
@@ -524,7 +524,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },

View File

@@ -64,52 +64,59 @@ func (t *Tgbot) GetHashStorage() *global.HashStorage {
}
func (t *Tgbot) Start(i18nFS embed.FS) error {
// Initialize localizer
err := locale.InitLocalizer(i18nFS, &t.settingService)
if err != nil {
return err
}
// init hash storage => store callback queries
// Initialize hash storage to store callback queries
hashStorage = global.NewHashStorage(20 * time.Minute)
t.SetHostname()
tgBottoken, err := t.settingService.GetTgBotToken()
if err != nil || tgBottoken == "" {
logger.Warning("Get TgBotToken failed:", err)
// Get Telegram bot token
tgBotToken, err := t.settingService.GetTgBotToken()
if err != nil || tgBotToken == "" {
logger.Warning("Failed to get Telegram bot token:", err)
return err
}
tgBotid, err := t.settingService.GetTgBotChatId()
// Get Telegram bot chat ID(s)
tgBotID, err := t.settingService.GetTgBotChatId()
if err != nil {
logger.Warning("Get GetTgBotChatId failed:", err)
logger.Warning("Failed to get Telegram bot chat ID:", err)
return err
}
if tgBotid != "" {
for _, adminId := range strings.Split(tgBotid, ",") {
id, err := strconv.Atoi(adminId)
// Parse admin IDs from comma-separated string
if tgBotID != "" {
for _, adminID := range strings.Split(tgBotID, ",") {
id, err := strconv.Atoi(adminID)
if err != nil {
logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
return err
}
adminIds = append(adminIds, int64(id))
}
}
// Get Telegram bot proxy URL
tgBotProxy, err := t.settingService.GetTgBotProxy()
if err != nil {
logger.Warning("Failed to get ProxyUrl:", err)
logger.Warning("Failed to get Telegram bot proxy URL:", err)
}
bot, err = t.NewBot(tgBottoken, tgBotProxy)
// Create new Telegram bot instance
bot, err = t.NewBot(tgBotToken, tgBotProxy)
if err != nil {
fmt.Println("Get tgbot's api error:", err)
logger.Error("Failed to initialize Telegram bot API:", err)
return err
}
// listen for TG bot income messages
// Start receiving Telegram bot messages
if !isRunning {
logger.Info("Starting Telegram receiver ...")
logger.Info("Telegram bot receiver started")
go t.OnReceive()
isRunning = true
}
@@ -201,7 +208,7 @@ func (t *Tgbot) OnReceive() {
}, th.AnyCommand())
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) {
t.asnwerCallback(&query, checkAdmin(query.From.ID))
t.answerCallback(&query, checkAdmin(query.From.ID))
}, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
@@ -286,7 +293,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
}
}
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
chatId := callbackQuery.Message.GetChat().ID
if isAdmin {
@@ -964,7 +971,7 @@ func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
return info
}
// Send server usage without an inline keyborad
// Send server usage without an inline keyboard
func (t *Tgbot) sendServerUsage() string {
info := t.prepareServerUsageInfo()
return info
@@ -1019,7 +1026,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
return info
}
func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) {
func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) {
if !t.IsRunning() {
return
}
@@ -1037,11 +1044,12 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
msg := ""
if status == LoginSuccess {
msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
} else if status == LoginFail {
msg += t.I18nBot("tgbot.messages.loginFailed")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
}
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.username", "Username=="+username)
msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip)
msg += t.I18nBot("tgbot.messages.time", "Time=="+time)
@@ -1051,14 +1059,14 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
func (t *Tgbot) getInboundUsages() string {
info := ""
// get traffic
inbouds, err := t.inboundService.GetAllInbounds()
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
logger.Warning("GetAllInbounds run failed:", err)
info += t.I18nBot("tgbot.answers.getInboundsFailed")
} else {
// NOTE:If there no any sessions here,need to notify here
// TODO:Sub-node push, automatic conversion format
for _, inbound := range inbouds {
for _, inbound := range inbounds {
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
@@ -1331,20 +1339,20 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
}
func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbouds, err := t.inboundService.SearchInbounds(remark)
inbounds, err := t.inboundService.SearchInbounds(remark)
if err != nil {
logger.Warning(err)
msg := t.I18nBot("tgbot.wentWrong")
t.SendMsgToTgbot(chatId, msg)
return
}
if len(inbouds) == 0 {
if len(inbounds) == 0 {
msg := t.I18nBot("tgbot.noInbounds")
t.SendMsgToTgbot(chatId, msg)
return
}
for _, inbound := range inbouds {
for _, inbound := range inbounds {
info := ""
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))

View File

@@ -97,7 +97,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
if !clientTraffic.Enable {
clients = RemoveIndex(clients, index-indexDecrease)
indexDecrease++
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
logger.Infof("Remove Inbound User %s due to expiration or traffic limit", c["email"])
}
}
}
@@ -165,11 +165,20 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
if !s.IsXrayRunning() {
return nil, nil, errors.New("xray is not running")
err := errors.New("xray is not running")
logger.Debug("Attempted to fetch Xray traffic, but Xray is not running:", err)
return nil, nil, err
}
s.xrayAPI.Init(p.GetAPIPort())
apiPort := p.GetAPIPort()
s.xrayAPI.Init(apiPort)
defer s.xrayAPI.Close()
return s.xrayAPI.GetTraffic(true)
traffic, clientTraffic, err := s.xrayAPI.GetTraffic(true)
if err != nil {
logger.Debug("Failed to fetch Xray traffic:", err)
return nil, nil, err
}
return traffic, clientTraffic, nil
}
func (s *XrayService) RestartXray(isForce bool) error {
@@ -202,7 +211,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
func (s *XrayService) StopXray() error {
lock.Lock()
defer lock.Unlock()
logger.Debug("stop xray")
logger.Debug("Attempting to stop Xray...")
if s.IsXrayRunning() {
return p.Stop()
}

View File

@@ -128,7 +128,7 @@ func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string
return result, nil
}
func (s *XraySettingService) SetWarpLicence(license string) (string, error) {
func (s *XraySettingService) SetWarpLicense(license string) (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {

View File

@@ -9,9 +9,7 @@ import (
"github.com/gin-gonic/gin"
)
const (
loginUser = "LOGIN_USER"
)
const loginUser = "LOGIN_USER"
func init() {
gob.Register(model.User{})
@@ -34,24 +32,28 @@ func SetMaxAge(c *gin.Context, maxAge int) error {
func GetLoginUser(c *gin.Context) *model.User {
s := sessions.Default(c)
obj := s.Get(loginUser)
if obj == nil {
return nil
if obj := s.Get(loginUser); obj != nil {
if user, ok := obj.(model.User); ok {
return &user
}
}
user := obj.(model.User)
return &user
return nil
}
func IsLogin(c *gin.Context) bool {
return GetLoginUser(c) != nil
}
func ClearSession(c *gin.Context) {
func ClearSession(c *gin.Context) error {
s := sessions.Default(c)
s.Clear()
s.Options(sessions.Options{
Path: "/",
MaxAge: -1,
})
s.Save()
if err := s.Save(); err != nil {
return err
}
c.SetCookie("3x-ui", "", -1, "/", "", false, true)
return nil
}

View File

@@ -44,7 +44,7 @@
"monitor" = "Listen IP"
"certificate" = "Digital Certificate"
"fail" = " Failed"
"success" = " Successful"
"success" = " Successfully"
"getVersion" = "Get Version"
"install" = "Install"
"clients" = "Clients"
@@ -78,7 +78,7 @@
"invalidFormData" = "The Input data format is invalid."
"emptyUsername" = "Username is required"
"emptyPassword" = "Password is required"
"wrongUsernameOrPassword" = "Invalid username or password."
"wrongUsernameOrPassword" = "Invalid username or password or secret."
"successLogin" = "Login"
[pages.index]
@@ -544,7 +544,7 @@
"selectUserFailed" = "❌ Error in user selection!"
"userSaved" = "✅ Telegram User saved."
"loginSuccess" = "✅ Logged in to the panel successfully.\r\n"
"loginFailed" = "❗️ Log in to the panel failed.\r\n"
"loginFailed" = "❗Login attempt to the panel failed.\r\n"
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
@@ -562,6 +562,7 @@
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Username: {{ .Username }}\r\n"
"password" = "👤 Password: {{ .Password }}\r\n"
"time" = "⏰ Time: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"

View File

@@ -560,6 +560,7 @@
"traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Estado de Xray: {{ .State }}\r\n"
"username" = "👤 Nombre de usuario: {{ .Username }}\r\n"
"password" = "👤 Contraseña: {{ .Password }}\r\n"
"time" = "⏰ Hora: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Puerto: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " وضعیت‌ایکس‌ری: {{ .State }}\r\n"
"username" = "👤 نام‌کاربری: {{ .Username }}\r\n"
"password" = "👤 رمز عبور: {{ .Password }}\r\n"
"time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
"password" = "👤 Kata Sandi: {{ .Password }}\r\n"
"time" = "⏰ Waktu: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Состояние Xray: {{ .State }}\r\n"
"username" = "👤 Имя пользователя: {{ .Username }}\r\n"
"password" = "👤 Пароль: {{ .Password }}\r\n"
"time" = "⏰ Время: {{ .Time }}\r\n"
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
"port" = "🔌 Порт: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Статус: {{ .State }}\r\n"
"username" = "👤 Ім'я користувача: {{ .Username }}\r\n"
"password" = "👤 Пароль: {{ .Password }}\r\n"
"time" = "⏰ Час: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Порт: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Trạng thái Xray: {{ .State }}\r\n"
"username" = "👤 Tên người dùng: {{ .Username }}\r\n"
"password" = "👤 Mật khẩu: {{ .Password }}\r\n"
"time" = "⏰ Thời gian: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Cổng: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Xray 状态:{{ .State }}\r\n"
"username" = "👤 用户名:{{ .Username }}\r\n"
"password" = "👤 密码: {{ .Password }}\r\n"
"time" = "⏰ 时间:{{ .Time }}\r\n"
"inbound" = "📍 入站:{{ .Remark }}\r\n"
"port" = "🔌 端口:{{ .Port }}\r\n"

View File

@@ -180,7 +180,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
assetsBasePath := basePath + "assets/"
store := cookie.NewStore(secret)
engine.Use(sessions.Sessions("session", store))
engine.Use(sessions.Sessions("3x-ui", store))
engine.Use(func(c *gin.Context) {
c.Set("base_path", basePath)
})
@@ -268,7 +268,7 @@ func (s *Server) startTask() {
// Make a traffic condition every day, 8:30
var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotenabled()
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) {
runtime, err := s.settingService.GetTgbotRuntime()
if err != nil || runtime == "" {
@@ -344,13 +344,13 @@ func (s *Server) Start() (err error) {
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("web server run https on", listener.Addr())
logger.Info("Web server running HTTPS on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("web server run http on", listener.Addr())
logger.Error("Error loading certificates:", err)
logger.Info("Web server running HTTP on", listener.Addr())
}
} else {
logger.Info("web server run http on", listener.Addr())
logger.Info("Web server running HTTP on", listener.Addr())
}
s.listener = listener
@@ -364,7 +364,7 @@ func (s *Server) Start() (err error) {
s.startTask()
isTgbotenabled, err := s.settingService.GetTgbotenabled()
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) {
tgBot := s.tgbotService.NewTgbot()
tgBot.Start(i18nFS)

View File

@@ -262,10 +262,9 @@ reset_webbasepath() {
echo -e "${yellow}Resetting Web Base Path${plain}"
# Prompt user to set a new web base path
read -rp "Please set the new web base path [default is a random path]: " config_webBasePath
read -rp "Please set the new web base path [press 'y' for a random path]: " config_webBasePath
# If user input is empty, generate a random path
if [[ -z $config_webBasePath ]]; then
if [[ $config_webBasePath == "y" ]]; then
config_webBasePath=$(gen_random_string 10)
fi

View File

@@ -31,24 +31,27 @@ type XrayAPI struct {
isConnected bool
}
func (x *XrayAPI) Init(apiPort int) (err error) {
if apiPort == 0 {
return common.NewError("xray api port wrong:", apiPort)
func (x *XrayAPI) Init(apiPort int) error {
if apiPort <= 0 {
return fmt.Errorf("invalid Xray API port: %d", apiPort)
}
conn, err := grpc.NewClient(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
addr := fmt.Sprintf("127.0.0.1:%d", apiPort)
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return err
return fmt.Errorf("failed to connect to Xray API: %w", err)
}
x.grpcClient = conn
x.isConnected = true
hsClient := command.NewHandlerServiceClient(x.grpcClient)
ssClient := statsService.NewStatsServiceClient(x.grpcClient)
hsClient := command.NewHandlerServiceClient(conn)
ssClient := statsService.NewStatsServiceClient(conn)
x.HandlerServiceClient = &hsClient
x.StatsServiceClient = &ssClient
return
return nil
}
func (x *XrayAPI) Close() {
@@ -149,94 +152,101 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
return err
}
func (x *XrayAPI) RemoveUser(inboundTag string, email string) error {
client := *x.HandlerServiceClient
_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
Tag: inboundTag,
Operation: serial.ToTypedMessage(&command.RemoveUserOperation{
Email: email,
}),
})
return err
func (x *XrayAPI) RemoveUser(inboundTag, email string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
op := &command.RemoveUserOperation{Email: email}
req := &command.AlterInboundRequest{
Tag: inboundTag,
Operation: serial.ToTypedMessage(op),
}
_, err := (*x.HandlerServiceClient).AlterInbound(ctx, req)
if err != nil {
return fmt.Errorf("failed to remove user: %w", err)
}
return nil
}
func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
if x.grpcClient == nil {
return nil, nil, common.NewError("xray api is not initialized")
}
trafficRegex := regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
ClientTrafficRegex := regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
client := *x.StatsServiceClient
trafficRegex := regexp.MustCompile(`(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)`)
clientTrafficRegex := regexp.MustCompile(`user>>>([^>]+)>>>traffic>>>(downlink|uplink)`)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
request := &statsService.QueryStatsRequest{
Reset_: reset,
}
resp, err := client.QueryStats(ctx, request)
resp, err := (*x.StatsServiceClient).QueryStats(ctx, &statsService.QueryStatsRequest{Reset_: reset})
if err != nil {
logger.Debug("Failed to query Xray stats:", err)
return nil, nil, err
}
tagTrafficMap := map[string]*Traffic{}
emailTrafficMap := map[string]*ClientTraffic{}
clientTraffics := make([]*ClientTraffic, 0)
traffics := make([]*Traffic, 0)
tagTrafficMap := make(map[string]*Traffic)
emailTrafficMap := make(map[string]*ClientTraffic)
for _, stat := range resp.GetStat() {
matchs := trafficRegex.FindStringSubmatch(stat.Name)
if len(matchs) < 3 {
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
if len(matchs) < 3 {
continue
} else {
isUser := matchs[1] == "user"
email := matchs[2]
isDown := matchs[3] == "downlink"
if !isUser {
continue
}
traffic, ok := emailTrafficMap[email]
if !ok {
traffic = &ClientTraffic{
Email: email,
}
emailTrafficMap[email] = traffic
clientTraffics = append(clientTraffics, traffic)
}
if isDown {
traffic.Down = stat.Value
} else {
traffic.Up = stat.Value
}
}
continue
}
isInbound := matchs[1] == "inbound"
isOutbound := matchs[1] == "outbound"
tag := matchs[2]
isDown := matchs[3] == "downlink"
if tag == "api" {
continue
}
traffic, ok := tagTrafficMap[tag]
if !ok {
traffic = &Traffic{
IsInbound: isInbound,
IsOutbound: isOutbound,
Tag: tag,
}
tagTrafficMap[tag] = traffic
traffics = append(traffics, traffic)
}
if isDown {
traffic.Down = stat.Value
} else {
traffic.Up = stat.Value
if matches := trafficRegex.FindStringSubmatch(stat.Name); len(matches) == 4 {
processTraffic(matches, stat.Value, tagTrafficMap)
} else if matches := clientTrafficRegex.FindStringSubmatch(stat.Name); len(matches) == 3 {
processClientTraffic(matches, stat.Value, emailTrafficMap)
}
}
return traffics, clientTraffics, nil
return mapToSlice(tagTrafficMap), mapToSlice(emailTrafficMap), nil
}
func processTraffic(matches []string, value int64, trafficMap map[string]*Traffic) {
isInbound := matches[1] == "inbound"
tag := matches[2]
isDown := matches[3] == "downlink"
if tag == "api" {
return
}
traffic, ok := trafficMap[tag]
if !ok {
traffic = &Traffic{
IsInbound: isInbound,
IsOutbound: !isInbound,
Tag: tag,
}
trafficMap[tag] = traffic
}
if isDown {
traffic.Down = value
} else {
traffic.Up = value
}
}
func processClientTraffic(matches []string, value int64, clientTrafficMap map[string]*ClientTraffic) {
email := matches[1]
isDown := matches[2] == "downlink"
traffic, ok := clientTrafficMap[email]
if !ok {
traffic = &ClientTraffic{Email: email}
clientTrafficMap[email] = traffic
}
if isDown {
traffic.Down = value
} else {
traffic.Up = value
}
}
func mapToSlice[T any](m map[string]*T) []*T {
result := make([]*T, 0, len(m))
for _, v := range m {
result = append(result, v)
}
return result
}

View File

@@ -60,14 +60,14 @@ func GetAccessPersistentPrevLogPath() string {
func GetAccessLogPath() (string, error) {
config, err := os.ReadFile(GetConfigPath())
if err != nil {
logger.Warningf("Something went wrong: %s", err)
logger.Warningf("Failed to read configuration file: %s", err)
return "", err
}
jsonConfig := map[string]interface{}{}
err = json.Unmarshal([]byte(config), &jsonConfig)
if err != nil {
logger.Warningf("Something went wrong: %s", err)
logger.Warningf("Failed to parse JSON configuration: %s", err)
return "", err
}
@@ -206,7 +206,7 @@ func (p *process) Start() (err error) {
err = os.MkdirAll(config.GetLogFolder(), 0o770)
if err != nil {
logger.Warningf("Something went wrong: %s", err)
logger.Warningf("Failed to create log folder: %s", err)
}
configPath := GetConfigPath()
@@ -224,7 +224,7 @@ func (p *process) Start() (err error) {
go func() {
err := cmd.Run()
if err != nil {
logger.Error("Failure in running xray-core: ", err)
logger.Error("Failure in running xray-core:", err)
p.exitErr = err
}
}()