From 8153e0ac05d8ad54c3d98c47bb7c4673951b6197 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Tue, 7 Oct 2025 13:46:30 +0200 Subject: [PATCH 01/26] fragment : MaxSplit --- web/html/settings.html | 40 +++++++++++++------ .../settings/panel/subscription/json.html | 16 +++++--- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/web/html/settings.html b/web/html/settings.html index 26b936fa..e664e6ec 100644 --- a/web/html/settings.html +++ b/web/html/settings.html @@ -9,19 +9,20 @@ + message='{{ i18n "secAlertTitle" }}' color="red" show-icon closable> + + + + @@ -74,7 +77,8 @@ - + [[ p ]] From b578a33518fdd883a33aef71c315ce46d3237aec Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Tue, 7 Oct 2025 13:49:08 +0200 Subject: [PATCH 02/26] update dependencies --- go.mod | 19 +- go.sum | 40 +-- web/job/ldap_sync_job.go | 674 +++++++++++++++++++-------------------- 3 files changed, 366 insertions(+), 367 deletions(-) diff --git a/go.mod b/go.mod index d77c23cc..4040c87a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-contrib/gzip v1.2.3 github.com/gin-contrib/sessions v1.0.4 github.com/gin-gonic/gin v1.11.0 - github.com/go-ldap/ldap/v3 v3.4.11 + github.com/go-ldap/ldap/v3 v3.4.12 github.com/goccy/go-json v0.10.5 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 @@ -15,7 +15,7 @@ require ( github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml/v2 v2.2.4 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v4 v4.25.8 + github.com/shirou/gopsutil/v4 v4.25.9 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/valyala/fasthttp v1.66.0 github.com/xlzd/gotp v0.1.0 @@ -24,7 +24,7 @@ require ( golang.org/x/crypto v0.42.0 golang.org/x/sys v0.36.0 golang.org/x/text v0.29.0 - google.golang.org/grpc v1.75.1 + google.golang.org/grpc v1.76.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.0 ) @@ -45,7 +45,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/gorilla/context v1.1.2 // indirect @@ -70,11 +70,11 @@ require ( github.com/pires/go-proxyproto v0.8.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.54.0 // indirect + github.com/quic-go/quic-go v0.55.0 // indirect github.com/refraction-networking/utls v1.8.0 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/sagernet/sing v0.7.10 // indirect + github.com/sagernet/sing v0.7.12 // indirect github.com/sagernet/sing-shadowsocks v0.2.9 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect @@ -86,9 +86,8 @@ require ( github.com/valyala/fastjson v1.6.4 // indirect github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect - github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c // indirect + github.com/xtls/reality v0.0.0-20251005124704-8f4f0a188196 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.uber.org/mock v0.6.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/arch v0.21.0 // indirect golang.org/x/mod v0.28.0 // indirect @@ -98,8 +97,8 @@ require ( golang.org/x/tools v0.37.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect - google.golang.org/protobuf v1.36.9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87 // indirect + google.golang.org/protobuf v1.36.10 // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect lukechampine.com/blake3 v1.4.1 // indirect ) diff --git a/go.sum b/go.sum index 1cae2aae..05f5cc41 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= -github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= @@ -39,8 +39,8 @@ github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= -github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -54,8 +54,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= @@ -148,8 +148,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE= github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -158,14 +158,14 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw= -github.com/sagernet/sing v0.7.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.12 h1:MpMbO56crPRZTbltoj1wGk4Xj9+GiwH1wTO4s3fz1EA= +github.com/sagernet/sing v0.7.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM= github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= -github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= -github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= +github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= +github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -200,8 +200,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= -github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c h1:LHLhQY3mKXSpTcQAkjFR4/6ar3rXjQryNeM7khK3AHU= -github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= +github.com/xtls/reality v0.0.0-20251005124704-8f4f0a188196 h1:jb1y+Rm6UBW/CEV0FehsKlQ/2dnLsQjyUjn3UfWwbic= +github.com/xtls/reality v0.0.0-20251005124704-8f4f0a188196/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= github.com/xtls/xray-core v1.250911.0 h1:KMN8zVurAjHFixiUoFV/jwmzYohf27dQRntjV+8LQno= github.com/xtls/xray-core v1.250911.0/go.mod h1:LkqA/BFVtPS2e5fRzg/bkYas9nQu4Uztlx+/fjlLM9k= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -256,12 +256,12 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 h1:V1jCN2HBa8sySkR5vLcCSqJSTMv093Rw9EJefhQGP7M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87 h1:WgGZrMngVRRve7T3P5gbXdmedSmUpkf8uIUu1fg+biY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/web/job/ldap_sync_job.go b/web/job/ldap_sync_job.go index cb954932..6642bbcf 100644 --- a/web/job/ldap_sync_job.go +++ b/web/job/ldap_sync_job.go @@ -1,421 +1,421 @@ package job import ( - "time" + "time" - "github.com/mhsanaei/3x-ui/v2/database/model" - "github.com/mhsanaei/3x-ui/v2/logger" - ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap" - "github.com/mhsanaei/3x-ui/v2/web/service" - "strings" + "strings" - "github.com/google/uuid" - "strconv" + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/logger" + ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap" + "github.com/mhsanaei/3x-ui/v2/web/service" + + "strconv" + + "github.com/google/uuid" ) var DefaultTruthyValues = []string{"true", "1", "yes", "on"} type LdapSyncJob struct { - settingService service.SettingService - inboundService service.InboundService - xrayService service.XrayService + settingService service.SettingService + inboundService service.InboundService + xrayService service.XrayService } // --- Helper functions for mustGet --- func mustGetString(fn func() (string, error)) string { - v, err := fn() - if err != nil { - panic(err) - } - return v + v, err := fn() + if err != nil { + panic(err) + } + return v } func mustGetInt(fn func() (int, error)) int { - v, err := fn() - if err != nil { - panic(err) - } - return v + v, err := fn() + if err != nil { + panic(err) + } + return v } func mustGetBool(fn func() (bool, error)) bool { - v, err := fn() - if err != nil { - panic(err) - } - return v + v, err := fn() + if err != nil { + panic(err) + } + return v } func mustGetStringOr(fn func() (string, error), fallback string) string { - v, err := fn() - if err != nil || v == "" { - return fallback - } - return v + v, err := fn() + if err != nil || v == "" { + return fallback + } + return v } - func NewLdapSyncJob() *LdapSyncJob { - return new(LdapSyncJob) + return new(LdapSyncJob) } func (j *LdapSyncJob) Run() { - logger.Info("LDAP sync job started") + logger.Info("LDAP sync job started") - enabled, err := j.settingService.GetLdapEnable() - if err != nil || !enabled { - logger.Warning("LDAP disabled or failed to fetch flag") - return - } + enabled, err := j.settingService.GetLdapEnable() + if err != nil || !enabled { + logger.Warning("LDAP disabled or failed to fetch flag") + return + } - // --- LDAP fetch --- - cfg := ldaputil.Config{ - Host: mustGetString(j.settingService.GetLdapHost), - Port: mustGetInt(j.settingService.GetLdapPort), - UseTLS: mustGetBool(j.settingService.GetLdapUseTLS), - BindDN: mustGetString(j.settingService.GetLdapBindDN), - Password: mustGetString(j.settingService.GetLdapPassword), - BaseDN: mustGetString(j.settingService.GetLdapBaseDN), - UserFilter: mustGetString(j.settingService.GetLdapUserFilter), - UserAttr: mustGetString(j.settingService.GetLdapUserAttr), - FlagField: mustGetStringOr(j.settingService.GetLdapFlagField, mustGetString(j.settingService.GetLdapVlessField)), - TruthyVals: splitCsv(mustGetString(j.settingService.GetLdapTruthyValues)), - Invert: mustGetBool(j.settingService.GetLdapInvertFlag), - } + // --- LDAP fetch --- + cfg := ldaputil.Config{ + Host: mustGetString(j.settingService.GetLdapHost), + Port: mustGetInt(j.settingService.GetLdapPort), + UseTLS: mustGetBool(j.settingService.GetLdapUseTLS), + BindDN: mustGetString(j.settingService.GetLdapBindDN), + Password: mustGetString(j.settingService.GetLdapPassword), + BaseDN: mustGetString(j.settingService.GetLdapBaseDN), + UserFilter: mustGetString(j.settingService.GetLdapUserFilter), + UserAttr: mustGetString(j.settingService.GetLdapUserAttr), + FlagField: mustGetStringOr(j.settingService.GetLdapFlagField, mustGetString(j.settingService.GetLdapVlessField)), + TruthyVals: splitCsv(mustGetString(j.settingService.GetLdapTruthyValues)), + Invert: mustGetBool(j.settingService.GetLdapInvertFlag), + } - flags, err := ldaputil.FetchVlessFlags(cfg) - if err != nil { - logger.Warning("LDAP fetch failed:", err) - return - } - logger.Infof("Fetched %d LDAP flags", len(flags)) + flags, err := ldaputil.FetchVlessFlags(cfg) + if err != nil { + logger.Warning("LDAP fetch failed:", err) + return + } + logger.Infof("Fetched %d LDAP flags", len(flags)) - // --- Load all inbounds and all clients once --- - inboundTags := splitCsv(mustGetString(j.settingService.GetLdapInboundTags)) - inbounds, err := j.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("Failed to get inbounds:", err) - return - } + // --- Load all inbounds and all clients once --- + inboundTags := splitCsv(mustGetString(j.settingService.GetLdapInboundTags)) + inbounds, err := j.inboundService.GetAllInbounds() + if err != nil { + logger.Warning("Failed to get inbounds:", err) + return + } - allClients := map[string]*model.Client{} // email -> client - inboundMap := map[string]*model.Inbound{} // tag -> inbound - for _, ib := range inbounds { - inboundMap[ib.Tag] = ib - clients, _ := j.inboundService.GetClients(ib) - for i := range clients { - allClients[clients[i].Email] = &clients[i] - } - } + allClients := map[string]*model.Client{} // email -> client + inboundMap := map[string]*model.Inbound{} // tag -> inbound + for _, ib := range inbounds { + inboundMap[ib.Tag] = ib + clients, _ := j.inboundService.GetClients(ib) + for i := range clients { + allClients[clients[i].Email] = &clients[i] + } + } - // --- Prepare batch operations --- - autoCreate := mustGetBool(j.settingService.GetLdapAutoCreate) - defGB := mustGetInt(j.settingService.GetLdapDefaultTotalGB) - defExpiryDays := mustGetInt(j.settingService.GetLdapDefaultExpiryDays) - defLimitIP := mustGetInt(j.settingService.GetLdapDefaultLimitIP) + // --- Prepare batch operations --- + autoCreate := mustGetBool(j.settingService.GetLdapAutoCreate) + defGB := mustGetInt(j.settingService.GetLdapDefaultTotalGB) + defExpiryDays := mustGetInt(j.settingService.GetLdapDefaultExpiryDays) + defLimitIP := mustGetInt(j.settingService.GetLdapDefaultLimitIP) - clientsToCreate := map[string][]model.Client{} // tag -> []new clients - clientsToEnable := map[string][]string{} // tag -> []email - clientsToDisable := map[string][]string{} // tag -> []email + clientsToCreate := map[string][]model.Client{} // tag -> []new clients + clientsToEnable := map[string][]string{} // tag -> []email + clientsToDisable := map[string][]string{} // tag -> []email - for email, allowed := range flags { - exists := allClients[email] != nil - for _, tag := range inboundTags { - if !exists && allowed && autoCreate { - newClient := j.buildClient(inboundMap[tag], email, defGB, defExpiryDays, defLimitIP) - clientsToCreate[tag] = append(clientsToCreate[tag], newClient) - } else if exists { - if allowed && !allClients[email].Enable { - clientsToEnable[tag] = append(clientsToEnable[tag], email) - } else if !allowed && allClients[email].Enable { - clientsToDisable[tag] = append(clientsToDisable[tag], email) - } - } - } - } + for email, allowed := range flags { + exists := allClients[email] != nil + for _, tag := range inboundTags { + if !exists && allowed && autoCreate { + newClient := j.buildClient(inboundMap[tag], email, defGB, defExpiryDays, defLimitIP) + clientsToCreate[tag] = append(clientsToCreate[tag], newClient) + } else if exists { + if allowed && !allClients[email].Enable { + clientsToEnable[tag] = append(clientsToEnable[tag], email) + } else if !allowed && allClients[email].Enable { + clientsToDisable[tag] = append(clientsToDisable[tag], email) + } + } + } + } - // --- Execute batch create --- - for tag, newClients := range clientsToCreate { - if len(newClients) == 0 { - continue - } - payload := &model.Inbound{Id: inboundMap[tag].Id} - payload.Settings = j.clientsToJSON(newClients) - if _, err := j.inboundService.AddInboundClient(payload); err != nil { - logger.Warningf("Failed to add clients for tag %s: %v", tag, err) - } else { - logger.Infof("LDAP auto-create: %d clients for %s", len(newClients), tag) - j.xrayService.SetToNeedRestart() - } - } + // --- Execute batch create --- + for tag, newClients := range clientsToCreate { + if len(newClients) == 0 { + continue + } + payload := &model.Inbound{Id: inboundMap[tag].Id} + payload.Settings = j.clientsToJSON(newClients) + if _, err := j.inboundService.AddInboundClient(payload); err != nil { + logger.Warningf("Failed to add clients for tag %s: %v", tag, err) + } else { + logger.Infof("LDAP auto-create: %d clients for %s", len(newClients), tag) + j.xrayService.SetToNeedRestart() + } + } - // --- Execute enable/disable batch --- - for tag, emails := range clientsToEnable { - j.batchSetEnable(inboundMap[tag], emails, true) - } - for tag, emails := range clientsToDisable { - j.batchSetEnable(inboundMap[tag], emails, false) - } + // --- Execute enable/disable batch --- + for tag, emails := range clientsToEnable { + j.batchSetEnable(inboundMap[tag], emails, true) + } + for tag, emails := range clientsToDisable { + j.batchSetEnable(inboundMap[tag], emails, false) + } - // --- Auto delete clients not in LDAP --- - autoDelete := mustGetBool(j.settingService.GetLdapAutoDelete) - if autoDelete { - ldapEmailSet := map[string]struct{}{} - for e := range flags { - ldapEmailSet[e] = struct{}{} - } - for _, tag := range inboundTags { - j.deleteClientsNotInLDAP(tag, ldapEmailSet) - } - } + // --- Auto delete clients not in LDAP --- + autoDelete := mustGetBool(j.settingService.GetLdapAutoDelete) + if autoDelete { + ldapEmailSet := map[string]struct{}{} + for e := range flags { + ldapEmailSet[e] = struct{}{} + } + for _, tag := range inboundTags { + j.deleteClientsNotInLDAP(tag, ldapEmailSet) + } + } } - - func splitCsv(s string) []string { - if s == "" { - return DefaultTruthyValues - } - parts := strings.Split(s, ",") - out := make([]string, 0, len(parts)) - for _, p := range parts { - v := strings.TrimSpace(p) - if v != "" { - out = append(out, v) - } - } - return out + if s == "" { + return DefaultTruthyValues + } + parts := strings.Split(s, ",") + out := make([]string, 0, len(parts)) + for _, p := range parts { + v := strings.TrimSpace(p) + if v != "" { + out = append(out, v) + } + } + return out } - // buildClient creates a new client for auto-create func (j *LdapSyncJob) buildClient(ib *model.Inbound, email string, defGB, defExpiryDays, defLimitIP int) model.Client { - c := model.Client{ - Email: email, - Enable: true, - LimitIP: defLimitIP, - TotalGB: int64(defGB), - } - if defExpiryDays > 0 { - c.ExpiryTime = time.Now().Add(time.Duration(defExpiryDays) * 24 * time.Hour).UnixMilli() - } - switch ib.Protocol { - case model.Trojan, model.Shadowsocks: - c.Password = uuid.NewString() - default: - c.ID = uuid.NewString() - } - return c + c := model.Client{ + Email: email, + Enable: true, + LimitIP: defLimitIP, + TotalGB: int64(defGB), + } + if defExpiryDays > 0 { + c.ExpiryTime = time.Now().Add(time.Duration(defExpiryDays) * 24 * time.Hour).UnixMilli() + } + switch ib.Protocol { + case model.Trojan, model.Shadowsocks: + c.Password = uuid.NewString() + default: + c.ID = uuid.NewString() + } + return c } // batchSetEnable enables/disables clients in batch through a single call func (j *LdapSyncJob) batchSetEnable(ib *model.Inbound, emails []string, enable bool) { - if len(emails) == 0 { - return - } + if len(emails) == 0 { + return + } - // Prepare JSON for mass update - clients := make([]model.Client, 0, len(emails)) - for _, email := range emails { - clients = append(clients, model.Client{ - Email: email, - Enable: enable, - }) - } + // Prepare JSON for mass update + clients := make([]model.Client, 0, len(emails)) + for _, email := range emails { + clients = append(clients, model.Client{ + Email: email, + Enable: enable, + }) + } - payload := &model.Inbound{ - Id: ib.Id, - Settings: j.clientsToJSON(clients), - } + payload := &model.Inbound{ + Id: ib.Id, + Settings: j.clientsToJSON(clients), + } - // Use a single AddInboundClient call to update enable - if _, err := j.inboundService.AddInboundClient(payload); err != nil { - logger.Warningf("Batch set enable failed for inbound %s: %v", ib.Tag, err) - return - } + // Use a single AddInboundClient call to update enable + if _, err := j.inboundService.AddInboundClient(payload); err != nil { + logger.Warningf("Batch set enable failed for inbound %s: %v", ib.Tag, err) + return + } - logger.Infof("Batch set enable=%v for %d clients in inbound %s", enable, len(emails), ib.Tag) - j.xrayService.SetToNeedRestart() + logger.Infof("Batch set enable=%v for %d clients in inbound %s", enable, len(emails), ib.Tag) + j.xrayService.SetToNeedRestart() } // deleteClientsNotInLDAP deletes clients not in LDAP using batches and a single restart func (j *LdapSyncJob) deleteClientsNotInLDAP(inboundTag string, ldapEmails map[string]struct{}) { - inbounds, err := j.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("Failed to get inbounds for deletion:", err) - return - } + inbounds, err := j.inboundService.GetAllInbounds() + if err != nil { + logger.Warning("Failed to get inbounds for deletion:", err) + return + } - batchSize := 50 // clients in 1 batch - restartNeeded := false + batchSize := 50 // clients in 1 batch + restartNeeded := false - for _, ib := range inbounds { - if ib.Tag != inboundTag { - continue - } - clients, err := j.inboundService.GetClients(ib) - if err != nil { - logger.Warningf("Failed to get clients for inbound %s: %v", ib.Tag, err) - continue - } + for _, ib := range inbounds { + if ib.Tag != inboundTag { + continue + } + clients, err := j.inboundService.GetClients(ib) + if err != nil { + logger.Warningf("Failed to get clients for inbound %s: %v", ib.Tag, err) + continue + } - // Collect clients for deletion - toDelete := []model.Client{} - for _, c := range clients { - if _, ok := ldapEmails[c.Email]; !ok { - toDelete = append(toDelete, c) - } - } + // Collect clients for deletion + toDelete := []model.Client{} + for _, c := range clients { + if _, ok := ldapEmails[c.Email]; !ok { + toDelete = append(toDelete, c) + } + } - if len(toDelete) == 0 { - continue - } + if len(toDelete) == 0 { + continue + } - // Delete in batches - for i := 0; i < len(toDelete); i += batchSize { - end := i + batchSize - if end > len(toDelete) { - end = len(toDelete) - } - batch := toDelete[i:end] + // Delete in batches + for i := 0; i < len(toDelete); i += batchSize { + end := i + batchSize + if end > len(toDelete) { + end = len(toDelete) + } + batch := toDelete[i:end] - for _, c := range batch { - var clientKey string - switch ib.Protocol { - case model.Trojan: - clientKey = c.Password - case model.Shadowsocks: - clientKey = c.Email - default: // vless/vmess - clientKey = c.ID - } + for _, c := range batch { + var clientKey string + switch ib.Protocol { + case model.Trojan: + clientKey = c.Password + case model.Shadowsocks: + clientKey = c.Email + default: // vless/vmess + clientKey = c.ID + } - if _, err := j.inboundService.DelInboundClient(ib.Id, clientKey); err != nil { - logger.Warningf("Failed to delete client %s from inbound id=%d(tag=%s): %v", - c.Email, ib.Id, ib.Tag, err) - } else { - logger.Infof("Deleted client %s from inbound id=%d(tag=%s)", - c.Email, ib.Id, ib.Tag) - // do not restart here - restartNeeded = true - } - } - } - } + if _, err := j.inboundService.DelInboundClient(ib.Id, clientKey); err != nil { + logger.Warningf("Failed to delete client %s from inbound id=%d(tag=%s): %v", + c.Email, ib.Id, ib.Tag, err) + } else { + logger.Infof("Deleted client %s from inbound id=%d(tag=%s)", + c.Email, ib.Id, ib.Tag) + // do not restart here + restartNeeded = true + } + } + } + } - // One time after all batches - if restartNeeded { - j.xrayService.SetToNeedRestart() - logger.Info("Xray restart scheduled after batch deletion") - } + // One time after all batches + if restartNeeded { + j.xrayService.SetToNeedRestart() + logger.Info("Xray restart scheduled after batch deletion") + } } - // clientsToJSON serializes an array of clients to JSON func (j *LdapSyncJob) clientsToJSON(clients []model.Client) string { - b := strings.Builder{} - b.WriteString("{\"clients\":[") - for i, c := range clients { - if i > 0 { b.WriteString(",") } - b.WriteString(j.clientToJSON(c)) - } - b.WriteString("]}") - return b.String() + b := strings.Builder{} + b.WriteString("{\"clients\":[") + for i, c := range clients { + if i > 0 { + b.WriteString(",") + } + b.WriteString(j.clientToJSON(c)) + } + b.WriteString("]}") + return b.String() } - // ensureClientExists adds client with defaults to inbound tag if not present func (j *LdapSyncJob) ensureClientExists(inboundTag string, email string, defGB int, defExpiryDays int, defLimitIP int) { - inbounds, err := j.inboundService.GetAllInbounds() - if err != nil { - logger.Warning("ensureClientExists: get inbounds failed:", err) - return - } - var target *model.Inbound - for _, ib := range inbounds { - if ib.Tag == inboundTag { - target = ib - break - } - } - if target == nil { - logger.Debugf("ensureClientExists: inbound tag %s not found", inboundTag) - return - } - // check if email already exists in this inbound - clients, err := j.inboundService.GetClients(target) - if err == nil { - for _, c := range clients { - if c.Email == email { - return - } - } - } + inbounds, err := j.inboundService.GetAllInbounds() + if err != nil { + logger.Warning("ensureClientExists: get inbounds failed:", err) + return + } + var target *model.Inbound + for _, ib := range inbounds { + if ib.Tag == inboundTag { + target = ib + break + } + } + if target == nil { + logger.Debugf("ensureClientExists: inbound tag %s not found", inboundTag) + return + } + // check if email already exists in this inbound + clients, err := j.inboundService.GetClients(target) + if err == nil { + for _, c := range clients { + if c.Email == email { + return + } + } + } - // build new client according to protocol - newClient := model.Client{ - Email: email, - Enable: true, - LimitIP: defLimitIP, - TotalGB: int64(defGB), - } - if defExpiryDays > 0 { - newClient.ExpiryTime = time.Now().Add(time.Duration(defExpiryDays) * 24 * time.Hour).UnixMilli() - } + // build new client according to protocol + newClient := model.Client{ + Email: email, + Enable: true, + LimitIP: defLimitIP, + TotalGB: int64(defGB), + } + if defExpiryDays > 0 { + newClient.ExpiryTime = time.Now().Add(time.Duration(defExpiryDays) * 24 * time.Hour).UnixMilli() + } - switch target.Protocol { - case model.Trojan: - newClient.Password = uuid.NewString() - case model.Shadowsocks: - newClient.Password = uuid.NewString() - default: // VMESS/VLESS and others using ID - newClient.ID = uuid.NewString() - } + switch target.Protocol { + case model.Trojan: + newClient.Password = uuid.NewString() + case model.Shadowsocks: + newClient.Password = uuid.NewString() + default: // VMESS/VLESS and others using ID + newClient.ID = uuid.NewString() + } - // prepare inbound payload with only the new client - payload := &model.Inbound{Id: target.Id} - payload.Settings = `{"clients":[` + j.clientToJSON(newClient) + `]}` + // prepare inbound payload with only the new client + payload := &model.Inbound{Id: target.Id} + payload.Settings = `{"clients":[` + j.clientToJSON(newClient) + `]}` - if _, err := j.inboundService.AddInboundClient(payload); err != nil { - logger.Warning("ensureClientExists: add client failed:", err) - } else { - j.xrayService.SetToNeedRestart() - logger.Infof("LDAP auto-create: %s in %s", email, inboundTag) - } + if _, err := j.inboundService.AddInboundClient(payload); err != nil { + logger.Warning("ensureClientExists: add client failed:", err) + } else { + j.xrayService.SetToNeedRestart() + logger.Infof("LDAP auto-create: %s in %s", email, inboundTag) + } } // clientToJSON serializes minimal client fields to JSON object string without extra deps func (j *LdapSyncJob) clientToJSON(c model.Client) string { - // construct minimal JSON manually to avoid importing json for simple case - b := strings.Builder{} - b.WriteString("{") - if c.ID != "" { - b.WriteString("\"id\":\"") - b.WriteString(c.ID) - b.WriteString("\",") - } - if c.Password != "" { - b.WriteString("\"password\":\"") - b.WriteString(c.Password) - b.WriteString("\",") - } - b.WriteString("\"email\":\"") - b.WriteString(c.Email) - b.WriteString("\",") - b.WriteString("\"enable\":") - if c.Enable { b.WriteString("true") } else { b.WriteString("false") } - b.WriteString(",") - b.WriteString("\"limitIp\":") - b.WriteString(strconv.Itoa(c.LimitIP)) - b.WriteString(",") - b.WriteString("\"totalGB\":") - b.WriteString(strconv.FormatInt(c.TotalGB, 10)) - if c.ExpiryTime > 0 { - b.WriteString(",\"expiryTime\":") - b.WriteString(strconv.FormatInt(c.ExpiryTime, 10)) - } - b.WriteString("}") - return b.String() + // construct minimal JSON manually to avoid importing json for simple case + b := strings.Builder{} + b.WriteString("{") + if c.ID != "" { + b.WriteString("\"id\":\"") + b.WriteString(c.ID) + b.WriteString("\",") + } + if c.Password != "" { + b.WriteString("\"password\":\"") + b.WriteString(c.Password) + b.WriteString("\",") + } + b.WriteString("\"email\":\"") + b.WriteString(c.Email) + b.WriteString("\",") + b.WriteString("\"enable\":") + if c.Enable { + b.WriteString("true") + } else { + b.WriteString("false") + } + b.WriteString(",") + b.WriteString("\"limitIp\":") + b.WriteString(strconv.Itoa(c.LimitIP)) + b.WriteString(",") + b.WriteString("\"totalGB\":") + b.WriteString(strconv.FormatInt(c.TotalGB, 10)) + if c.ExpiryTime > 0 { + b.WriteString(",\"expiryTime\":") + b.WriteString(strconv.FormatInt(c.ExpiryTime, 10)) + } + b.WriteString("}") + return b.String() } - - From 00baeffe7497cfd7d4566aa03756a6f5b78f795d Mon Sep 17 00:00:00 2001 From: fpointsstar <74316923+thefsfc@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:31:32 +0900 Subject: [PATCH 03/26] Update translate.ru_RU.toml (#3574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix RU translation for login title: replace “Приветствие!” with “Добро пожаловать!” to match English “Welcome”. --- web/translation/translate.ru_RU.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index c75ae649..3416e7fa 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -99,7 +99,7 @@ [pages.login] "hello" = "Привет!" -"title" = "Приветствие!" +"title" = "Добро пожаловать!" "loginAgain" = "Сессия истекла. Войдите в систему снова" [pages.login.toasts] From 8afa39144ec68714aa584ad34925dde4bbdb14ca Mon Sep 17 00:00:00 2001 From: "Slava M." <28267670+slavafyi@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:39:29 +0300 Subject: [PATCH 04/26] feat: add file logger support (#3575) * feat: add backend for file logger --- logger/logger.go | 132 ++++++++++++++++++++++++++++++++++++----------- sub/sub.go | 2 +- 2 files changed, 103 insertions(+), 31 deletions(-) diff --git a/logger/logger.go b/logger/logger.go index ccacf697..7d26dcd0 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,21 +1,29 @@ // Package logger provides logging functionality for the 3x-ui panel with -// buffered log storage and multiple log levels. +// dual-backend logging (console/syslog and file) and buffered log storage for web UI. package logger import ( "fmt" "os" + "path/filepath" + "runtime" "time" + "github.com/mhsanaei/3x-ui/v2/config" "github.com/op/go-logging" ) -var ( - logger *logging.Logger +const ( + maxLogBufferSize = 10240 // Maximum log entries kept in memory + logFileName = "3xui.log" // Log file name + timeFormat = "2006/01/02 15:04:05" // Log timestamp format +) - // addToBuffer appends a log entry into the in-memory ring buffer used for - // retrieving recent logs via the web UI. It keeps the buffer bounded to avoid - // uncontrolled growth. +var ( + logger *logging.Logger + logFile *os.File + + // logBuffer maintains recent log entries in memory for web UI retrieval logBuffer []struct { time string level logging.Level @@ -23,37 +31,100 @@ var ( } ) -func init() { - InitLogger(logging.INFO) -} - -// InitLogger initializes the logger with the specified logging level. +// InitLogger initializes dual logging backends: console/syslog and file. +// Console logging uses the specified level, file logging always uses DEBUG level. func InitLogger(level logging.Level) { newLogger := logging.MustGetLogger("x-ui") - var err error - var backend logging.Backend - var format logging.Formatter - ppid := os.Getppid() + backends := make([]logging.Backend, 0, 2) - backend, err = logging.NewSyslogBackend("") - if err != nil { - println(err) - backend = logging.NewLogBackend(os.Stderr, "", 0) - } - if ppid > 0 && err != nil { - format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`) - } else { - format = logging.MustStringFormatter(`%{level} - %{message}`) + // Console/syslog backend with configurable level + if consoleBackend := initDefaultBackend(); consoleBackend != nil { + leveledBackend := logging.AddModuleLevel(consoleBackend) + leveledBackend.SetLevel(level, "x-ui") + backends = append(backends, leveledBackend) } - backendFormatter := logging.NewBackendFormatter(backend, format) - backendLeveled := logging.AddModuleLevel(backendFormatter) - backendLeveled.SetLevel(level, "x-ui") - newLogger.SetBackend(backendLeveled) + // File backend with DEBUG level for comprehensive logging + if fileBackend := initFileBackend(); fileBackend != nil { + leveledBackend := logging.AddModuleLevel(fileBackend) + leveledBackend.SetLevel(logging.DEBUG, "x-ui") + backends = append(backends, leveledBackend) + } + multiBackend := logging.MultiLogger(backends...) + newLogger.SetBackend(multiBackend) logger = newLogger } +// initDefaultBackend creates the console/syslog logging backend. +// Windows: Uses stderr directly (no syslog support) +// Unix-like: Attempts syslog, falls back to stderr +func initDefaultBackend() logging.Backend { + var backend logging.Backend + includeTime := false + + if runtime.GOOS == "windows" { + // Windows: Use stderr directly (no syslog support) + backend = logging.NewLogBackend(os.Stderr, "", 0) + includeTime = true + } else { + // Unix-like: Try syslog, fallback to stderr + if syslogBackend, err := logging.NewSyslogBackend(""); err != nil { + fmt.Fprintf(os.Stderr, "syslog backend disabled: %v\n", err) + backend = logging.NewLogBackend(os.Stderr, "", 0) + includeTime = os.Getppid() > 0 + } else { + backend = syslogBackend + } + } + + return logging.NewBackendFormatter(backend, newFormatter(includeTime)) +} + +// initFileBackend creates the file logging backend. +// Creates log directory and truncates log file on startup for fresh logs. +func initFileBackend() logging.Backend { + logDir := config.GetLogFolder() + if err := os.MkdirAll(logDir, 0o750); err != nil { + fmt.Fprintf(os.Stderr, "failed to create log folder %s: %v\n", logDir, err) + return nil + } + + logPath := filepath.Join(logDir, logFileName) + file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o660) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open log file %s: %v\n", logPath, err) + return nil + } + + // Close previous log file if exists + if logFile != nil { + _ = logFile.Close() + } + logFile = file + + backend := logging.NewLogBackend(file, "", 0) + return logging.NewBackendFormatter(backend, newFormatter(true)) +} + +// newFormatter creates a log formatter with optional timestamp. +func newFormatter(withTime bool) logging.Formatter { + format := `%{level} - %{message}` + if withTime { + format = `%{time:` + timeFormat + `} %{level} - %{message}` + } + return logging.MustStringFormatter(format) +} + +// CloseLogger closes the log file and cleans up resources. +// Should be called during application shutdown. +func CloseLogger() { + if logFile != nil { + _ = logFile.Close() + logFile = nil + } +} + // Debug logs a debug message and adds it to the log buffer. func Debug(args ...any) { logger.Debug(args...) @@ -114,9 +185,10 @@ func Errorf(format string, args ...any) { addToBuffer("ERROR", fmt.Sprintf(format, args...)) } +// addToBuffer adds a log entry to the in-memory ring buffer for web UI retrieval. func addToBuffer(level string, newLog string) { t := time.Now() - if len(logBuffer) >= 10240 { + if len(logBuffer) >= maxLogBufferSize { logBuffer = logBuffer[1:] } @@ -126,7 +198,7 @@ func addToBuffer(level string, newLog string) { level logging.Level log string }{ - time: t.Format("2006/01/02 15:04:05"), + time: t.Format(timeFormat), level: logLevel, log: newLog, }) diff --git a/sub/sub.go b/sub/sub.go index 1a1a7d9e..0605c8b9 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -103,7 +103,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { if basePath != "/" && !strings.HasSuffix(basePath, "/") { basePath += "/" } - logger.Debug("sub: Setting base_path to:", basePath) + // logger.Debug("sub: Setting base_path to:", basePath) engine.Use(func(c *gin.Context) { c.Set("base_path", basePath) }) From d8523bbdac4a65e3e33bca6d1a50a58f33804f52 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Tue, 14 Oct 2025 22:03:17 +0200 Subject: [PATCH 05/26] fix(import): prevent sqlite disk I/O error by validating temp DB then swapping --- database/db.go | 27 +++++++++++++++++++++++++++ web/service/server.go | 25 +++++++++++++++++++------ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/database/db.go b/database/db.go index 6de81d79..6b579dd9 100644 --- a/database/db.go +++ b/database/db.go @@ -4,6 +4,7 @@ package database import ( "bytes" + "errors" "io" "io/fs" "log" @@ -199,3 +200,29 @@ func Checkpoint() error { } return nil } + +// ValidateSQLiteDB opens the provided sqlite DB path with a throw-away connection +// and runs a PRAGMA integrity_check to ensure the file is structurally sound. +// It does not mutate global state or run migrations. +func ValidateSQLiteDB(dbPath string) error { + if _, err := os.Stat(dbPath); err != nil { // file must exist + return err + } + gdb, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{Logger: logger.Discard}) + if err != nil { + return err + } + sqlDB, err := gdb.DB() + if err != nil { + return err + } + defer sqlDB.Close() + var res string + if err := gdb.Raw("PRAGMA integrity_check;").Scan(&res).Error; err != nil { + return err + } + if res != "ok" { + return errors.New("sqlite integrity check failed: " + res) + } + return nil +} diff --git a/web/service/server.go b/web/service/server.go index eb261c88..b7cfc3a7 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -942,13 +942,26 @@ func (s *ServerService) ImportDB(file multipart.File) error { return common.NewErrorf("Error saving db: %v", err) } - // Check if we can init the db or not - if err = database.InitDB(tempPath); err != nil { - return common.NewErrorf("Error checking db: %v", err) + // Close temp file before opening via sqlite + if err = tempFile.Close(); err != nil { + return common.NewErrorf("Error closing temporary db file: %v", err) + } + tempFile = nil + + // Validate integrity (no migrations / side effects) + if err = database.ValidateSQLiteDB(tempPath); err != nil { + return common.NewErrorf("Invalid or corrupt db file: %v", err) } - // Stop Xray - s.StopXrayService() + // Stop Xray (ignore error but log) + if errStop := s.StopXrayService(); errStop != nil { + logger.Warningf("Failed to stop Xray before DB import: %v", errStop) + } + + // Close existing DB to release file locks (especially on Windows) + if errClose := database.CloseDB(); errClose != nil { + logger.Warningf("Failed to close existing DB before replacement: %v", errClose) + } // Backup the current database for fallback fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath()) @@ -983,7 +996,7 @@ func (s *ServerService) ImportDB(file multipart.File) error { return common.NewErrorf("Error moving db file: %v", err) } - // Migrate DB + // Open & migrate new DB if err = database.InitDB(config.GetDBPath()); err != nil { if errRename := os.Rename(fallbackPath, config.GetDBPath()); errRename != nil { return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename) From 2b2ed3349a081ee0c50041fcf7e01db4760c268e Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Wed, 15 Oct 2025 11:40:04 +0200 Subject: [PATCH 06/26] Xray-core v25.10.15 --- .github/workflows/release.yml | 4 +-- DockerInit.sh | 2 +- go.mod | 32 +++++++++---------- go.sum | 60 +++++++++++++++++------------------ 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d56cfdf..08112c7b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: cd x-ui/bin # Download dependencies - Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.9.11/" + Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.10.15/" if [ "${{ matrix.platform }}" == "amd64" ]; then wget -q ${Xray_URL}Xray-linux-64.zip unzip Xray-linux-64.zip @@ -183,7 +183,7 @@ jobs: cd x-ui\bin # Download Xray for Windows - $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.9.11/" + $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/" Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath . Remove-Item "Xray-windows-64.zip" diff --git a/DockerInit.sh b/DockerInit.sh index db391359..fb603fb8 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -27,7 +27,7 @@ case $1 in esac mkdir -p build/bin cd build/bin -wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.9.11/Xray-linux-${ARCH}.zip" +wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" diff --git a/go.mod b/go.mod index 4040c87a..816d05a2 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/mhsanaei/3x-ui/v2 -go 1.25.1 +go 1.25.2 require ( - github.com/gin-contrib/gzip v1.2.3 + github.com/gin-contrib/gzip v1.2.4 github.com/gin-contrib/sessions v1.0.4 github.com/gin-gonic/gin v1.11.0 github.com/go-ldap/ldap/v3 v3.4.12 @@ -17,13 +17,13 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v4 v4.25.9 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/valyala/fasthttp v1.66.0 + github.com/valyala/fasthttp v1.67.0 github.com/xlzd/gotp v0.1.0 - github.com/xtls/xray-core v1.250911.0 + github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6 go.uber.org/atomic v1.11.0 - golang.org/x/crypto v0.42.0 - golang.org/x/sys v0.36.0 - golang.org/x/text v0.29.0 + golang.org/x/crypto v0.43.0 + golang.org/x/sys v0.37.0 + golang.org/x/text v0.30.0 google.golang.org/grpc v1.76.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.0 @@ -61,7 +61,7 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect + github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/miekg/dns v1.1.68 // indirect @@ -71,7 +71,7 @@ require ( github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.55.0 // indirect - github.com/refraction-networking/utls v1.8.0 // indirect + github.com/refraction-networking/utls v1.8.1 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sagernet/sing v0.7.12 // indirect @@ -86,18 +86,18 @@ require ( github.com/valyala/fastjson v1.6.4 // indirect github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect - github.com/xtls/reality v0.0.0-20251005124704-8f4f0a188196 // indirect + github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/arch v0.21.0 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/net v0.44.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/time v0.13.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.38.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect google.golang.org/protobuf v1.36.10 // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/go.sum b/go.sum index 05f5cc41..d3e85890 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIp github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U= -github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c= +github.com/gin-contrib/gzip v1.2.4 h1:yNz4EhPC2kHSZJD1oc1zwp7MLEhEZ3goQeGM3a1b6jU= +github.com/gin-contrib/gzip v1.2.4/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U= github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= @@ -117,8 +117,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= -github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= @@ -150,8 +150,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= -github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE= -github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= +github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo= +github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -190,8 +190,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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU= -github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= +github.com/valyala/fasthttp v1.67.0 h1:tqKlJMUP6iuNG8hGjK/s9J4kadH7HLV4ijEcPGsezac= +github.com/valyala/fasthttp v1.67.0/go.mod h1:qYSIpqt/0XNmShgo/8Aq8E3UYWVVwNS2QYmzd8WIEPM= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= @@ -200,10 +200,10 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= -github.com/xtls/reality v0.0.0-20251005124704-8f4f0a188196 h1:jb1y+Rm6UBW/CEV0FehsKlQ/2dnLsQjyUjn3UfWwbic= -github.com/xtls/reality v0.0.0-20251005124704-8f4f0a188196/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= -github.com/xtls/xray-core v1.250911.0 h1:KMN8zVurAjHFixiUoFV/jwmzYohf27dQRntjV+8LQno= -github.com/xtls/xray-core v1.250911.0/go.mod h1:LkqA/BFVtPS2e5fRzg/bkYas9nQu4Uztlx+/fjlLM9k= +github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU= +github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= +github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6 h1:gwgJxWb9OABUJAYxiS33nQzk3MRVjidzBnHBrzKnxOw= +github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -226,14 +226,14 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -242,22 +242,22 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87 h1:WgGZrMngVRRve7T3P5gbXdmedSmUpkf8uIUu1fg+biY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251006185510-65f7160b3a87/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= From 01d4a7488dac0bb81dc8f31ec29d5c83f254b29e Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Wed, 15 Oct 2025 11:40:40 +0200 Subject: [PATCH 07/26] v2.8.5 --- config/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version b/config/version index 0409c163..7f04bb11 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -2.8.4 \ No newline at end of file +2.8.5 \ No newline at end of file From 713a7328f60eec94c56b7ad6d17a942dffcfd944 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Tue, 21 Oct 2025 13:02:55 +0200 Subject: [PATCH 08/26] gofmt --- util/ldap/ldap.go | 238 ++++++++++++++++++++--------------------- web/entity/entity.go | 44 ++++---- web/service/inbound.go | 25 +++-- web/service/setting.go | 80 +++++++------- web/service/user.go | 62 +++++------ 5 files changed, 223 insertions(+), 226 deletions(-) diff --git a/util/ldap/ldap.go b/util/ldap/ldap.go index 1c7a20e7..795d0e23 100644 --- a/util/ldap/ldap.go +++ b/util/ldap/ldap.go @@ -1,144 +1,142 @@ package ldaputil import ( - "crypto/tls" - "fmt" + "crypto/tls" + "fmt" - "github.com/go-ldap/ldap/v3" + "github.com/go-ldap/ldap/v3" ) type Config struct { - Host string - Port int - UseTLS bool - BindDN string - Password string - BaseDN string - UserFilter string - UserAttr string - FlagField string - TruthyVals []string - Invert bool + Host string + Port int + UseTLS bool + BindDN string + Password string + BaseDN string + UserFilter string + UserAttr string + FlagField string + TruthyVals []string + Invert bool } // FetchVlessFlags returns map[email]enabled func FetchVlessFlags(cfg Config) (map[string]bool, error) { - addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) - var conn *ldap.Conn - var err error - if cfg.UseTLS { - conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) - } else { - conn, err = ldap.Dial("tcp", addr) - } - if err != nil { - return nil, err - } - defer conn.Close() + addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + var conn *ldap.Conn + var err error + if cfg.UseTLS { + conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) + } else { + conn, err = ldap.Dial("tcp", addr) + } + if err != nil { + return nil, err + } + defer conn.Close() - if cfg.BindDN != "" { - if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { - return nil, err - } - } + if cfg.BindDN != "" { + if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { + return nil, err + } + } - if cfg.UserFilter == "" { - cfg.UserFilter = "(objectClass=person)" - } - if cfg.UserAttr == "" { - cfg.UserAttr = "mail" - } - // if field not set we fallback to legacy vless_enabled - if cfg.FlagField == "" { - cfg.FlagField = "vless_enabled" - } + if cfg.UserFilter == "" { + cfg.UserFilter = "(objectClass=person)" + } + if cfg.UserAttr == "" { + cfg.UserAttr = "mail" + } + // if field not set we fallback to legacy vless_enabled + if cfg.FlagField == "" { + cfg.FlagField = "vless_enabled" + } - req := ldap.NewSearchRequest( - cfg.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - cfg.UserFilter, - []string{cfg.UserAttr, cfg.FlagField}, - nil, - ) + req := ldap.NewSearchRequest( + cfg.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + cfg.UserFilter, + []string{cfg.UserAttr, cfg.FlagField}, + nil, + ) - res, err := conn.Search(req) - if err != nil { - return nil, err - } + res, err := conn.Search(req) + if err != nil { + return nil, err + } - result := make(map[string]bool, len(res.Entries)) - for _, e := range res.Entries { - user := e.GetAttributeValue(cfg.UserAttr) - if user == "" { - continue - } - val := e.GetAttributeValue(cfg.FlagField) - enabled := false - for _, t := range cfg.TruthyVals { - if val == t { - enabled = true - break - } - } - if cfg.Invert { - enabled = !enabled - } - result[user] = enabled - } - return result, nil + result := make(map[string]bool, len(res.Entries)) + for _, e := range res.Entries { + user := e.GetAttributeValue(cfg.UserAttr) + if user == "" { + continue + } + val := e.GetAttributeValue(cfg.FlagField) + enabled := false + for _, t := range cfg.TruthyVals { + if val == t { + enabled = true + break + } + } + if cfg.Invert { + enabled = !enabled + } + result[user] = enabled + } + return result, nil } // AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password. func AuthenticateUser(cfg Config, username, password string) (bool, error) { - addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) - var conn *ldap.Conn - var err error - if cfg.UseTLS { - conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) - } else { - conn, err = ldap.Dial("tcp", addr) - } - if err != nil { - return false, err - } - defer conn.Close() + addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + var conn *ldap.Conn + var err error + if cfg.UseTLS { + conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) + } else { + conn, err = ldap.Dial("tcp", addr) + } + if err != nil { + return false, err + } + defer conn.Close() - // Optional initial bind for search - if cfg.BindDN != "" { - if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { - return false, err - } - } + // Optional initial bind for search + if cfg.BindDN != "" { + if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { + return false, err + } + } - if cfg.UserFilter == "" { - cfg.UserFilter = "(objectClass=person)" - } - if cfg.UserAttr == "" { - cfg.UserAttr = "uid" - } + if cfg.UserFilter == "" { + cfg.UserFilter = "(objectClass=person)" + } + if cfg.UserAttr == "" { + cfg.UserAttr = "uid" + } - // Build filter to find specific user - filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username)) - req := ldap.NewSearchRequest( - cfg.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, - filter, - []string{"dn"}, - nil, - ) - res, err := conn.Search(req) - if err != nil { - return false, err - } - if len(res.Entries) == 0 { - return false, nil - } - userDN := res.Entries[0].DN - // Try to bind as the user - if err := conn.Bind(userDN, password); err != nil { - return false, nil - } - return true, nil + // Build filter to find specific user + filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username)) + req := ldap.NewSearchRequest( + cfg.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, + filter, + []string{"dn"}, + nil, + ) + res, err := conn.Search(req) + if err != nil { + return false, err + } + if len(res.Entries) == 0 { + return false, nil + } + userDN := res.Entries[0].DN + // Try to bind as the user + if err := conn.Bind(userDN, password); err != nil { + return false, nil + } + return true, nil } - - diff --git a/web/entity/entity.go b/web/entity/entity.go index de054e2b..42e2df85 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -74,30 +74,30 @@ type AllSetting struct { SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration - SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` - + SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` + // LDAP settings - LdapEnable bool `json:"ldapEnable" form:"ldapEnable"` - LdapHost string `json:"ldapHost" form:"ldapHost"` - LdapPort int `json:"ldapPort" form:"ldapPort"` - LdapUseTLS bool `json:"ldapUseTLS" form:"ldapUseTLS"` - LdapBindDN string `json:"ldapBindDN" form:"ldapBindDN"` - LdapPassword string `json:"ldapPassword" form:"ldapPassword"` - LdapBaseDN string `json:"ldapBaseDN" form:"ldapBaseDN"` - LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"` - LdapUserAttr string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid - LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"` - LdapSyncCron string `json:"ldapSyncCron" form:"ldapSyncCron"` + LdapEnable bool `json:"ldapEnable" form:"ldapEnable"` + LdapHost string `json:"ldapHost" form:"ldapHost"` + LdapPort int `json:"ldapPort" form:"ldapPort"` + LdapUseTLS bool `json:"ldapUseTLS" form:"ldapUseTLS"` + LdapBindDN string `json:"ldapBindDN" form:"ldapBindDN"` + LdapPassword string `json:"ldapPassword" form:"ldapPassword"` + LdapBaseDN string `json:"ldapBaseDN" form:"ldapBaseDN"` + LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"` + LdapUserAttr string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid + LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"` + LdapSyncCron string `json:"ldapSyncCron" form:"ldapSyncCron"` // Generic flag configuration - LdapFlagField string `json:"ldapFlagField" form:"ldapFlagField"` - LdapTruthyValues string `json:"ldapTruthyValues" form:"ldapTruthyValues"` - LdapInvertFlag bool `json:"ldapInvertFlag" form:"ldapInvertFlag"` - LdapInboundTags string `json:"ldapInboundTags" form:"ldapInboundTags"` - LdapAutoCreate bool `json:"ldapAutoCreate" form:"ldapAutoCreate"` - LdapAutoDelete bool `json:"ldapAutoDelete" form:"ldapAutoDelete"` - LdapDefaultTotalGB int `json:"ldapDefaultTotalGB" form:"ldapDefaultTotalGB"` - LdapDefaultExpiryDays int `json:"ldapDefaultExpiryDays" form:"ldapDefaultExpiryDays"` - LdapDefaultLimitIP int `json:"ldapDefaultLimitIP" form:"ldapDefaultLimitIP"` + LdapFlagField string `json:"ldapFlagField" form:"ldapFlagField"` + LdapTruthyValues string `json:"ldapTruthyValues" form:"ldapTruthyValues"` + LdapInvertFlag bool `json:"ldapInvertFlag" form:"ldapInvertFlag"` + LdapInboundTags string `json:"ldapInboundTags" form:"ldapInboundTags"` + LdapAutoCreate bool `json:"ldapAutoCreate" form:"ldapAutoCreate"` + LdapAutoDelete bool `json:"ldapAutoDelete" form:"ldapAutoDelete"` + LdapDefaultTotalGB int `json:"ldapDefaultTotalGB" form:"ldapDefaultTotalGB"` + LdapDefaultExpiryDays int `json:"ldapDefaultExpiryDays" form:"ldapDefaultExpiryDays"` + LdapDefaultLimitIP int `json:"ldapDefaultLimitIP" form:"ldapDefaultLimitIP"` // JSON subscription routing rules } diff --git a/web/service/inbound.go b/web/service/inbound.go index 93414801..66e87a4f 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1569,21 +1569,20 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo return !clientOldEnabled, needRestart, nil } - // SetClientEnableByEmail sets client enable state to desired value; returns (changed, needRestart, error) func (s *InboundService) SetClientEnableByEmail(clientEmail string, enable bool) (bool, bool, error) { - current, err := s.checkIsEnabledByEmail(clientEmail) - if err != nil { - return false, false, err - } - if current == enable { - return false, false, nil - } - newEnabled, needRestart, err := s.ToggleClientEnableByEmail(clientEmail) - if err != nil { - return false, needRestart, err - } - return newEnabled == enable, needRestart, nil + current, err := s.checkIsEnabledByEmail(clientEmail) + if err != nil { + return false, false, err + } + if current == enable { + return false, false, nil + } + newEnabled, needRestart, err := s.ToggleClientEnableByEmail(clientEmail) + if err != nil { + return false, needRestart, err + } + return newEnabled == enable, needRestart, nil } func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) { diff --git a/web/service/setting.go b/web/service/setting.go index fa85d58c..c8ce7896 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -74,26 +74,26 @@ var defaultValueMap = map[string]string{ "externalTrafficInformEnable": "false", "externalTrafficInformURI": "", // LDAP defaults - "ldapEnable": "false", - "ldapHost": "", - "ldapPort": "389", - "ldapUseTLS": "false", - "ldapBindDN": "", - "ldapPassword": "", - "ldapBaseDN": "", - "ldapUserFilter": "(objectClass=person)", - "ldapUserAttr": "mail", - "ldapVlessField": "vless_enabled", - "ldapSyncCron": "@every 1m", - "ldapFlagField": "", - "ldapTruthyValues": "true,1,yes,on", - "ldapInvertFlag": "false", - "ldapInboundTags": "", - "ldapAutoCreate": "false", - "ldapAutoDelete": "false", - "ldapDefaultTotalGB": "0", - "ldapDefaultExpiryDays": "0", - "ldapDefaultLimitIP": "0", + "ldapEnable": "false", + "ldapHost": "", + "ldapPort": "389", + "ldapUseTLS": "false", + "ldapBindDN": "", + "ldapPassword": "", + "ldapBaseDN": "", + "ldapUserFilter": "(objectClass=person)", + "ldapUserAttr": "mail", + "ldapVlessField": "vless_enabled", + "ldapSyncCron": "@every 1m", + "ldapFlagField": "", + "ldapTruthyValues": "true,1,yes,on", + "ldapInvertFlag": "false", + "ldapInboundTags": "", + "ldapAutoCreate": "false", + "ldapAutoDelete": "false", + "ldapDefaultTotalGB": "0", + "ldapDefaultExpiryDays": "0", + "ldapDefaultLimitIP": "0", } // SettingService provides business logic for application settings management. @@ -565,83 +565,83 @@ func (s *SettingService) GetIpLimitEnable() (bool, error) { // LDAP exported getters func (s *SettingService) GetLdapEnable() (bool, error) { - return s.getBool("ldapEnable") + return s.getBool("ldapEnable") } func (s *SettingService) GetLdapHost() (string, error) { - return s.getString("ldapHost") + return s.getString("ldapHost") } func (s *SettingService) GetLdapPort() (int, error) { - return s.getInt("ldapPort") + return s.getInt("ldapPort") } func (s *SettingService) GetLdapUseTLS() (bool, error) { - return s.getBool("ldapUseTLS") + return s.getBool("ldapUseTLS") } func (s *SettingService) GetLdapBindDN() (string, error) { - return s.getString("ldapBindDN") + return s.getString("ldapBindDN") } func (s *SettingService) GetLdapPassword() (string, error) { - return s.getString("ldapPassword") + return s.getString("ldapPassword") } func (s *SettingService) GetLdapBaseDN() (string, error) { - return s.getString("ldapBaseDN") + return s.getString("ldapBaseDN") } func (s *SettingService) GetLdapUserFilter() (string, error) { - return s.getString("ldapUserFilter") + return s.getString("ldapUserFilter") } func (s *SettingService) GetLdapUserAttr() (string, error) { - return s.getString("ldapUserAttr") + return s.getString("ldapUserAttr") } func (s *SettingService) GetLdapVlessField() (string, error) { - return s.getString("ldapVlessField") + return s.getString("ldapVlessField") } func (s *SettingService) GetLdapSyncCron() (string, error) { - return s.getString("ldapSyncCron") + return s.getString("ldapSyncCron") } func (s *SettingService) GetLdapFlagField() (string, error) { - return s.getString("ldapFlagField") + return s.getString("ldapFlagField") } func (s *SettingService) GetLdapTruthyValues() (string, error) { - return s.getString("ldapTruthyValues") + return s.getString("ldapTruthyValues") } func (s *SettingService) GetLdapInvertFlag() (bool, error) { - return s.getBool("ldapInvertFlag") + return s.getBool("ldapInvertFlag") } func (s *SettingService) GetLdapInboundTags() (string, error) { - return s.getString("ldapInboundTags") + return s.getString("ldapInboundTags") } func (s *SettingService) GetLdapAutoCreate() (bool, error) { - return s.getBool("ldapAutoCreate") + return s.getBool("ldapAutoCreate") } func (s *SettingService) GetLdapAutoDelete() (bool, error) { - return s.getBool("ldapAutoDelete") + return s.getBool("ldapAutoDelete") } func (s *SettingService) GetLdapDefaultTotalGB() (int, error) { - return s.getInt("ldapDefaultTotalGB") + return s.getInt("ldapDefaultTotalGB") } func (s *SettingService) GetLdapDefaultExpiryDays() (int, error) { - return s.getInt("ldapDefaultExpiryDays") + return s.getInt("ldapDefaultExpiryDays") } func (s *SettingService) GetLdapDefaultLimitIP() (int, error) { - return s.getInt("ldapDefaultLimitIP") + return s.getInt("ldapDefaultLimitIP") } func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { diff --git a/web/service/user.go b/web/service/user.go index 87c46bf2..1bde69f6 100644 --- a/web/service/user.go +++ b/web/service/user.go @@ -7,7 +7,7 @@ import ( "github.com/mhsanaei/3x-ui/v2/database/model" "github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/util/crypto" - ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap" + ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap" "github.com/xlzd/gotp" "gorm.io/gorm" ) @@ -49,38 +49,38 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode return nil } - // If LDAP enabled and local password check fails, attempt LDAP auth - if !crypto.CheckPasswordHash(user.Password, password) { - ldapEnabled, _ := s.settingService.GetLdapEnable() - if !ldapEnabled { - return nil - } + // If LDAP enabled and local password check fails, attempt LDAP auth + if !crypto.CheckPasswordHash(user.Password, password) { + ldapEnabled, _ := s.settingService.GetLdapEnable() + if !ldapEnabled { + return nil + } - host, _ := s.settingService.GetLdapHost() - port, _ := s.settingService.GetLdapPort() - useTLS, _ := s.settingService.GetLdapUseTLS() - bindDN, _ := s.settingService.GetLdapBindDN() - ldapPass, _ := s.settingService.GetLdapPassword() - baseDN, _ := s.settingService.GetLdapBaseDN() - userFilter, _ := s.settingService.GetLdapUserFilter() - userAttr, _ := s.settingService.GetLdapUserAttr() + host, _ := s.settingService.GetLdapHost() + port, _ := s.settingService.GetLdapPort() + useTLS, _ := s.settingService.GetLdapUseTLS() + bindDN, _ := s.settingService.GetLdapBindDN() + ldapPass, _ := s.settingService.GetLdapPassword() + baseDN, _ := s.settingService.GetLdapBaseDN() + userFilter, _ := s.settingService.GetLdapUserFilter() + userAttr, _ := s.settingService.GetLdapUserAttr() - cfg := ldaputil.Config{ - Host: host, - Port: port, - UseTLS: useTLS, - BindDN: bindDN, - Password: ldapPass, - BaseDN: baseDN, - UserFilter: userFilter, - UserAttr: userAttr, - } - ok, err := ldaputil.AuthenticateUser(cfg, username, password) - if err != nil || !ok { - return nil - } - // On successful LDAP auth, continue 2FA checks below - } + cfg := ldaputil.Config{ + Host: host, + Port: port, + UseTLS: useTLS, + BindDN: bindDN, + Password: ldapPass, + BaseDN: baseDN, + UserFilter: userFilter, + UserAttr: userAttr, + } + ok, err := ldaputil.AuthenticateUser(cfg, username, password) + if err != nil || !ok { + return nil + } + // On successful LDAP auth, continue 2FA checks below + } twoFactorEnable, err := s.settingService.GetTwoFactorEnable() if err != nil { From 6e46e9b16e636ad399a52452d9ffecd6ce469d9c Mon Sep 17 00:00:00 2001 From: BOplaid <146466352+BOplaid@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:18:16 +0330 Subject: [PATCH 09/26] Improve English README (#3579) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d20850e..f00a2fb0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ **3X-UI** — advanced, open-source web-based control panel designed for managing Xray-core server. It offers a user-friendly interface for configuring and monitoring various VPN and proxy protocols. > [!IMPORTANT] -> This project is only for personal using, please do not use it for illegal purposes, please do not use it in a production environment. +> This project is only for personal usage, please do not use it for illegal purposes, and please do not use it in a production environment. As an enhanced fork of the original X-UI project, 3X-UI provides improved stability, broader protocol support, and additional features. From 020cd63e227c0015ef1b984ad909000e6fdd52e5 Mon Sep 17 00:00:00 2001 From: OleksandrParshyn <43094723+OleksandrParshyn@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:56:55 +0100 Subject: [PATCH 10/26] Fix: Graceful Telegram bot shutdown to prevent 409 Conflict (#3580) * Fix: Graceful Telegram bot shutdown to prevent 409 Conflict Introduces a `botCancel` context and a global `StopBot()` function to ensure the Telegram bot's Long Polling operation is safely terminated (via context cancellation) before the service restarts. This prevents the "Conflict: another update consumer is running" (409) error upon panel restart. Changes: - Added `botCancel context.CancelFunc` to manage context cancellation. - Implemented global `StopBot()` function. - Updated `Tgbot.Stop()` to call `StopBot()`. - Modified `Tgbot.OnReceive()` to use the new cancellable context for `UpdatesViaLongPolling`. * Fix: Prevent race condition and goroutine leak in TgBot Addresses a critical race condition on the global `botCancel` variable, which could occur if `Tgbot.OnReceive()` was called concurrently (e.g., during rapid panel restarts or unexpected behavior). Changes in tgbot.go: - Added `tgBotMutex sync.Mutex` to ensure thread safety. - Protected `botCancel` creation and assignment in `OnReceive()` using the mutex, and added a check to prevent overwriting an active context, which avoids goroutine leaks. - Protected the cancellation and cleanup logic in `StopBot()` with the mutex. * Refactor: Replace time.Sleep with sync.WaitGroup for reliable TgBot shutdown Replaced the unreliable `time.Sleep(1 * time.Second)` in `service.StopBot()` with `sync.WaitGroup`. This ensures the Long Polling goroutine is explicitly waited for and reliably exits before the panel continues, preventing potential resource leaks and incomplete shutdowns during restarts. Changes: - Added `botWG sync.WaitGroup` variable. - Updated `service.StopBot()` to call `botWG.Wait()` instead of `time.Sleep()`. - Modified `Tgbot.OnReceive()` to correctly use `botWG.Add(1)` and `defer botWG.Done()` within the Long Polling goroutine. - Corrected the goroutine structure in `OnReceive()` to properly encapsulate all message handling logic. --- web/service/tgbot.go | 393 ++++++++++++++++++++++++------------------- 1 file changed, 223 insertions(+), 170 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 0c9d820c..1573b2bf 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -38,7 +38,15 @@ import ( ) var ( - bot *telego.Bot + bot *telego.Bot + + // botCancel stores the function to cancel the context, stopping Long Polling gracefully. + botCancel context.CancelFunc + // tgBotMutex protects concurrent access to botCancel variable + tgBotMutex sync.Mutex + // botWG waits for the OnReceive Long Polling goroutine to finish. + botWG sync.WaitGroup + botHandler *th.BotHandler adminIds []int64 isRunning bool @@ -306,8 +314,13 @@ func (t *Tgbot) SetHostname() { hostname = host } -// Stop stops the Telegram bot and cleans up resources. +// Stop safely stops the Telegram bot's Long Polling operation. +// This method now calls the global StopBot function and cleans up other resources. func (t *Tgbot) Stop() { + // Call the global StopBot function to gracefully shut down Long Polling + StopBot() + + // Stop the bot handler (in case the goroutine hasn't exited yet) if botHandler != nil { botHandler.Stop() } @@ -316,6 +329,27 @@ func (t *Tgbot) Stop() { adminIds = nil } +// StopBot safely stops the Telegram bot's Long Polling operation by cancelling its context. +// This is the global function called from main.go's signal handler and t.Stop(). +func StopBot() { + tgBotMutex.Lock() + defer tgBotMutex.Unlock() + + if botCancel != nil { + logger.Info("Sending cancellation signal to Telegram bot...") + + // Calling botCancel() cancels the context passed to UpdatesViaLongPolling, + // which stops the Long Polling operation and closes the updates channel, + // allowing the th.Start() goroutine to exit cleanly. + botCancel() + + botCancel = nil + // Giving the goroutine a small delay to exit cleanly. + botWG.Wait() + logger.Info("Telegram bot successfully stopped.") + } +} + // encodeQuery encodes the query string if it's longer than 64 characters. func (t *Tgbot) encodeQuery(query string) string { // NOTE: we only need to hash for more than 64 chars @@ -345,188 +379,207 @@ func (t *Tgbot) OnReceive() { params := telego.GetUpdatesParams{ Timeout: 30, // Increased timeout to reduce API calls } + // --- GRACEFUL SHUTDOWN FIX: Context creation --- + tgBotMutex.Lock() - updates, _ := bot.UpdatesViaLongPolling(context.Background(), ¶ms) + // Create a context with cancellation and store the cancel function. + var ctx context.Context - botHandler, _ = th.NewBotHandler(bot, updates) + // Check if botCancel is already set (to prevent race condition overwrite and goroutine leak) + if botCancel == nil { + ctx, botCancel = context.WithCancel(context.Background()) + } else { + // If botCancel is already set, use a non-cancellable context for this redundant call. + // This prevents overwriting the active botCancel and causing a goroutine leak from the previous call. + logger.Warning("TgBot OnReceive called concurrently. Using background context for redundant call.") + ctx = context.Background() // <<< ИЗМЕНЕНИЕ + } - botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error { - delete(userStates, message.Chat.ID) - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove()) - return nil - }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard"))) + tgBotMutex.Unlock() - botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error { - // Use goroutine with worker pool for concurrent command processing - go func() { - messageWorkerPool <- struct{}{} // Acquire worker - defer func() { <-messageWorkerPool }() // Release worker + // Get updates channel using the context. + updates, _ := bot.UpdatesViaLongPolling(ctx, ¶ms) + botWG.Go(func() { + botHandler, _ = th.NewBotHandler(bot, updates) + botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error { delete(userStates, message.Chat.ID) - t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID)) - }() - return nil - }, th.AnyCommand()) + t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove()) + return nil + }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard"))) - botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error { - // Use goroutine with worker pool for concurrent callback processing - go func() { - messageWorkerPool <- struct{}{} // Acquire worker - defer func() { <-messageWorkerPool }() // Release worker + botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error { + // Use goroutine with worker pool for concurrent command processing + go func() { + messageWorkerPool <- struct{}{} // Acquire worker + defer func() { <-messageWorkerPool }() // Release worker - delete(userStates, query.Message.GetChat().ID) - t.answerCallback(&query, checkAdmin(query.From.ID)) - }() - return nil - }, th.AnyCallbackQueryWithMessage()) - - botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error { - if userState, exists := userStates[message.Chat.ID]; exists { - switch userState { - case "awaiting_id": - if client_Id == strings.TrimSpace(message.Text) { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) - message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) - t.addClient(message.Chat.ID, message_text) - return nil - } - - client_Id = strings.TrimSpace(message.Text) - if t.isSingleWord(client_Id) { - userStates[message.Chat.ID] = "awaiting_id" - - cancel_btn_markup := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), - ), - ) - - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) - } else { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_id"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) - message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) - t.addClient(message.Chat.ID, message_text) - } - case "awaiting_password_tr": - if client_TrPassword == strings.TrimSpace(message.Text) { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - return nil - } - - client_TrPassword = strings.TrimSpace(message.Text) - if t.isSingleWord(client_TrPassword) { - userStates[message.Chat.ID] = "awaiting_password_tr" - - cancel_btn_markup := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), - ), - ) - - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) - } else { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_password"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) - message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) - t.addClient(message.Chat.ID, message_text) - } - case "awaiting_password_sh": - if client_ShPassword == strings.TrimSpace(message.Text) { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - return nil - } - - client_ShPassword = strings.TrimSpace(message.Text) - if t.isSingleWord(client_ShPassword) { - userStates[message.Chat.ID] = "awaiting_password_sh" - - cancel_btn_markup := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), - ), - ) - - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) - } else { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_password"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) - message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) - t.addClient(message.Chat.ID, message_text) - } - case "awaiting_email": - if client_Email == strings.TrimSpace(message.Text) { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - return nil - } - - client_Email = strings.TrimSpace(message.Text) - if t.isSingleWord(client_Email) { - userStates[message.Chat.ID] = "awaiting_email" - - cancel_btn_markup := tu.InlineKeyboard( - tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), - ), - ) - - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) - } else { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_email"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) - message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) - t.addClient(message.Chat.ID, message_text) - } - case "awaiting_comment": - if client_Comment == strings.TrimSpace(message.Text) { - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) - delete(userStates, message.Chat.ID) - return nil - } - - client_Comment = strings.TrimSpace(message.Text) - t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_comment"), 3, tu.ReplyKeyboardRemove()) delete(userStates, message.Chat.ID) - inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) - message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) - t.addClient(message.Chat.ID, message_text) - } + t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID)) + }() + return nil + }, th.AnyCommand()) - } else { - if message.UsersShared != nil { - if checkAdmin(message.From.ID) { - for _, sharedUser := range message.UsersShared.Users { - userID := sharedUser.UserID - needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID) - if needRestart { - t.xrayService.SetToNeedRestart() - } - output := "" - if err != nil { - output += t.I18nBot("tgbot.messages.selectUserFailed") - } else { - output += t.I18nBot("tgbot.messages.userSaved") - } - t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove()) + botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error { + // Use goroutine with worker pool for concurrent callback processing + go func() { + messageWorkerPool <- struct{}{} // Acquire worker + defer func() { <-messageWorkerPool }() // Release worker + + delete(userStates, query.Message.GetChat().ID) + t.answerCallback(&query, checkAdmin(query.From.ID)) + }() + return nil + }, th.AnyCallbackQueryWithMessage()) + + botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error { + if userState, exists := userStates[message.Chat.ID]; exists { + switch userState { + case "awaiting_id": + if client_Id == strings.TrimSpace(message.Text) { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) + message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + t.addClient(message.Chat.ID, message_text) + return nil + } + + client_Id = strings.TrimSpace(message.Text) + if t.isSingleWord(client_Id) { + userStates[message.Chat.ID] = "awaiting_id" + + cancel_btn_markup := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), + ), + ) + + t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) + } else { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_id"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) + message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + t.addClient(message.Chat.ID, message_text) + } + case "awaiting_password_tr": + if client_TrPassword == strings.TrimSpace(message.Text) { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + return nil + } + + client_TrPassword = strings.TrimSpace(message.Text) + if t.isSingleWord(client_TrPassword) { + userStates[message.Chat.ID] = "awaiting_password_tr" + + cancel_btn_markup := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), + ), + ) + + t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) + } else { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_password"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) + message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + t.addClient(message.Chat.ID, message_text) + } + case "awaiting_password_sh": + if client_ShPassword == strings.TrimSpace(message.Text) { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + return nil + } + + client_ShPassword = strings.TrimSpace(message.Text) + if t.isSingleWord(client_ShPassword) { + userStates[message.Chat.ID] = "awaiting_password_sh" + + cancel_btn_markup := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), + ), + ) + + t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) + } else { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_password"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) + message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + t.addClient(message.Chat.ID, message_text) + } + case "awaiting_email": + if client_Email == strings.TrimSpace(message.Text) { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + return nil + } + + client_Email = strings.TrimSpace(message.Text) + if t.isSingleWord(client_Email) { + userStates[message.Chat.ID] = "awaiting_email" + + cancel_btn_markup := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("add_client_default_info"), + ), + ) + + t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.incorrect_input"), cancel_btn_markup) + } else { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_email"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) + message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + t.addClient(message.Chat.ID, message_text) + } + case "awaiting_comment": + if client_Comment == strings.TrimSpace(message.Text) { + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + return nil + } + + client_Comment = strings.TrimSpace(message.Text) + t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.received_comment"), 3, tu.ReplyKeyboardRemove()) + delete(userStates, message.Chat.ID) + inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID) + message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + t.addClient(message.Chat.ID, message_text) + } + + } else { + if message.UsersShared != nil { + if checkAdmin(message.From.ID) { + for _, sharedUser := range message.UsersShared.Users { + userID := sharedUser.UserID + needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID) + if needRestart { + t.xrayService.SetToNeedRestart() + } + output := "" + if err != nil { + output += t.I18nBot("tgbot.messages.selectUserFailed") + } else { + output += t.I18nBot("tgbot.messages.userSaved") + } + t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove()) + } + } else { + t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove()) } - } else { - t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove()) } } - } - return nil - }, th.AnyMessage()) + return nil + }, th.AnyMessage()) - botHandler.Start() + botHandler.Start() + }) } // answerCommand processes incoming command messages from Telegram users. From 313b51f96f2f94de9908c6900988a7d1b170f08f Mon Sep 17 00:00:00 2001 From: Denis Gorelov <86232599+RFOTQ@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:07:05 +0300 Subject: [PATCH 11/26] feat: Add random Reality Target/SNI selection from 52 popular services (#3577) * feat: Add random Reality Target/SNI selection from 52 popular services - Created reality_targets.js with list of 52 popular services - Updated RealityStreamSettings to use random targets by default - Added UI randomize buttons with sync icon in Reality settings form - Implemented randomizeRealityTarget() method in inbound modal - Replaces hardcoded google.com with diverse global services * fix --------- Co-authored-by: mhsanaei --- web/assets/js/model/inbound.js | 12 +++- web/assets/js/model/reality_targets.js | 86 ++++++++++++++++++++++++++ web/html/form/reality_settings.html | 20 +++++- web/html/inbounds.html | 1 + web/html/modals/inbound_modal.html | 7 +++ 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 web/assets/js/model/reality_targets.js diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index aecedf75..0718b20c 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -729,8 +729,8 @@ class RealityStreamSettings extends XrayCommonClass { constructor( show = false, xver = 0, - target = 'google.com:443', - serverNames = 'google.com,www.google.com', + target = '', + serverNames = '', privateKey = '', minClientVer = '', maxClientVer = '', @@ -740,6 +740,14 @@ class RealityStreamSettings extends XrayCommonClass { settings = new RealityStreamSettings.Settings() ) { super(); + // If target/serverNames are not provided, use random values + if (!target && !serverNames) { + const randomTarget = typeof getRandomRealityTarget !== 'undefined' + ? getRandomRealityTarget() + : { target: 'google.com:443', sni: 'google.com,www.google.com' }; + target = randomTarget.target; + serverNames = randomTarget.sni; + } this.show = show; this.xver = xver; this.target = target; diff --git a/web/assets/js/model/reality_targets.js b/web/assets/js/model/reality_targets.js new file mode 100644 index 00000000..348ff296 --- /dev/null +++ b/web/assets/js/model/reality_targets.js @@ -0,0 +1,86 @@ +// List of popular services for VLESS Reality Target/SNI randomization +const REALITY_TARGETS = [ + // CDN & Cloud Infrastructure + { target: 'www.cloudflare.com:443', sni: 'www.cloudflare.com,cloudflare.com' }, + { target: 'www.microsoft.com:443', sni: 'www.microsoft.com,microsoft.com' }, + { target: 'www.apple.com:443', sni: 'www.apple.com,apple.com' }, + { target: 'www.amazon.com:443', sni: 'www.amazon.com,amazon.com' }, + { target: 'cloud.google.com:443', sni: 'cloud.google.com,www.google.com' }, + { target: 'azure.microsoft.com:443', sni: 'azure.microsoft.com,www.azure.com' }, + { target: 'aws.amazon.com:443', sni: 'aws.amazon.com,amazon.com' }, + { target: 'www.digitalocean.com:443', sni: 'www.digitalocean.com,digitalocean.com' }, + + // Social Media + { target: 'www.facebook.com:443', sni: 'www.facebook.com,facebook.com' }, + { target: 'www.instagram.com:443', sni: 'www.instagram.com,instagram.com' }, + { target: 'www.twitter.com:443', sni: 'www.twitter.com,twitter.com' }, + { target: 'www.linkedin.com:443', sni: 'www.linkedin.com,linkedin.com' }, + { target: 'www.reddit.com:443', sni: 'www.reddit.com,reddit.com' }, + { target: 'www.pinterest.com:443', sni: 'www.pinterest.com,pinterest.com' }, + { target: 'www.tumblr.com:443', sni: 'www.tumblr.com,tumblr.com' }, + + // Video & Streaming + { target: 'www.youtube.com:443', sni: 'www.youtube.com,youtube.com' }, + { target: 'www.netflix.com:443', sni: 'www.netflix.com,netflix.com' }, + { target: 'www.twitch.tv:443', sni: 'www.twitch.tv,twitch.tv' }, + { target: 'vimeo.com:443', sni: 'vimeo.com,www.vimeo.com' }, + { target: 'www.hulu.com:443', sni: 'www.hulu.com,hulu.com' }, + { target: 'www.disneyplus.com:443', sni: 'www.disneyplus.com,disneyplus.com' }, + + // News & Media + { target: 'www.bbc.com:443', sni: 'www.bbc.com,bbc.com' }, + { target: 'www.cnn.com:443', sni: 'www.cnn.com,cnn.com' }, + { target: 'www.nytimes.com:443', sni: 'www.nytimes.com,nytimes.com' }, + { target: 'www.theguardian.com:443', sni: 'www.theguardian.com,theguardian.com' }, + { target: 'www.reuters.com:443', sni: 'www.reuters.com,reuters.com' }, + { target: 'www.bloomberg.com:443', sni: 'www.bloomberg.com,bloomberg.com' }, + + // E-commerce + { target: 'www.ebay.com:443', sni: 'www.ebay.com,ebay.com' }, + { target: 'www.alibaba.com:443', sni: 'www.alibaba.com,alibaba.com' }, + { target: 'www.shopify.com:443', sni: 'www.shopify.com,shopify.com' }, + { target: 'www.walmart.com:443', sni: 'www.walmart.com,walmart.com' }, + { target: 'www.target.com:443', sni: 'www.target.com,target.com' }, + + // Tech Companies + { target: 'www.github.com:443', sni: 'www.github.com,github.com' }, + { target: 'www.stackoverflow.com:443', sni: 'www.stackoverflow.com,stackoverflow.com' }, + { target: 'www.gitlab.com:443', sni: 'www.gitlab.com,gitlab.com' }, + { target: 'www.docker.com:443', sni: 'www.docker.com,docker.com' }, + { target: 'www.nvidia.com:443', sni: 'www.nvidia.com,nvidia.com' }, + { target: 'www.intel.com:443', sni: 'www.intel.com,intel.com' }, + { target: 'www.amd.com:443', sni: 'www.amd.com,amd.com' }, + + // Communication & Productivity + { target: 'www.zoom.us:443', sni: 'www.zoom.us,zoom.us' }, + { target: 'slack.com:443', sni: 'slack.com,www.slack.com' }, + { target: 'www.dropbox.com:443', sni: 'www.dropbox.com,dropbox.com' }, + { target: 'www.notion.so:443', sni: 'www.notion.so,notion.so' }, + { target: 'www.atlassian.com:443', sni: 'www.atlassian.com,atlassian.com' }, + { target: 'www.salesforce.com:443', sni: 'www.salesforce.com,salesforce.com' }, + + // Search & General + { target: 'www.wikipedia.org:443', sni: 'www.wikipedia.org,wikipedia.org' }, + { target: 'www.bing.com:443', sni: 'www.bing.com,bing.com' }, + { target: 'www.yahoo.com:443', sni: 'www.yahoo.com,yahoo.com' }, + { target: 'www.duckduckgo.com:443', sni: 'www.duckduckgo.com,duckduckgo.com' }, + + // Gaming + { target: 'store.steampowered.com:443', sni: 'store.steampowered.com,steampowered.com' }, + { target: 'www.ea.com:443', sni: 'www.ea.com,ea.com' }, + { target: 'www.epicgames.com:443', sni: 'www.epicgames.com,epicgames.com' }, +]; + +/** + * Returns a random Reality target configuration from the predefined list + * @returns {Object} Object with target and sni properties + */ +function getRandomRealityTarget() { + const randomIndex = Math.floor(Math.random() * REALITY_TARGETS.length); + const selected = REALITY_TARGETS[randomIndex]; + // Return a copy to avoid reference issues + return { + target: selected.target, + sni: selected.sni + }; +} diff --git a/web/html/form/reality_settings.html b/web/html/form/reality_settings.html index 218ba86d..29170f03 100644 --- a/web/html/form/reality_settings.html +++ b/web/html/form/reality_settings.html @@ -12,10 +12,26 @@ [[ key ]] - + + - + + diff --git a/web/html/inbounds.html b/web/html/inbounds.html index 2ab00f09..8616dce5 100644 --- a/web/html/inbounds.html +++ b/web/html/inbounds.html @@ -602,6 +602,7 @@ {{template "page/body_scripts" .}} + {{template "component/aSidebar" .}} diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html index 176f2eee..3c844381 100644 --- a/web/html/modals/inbound_modal.html +++ b/web/html/modals/inbound_modal.html @@ -158,6 +158,13 @@ this.inbound.stream.reality.mldsa65Seed = ''; this.inbound.stream.reality.settings.mldsa65Verify = ''; }, + randomizeRealityTarget() { + if (typeof getRandomRealityTarget !== 'undefined') { + const randomTarget = getRandomRealityTarget(); + this.inbound.stream.reality.target = randomTarget.target; + this.inbound.stream.reality.serverNames = randomTarget.sni; + } + }, async getNewEchCert() { inModal.loading(true); const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni }); From b0c223c63126a576dffa83a16277fbd95af2db41 Mon Sep 17 00:00:00 2001 From: Rashid Yusubov <123075706+rashid-yusubov@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:07:49 +0300 Subject: [PATCH 12/26] fix: improve russian localization (#3576) * fix: improve russian localization * fix: updating the Russian translation according to the suggestions --- web/translation/translate.ru_RU.toml | 144 +++++++++++++-------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 3416e7fa..847b718e 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -32,7 +32,7 @@ "copySuccess" = "Скопировано" "sure" = "Да" "encryption" = "Шифрование" -"useIPv4ForHost" = "Использовать IPv4 для хоста" +"useIPv4ForHost" = "Использовать IPv4 для подключения к хосту" "transmission" = "Транспорт" "host" = "Хост" "path" = "Путь" @@ -46,8 +46,8 @@ "online" = "Онлайн" "domainName" = "Домен" "monitor" = "Мониторинг IP" -"certificate" = "SSL сертификат" -"fail" = "Ошибка" +"certificate" = "SSL-сертификат" +"fail" = "Сбой" "comment" = "Комментарий" "success" = "Успешно" "lastOnline" = "Был(а) в сети" @@ -55,17 +55,17 @@ "install" = "Установка" "clients" = "Клиенты" "usage" = "Использование" -"twoFactorCode" = "Код" +"twoFactorCode" = "Код 2FA" "remained" = "Остаток" "security" = "Безопасность" "secAlertTitle" = "Предупреждение системы безопасности" -"secAlertSsl" = "Это соединение не защищено. Пожалуйста, не вводите конфиденциальную информацию, пока не установите SSL сертификат для защиты соединения" -"secAlertConf" = "Некоторые настройки уязвимы для атак. Чтобы в будущем не было проблем, нужно усилить защиту." -"secAlertSSL" = "Ваше подключение к панели не защищено. Установите SSL сертификат для защиты данных." -"secAlertPanelPort" = "Порт панели по умолчанию небезопасен. Установите случайный или просто другой порт." -"secAlertPanelURI" = "Адрес панели по умолчанию небезопасен. Сделайте адрес сложным." -"secAlertSubURI" = "URI-адрес подписки по умолчанию небезопасен. Пожалуйста, настройте сложный URI-адрес." -"secAlertSubJsonURI" = "URI-адрес по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-адрес." +"secAlertSsl" = "Соединение не защищено. Не вводите конфиденциальные данные до установки SSL-сертификата." +"secAlertConf" = "Некоторые настройки уязвимы. Рекомендуется усилить защиту для предотвращения атак." +"secAlertSSL" = "Подключение к панели не защищено. Установите SSL-сертификат для защиты данных." +"secAlertPanelPort" = "Порт панели по умолчанию небезопасен. Установите нестандартный или случайный порт." +"secAlertPanelURI" = "Адрес панели по умолчанию небезопасен. Настройте уникальный и сложный URI." +"secAlertSubURI" = "URI подписки по умолчанию небезопасен. Настройте уникальный и сложный адрес." +"secAlertSubJsonURI" = "URI JSON-подписки по умолчанию небезопасен. Настройте уникальный и сложный адрес." "emptyDnsDesc" = "Нет добавленных DNS-серверов." "emptyFakeDnsDesc" = "Нет добавленных Fake DNS-серверов." "emptyBalancersDesc" = "Нет добавленных балансировщиков." @@ -83,15 +83,15 @@ "individualLinks" = "Индивидуальные ссылки" "active" = "Активна" "inactive" = "Неактивна" -"unlimited" = "Безлимит" -"noExpiry" = "Без срока" +"unlimited" = "Неограниченно" +"noExpiry" = "Бессрочно" [menu] "theme" = "Тема" "dark" = "Темная" "ultraDark" = "Очень темная" "dashboard" = "Дашборд" -"inbounds" = "Инбаунды" +"inbounds" = "Подключения" "settings" = "Настройки" "xray" = "Настройки Xray" "logout" = "Выход" @@ -107,7 +107,7 @@ "emptyUsername" = "Введите имя пользователя" "emptyPassword" = "Введите пароль" "wrongUsernameOrPassword" = "Неверные данные учетной записи." -"successLogin" = "Вы успешно вошли в аккаунт" +"successLogin" = "Вход выполнен успешно" [pages.index] "title" = "Дашборд" @@ -122,7 +122,7 @@ "stopXray" = "Остановить" "restartXray" = "Перезапустить" "xraySwitch" = "Выбор версии" -"xraySwitchClick" = "Выберите желаемую версию" +"xraySwitchClick" = "Выберите нужную версию" "xraySwitchClickDesk" = "Важно: старые версии могут не поддерживать текущие настройки" "xrayStatusUnknown" = "Неизвестно" "xrayStatusRunning" = "Запущен" @@ -134,7 +134,7 @@ "systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут" "connectionCount" = "Количество соединений" "ipAddresses" = "IP-адреса сервера" -"toggleIpVisibility" = "Переключить видимость IP-адресов сервера" +"toggleIpVisibility" = "Скрыть или показать IP-адреса сервера" "overallSpeed" = "Общая скорость передачи трафика" "upload" = "Отправка" "download" = "Загрузка" @@ -168,10 +168,10 @@ [pages.inbounds] "allTimeTraffic" = "Общий трафик" "allTimeTrafficUsage" = "Общее использование за все время" -"title" = "Инбаунды" -"totalDownUp" = "Объем отправленного/полученного трафика" +"title" = "Подключения" +"totalDownUp" = "Отправлено/получено" "totalUsage" = "Всего трафика" -"inboundCount" = "Всего инбаундов" +"inboundCount" = "Всего подключений" "operate" = "Меню" "enable" = "Включить" "remark" = "Примечание" @@ -185,13 +185,13 @@ "createdAt" = "Создано" "updatedAt" = "Обновлено" "resetTraffic" = "Сброс трафика" -"addInbound" = "Создать инбаунд" +"addInbound" = "Создать подключение" "generalActions" = "Общие действия" "autoRefresh" = "Автообновление" "autoRefreshInterval" = "Интервал" -"modifyInbound" = "Изменить инбаунд" -"deleteInbound" = "Удалить инбаунд" -"deleteInboundContent" = "Вы уверены, что хотите удалить инбаунд?" +"modifyInbound" = "Изменить подключение" +"deleteInbound" = "Удалить подключение" +"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?" "deleteClient" = "Удалить клиента" "deleteClientContent" = "Вы уверены, что хотите удалить клиента?" "resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?" @@ -214,11 +214,11 @@ "export" = "Экспорт ссылок" "clone" = "Клонировать" "cloneInbound" = "Клонировать" -"cloneInboundContent" = "Будут клонированы все настройки инбаундов, кроме списка клиентов, порта и IP-адреса прослушивания" +"cloneInboundContent" = "Будут клонированы все настройки подключений, кроме списка клиентов, порта и IP-адреса прослушивания" "cloneInboundOk" = "Клонировано" -"resetAllTraffic" = "Сброс трафика всех инбаундов" -"resetAllTrafficTitle" = "Сброс трафика всех инбаундов" -"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех инбаундов?" +"resetAllTraffic" = "Сброс трафика всех подключений" +"resetAllTrafficTitle" = "Сброс трафика всех подключений" +"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?" "resetInboundClientTraffics" = "Сброс трафика клиента" "resetInboundClientTrafficTitle" = "Сброс трафика клиентов" "resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для этих клиентов?" @@ -231,7 +231,7 @@ "email" = "Email" "emailDesc" = "Пожалуйста, укажите уникальный Email" "IPLimit" = "Лимит по количеству IP" -"IPLimitDesc" = "Ограничение количества одновременных подключений с разных IP(0 – отключить)" +"IPLimitDesc" = "Ограничение числа одновременных подключений с разных IP (0 – отключить)" "IPLimitlog" = "Лог IP-адресов" "IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить лог)" "IPLimitlogclear" = "Очистить лог" @@ -240,19 +240,19 @@ "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее'" "info" = "Информация" "same" = "Тот же" -"inboundData" = "Данные инбаундов" -"exportInbound" = "Экспорт инбаундов" +"inboundData" = "Данные подключений" +"exportInbound" = "Экспорт подключений" "import" = "Импортировать" -"importInbound" = "Импорт инбаундов" +"importInbound" = "Импорт подключений" "periodicTrafficResetTitle" = "Сброс трафика" "periodicTrafficResetDesc" = "Автоматический сброс счетчика трафика через указанные интервалы" "lastReset" = "Последний сброс" [pages.client] -"add" = "Создать клиента" +"add" = "Добавить клиента" "edit" = "Редактировать клиента" "submitAdd" = "Добавить" -"submitEdit" = "Сохранить" +"submitEdit" = "Сохранить изменения" "clientCount" = "Количество клиентов" "bulk" = "Добавить несколько" "method" = "Метод" @@ -276,13 +276,13 @@ "obtain" = "Получить" "updateSuccess" = "Обновление прошло успешно" "logCleanSuccess" = "Лог был очищен" -"inboundsUpdateSuccess" = "Инбаунды успешно обновлены" -"inboundUpdateSuccess" = "Инбаунд успешно обновлено" -"inboundCreateSuccess" = "Инбаунд успешно создано" -"inboundDeleteSuccess" = "Инбаунд успешно удалено" -"inboundClientAddSuccess" = "Клиент(ы) инбаунда добавлен(ы)" -"inboundClientDeleteSuccess" = "Клиент инбаунда удалён" -"inboundClientUpdateSuccess" = "Клиент инбаунда обновлён" +"inboundsUpdateSuccess" = "Подключения успешно обновлены" +"inboundUpdateSuccess" = "Подключение успешно обновлено" +"inboundCreateSuccess" = "Подключение успешно создано" +"inboundDeleteSuccess" = "Подключение успешно удалено" +"inboundClientAddSuccess" = "Клиент(ы) подключения добавлен(ы)" +"inboundClientDeleteSuccess" = "Клиент подключения удалён" +"inboundClientUpdateSuccess" = "Клиент подключения обновлён" "delDepletedClientsSuccess" = "Все исчерпанные клиенты удалены" "resetAllClientTrafficSuccess" = "Весь трафик клиента сброшен" "resetAllTrafficSuccess" = "Весь трафик сброшен" @@ -310,7 +310,7 @@ [pages.settings] "title" = "Настройки" "save" = "Сохранить" -"infoDesc" = "Каждое внесённое изменение должно быть сохранено. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу." +"infoDesc" = "Сохраните изменения и перезапустите панель для их применения." "restartPanel" = "Перезапуск панели" "restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель будет недоступна, проверьте лог сервера" "restartPanelSuccess" = "Панель успешно перезапущена" @@ -318,11 +318,11 @@ "resetDefaultConfig" = "Восстановить настройки по умолчанию" "panelSettings" = "Панель" "securitySettings" = "Учетная запись" -"TGBotSettings" = "Telegram" +"TGBotSettings" = "Telegram-Бот" "panelListeningIP" = "IP-адрес для управления панелью" "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" "panelListeningDomain" = "Домен панели" -"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы подключаться с любых доменов и IP-адресов" +"panelListeningDomainDesc" = "Оставьте пустым для подключения с любых доменов и IP." "panelPort" = "Порт панели" "panelPortDesc" = "Порт, на котором работает панель" "publicKeyPath" = "Путь к файлу публичного ключа сертификата панели" @@ -332,11 +332,11 @@ "panelUrlPath" = "Корневой путь URL адреса панели" "panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться '/'" "pageSize" = "Размер нумерации страниц" -"pageSizeDesc" = "Определить размер страницы для таблицы инбаундов. Установите 0, чтобы отключить" +"pageSizeDesc" = "Определить размер страницы для таблицы подключений. Установите 0, чтобы отключить" "remarkModel" = "Модель примечания и символ разделения" -"datepicker" = "Выбор даты" +"datepicker" = "Тип календаря" "datepickerPlaceholder" = "Выберите дату" -"datepickerDescription" = "Запланированные задачи будут выполняться в выбранное время" +"datepickerDescription" = "Запланированные задачи будут выполняться в соответствии с этим календарем." "sampleRemark" = "Пример примечания" "oldUsername" = "Текущий логин" "currentPassword" = "Текущий пароль" @@ -346,7 +346,7 @@ "telegramBotEnableDesc" = "Доступ к функциям панели через Telegram-бота" "telegramToken" = "Токен Telegram бота" "telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" -"telegramProxy" = "Прокси Socks5" +"telegramProxy" = "Прокси-сервер Socks5" "telegramProxyDesc" = "Если для подключения к Telegram вам нужен прокси Socks5, настройте его параметры согласно руководству." "telegramAPIServer" = "API-сервер Telegram" "telegramAPIServerDesc" = "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию." @@ -451,11 +451,11 @@ "RoutingStrategy" = "Настройка маршрутизации доменов" "RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS" "Torrent" = "Заблокировать BitTorrent" -"Inbounds" = "Инбаунды" +"Inbounds" = "Входящие подключения" "InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных клиентов" -"Outbounds" = "Аутбаунды" +"Outbounds" = "Исходящие подключения" "Balancers" = "Балансировщик" -"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить аутбаунды для этого сервера" +"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие подключения для этого сервера" "Routings" = "Маршрутизация" "RoutingsDesc" = "Важен приоритет каждого правила!" "completeTemplate" = "Все" @@ -486,8 +486,8 @@ "down" = "Опустить вниз" "source" = "Источник" "dest" = "Пункт назначения" -"inbound" = "Инбаунд" -"outbound" = "Аутбаунд" +"inbound" = "Входящее подключение" +"outbound" = "Исходящее подключение" "balancer" = "Балансировщик" "info" = "Информация" "add" = "Создать правило" @@ -495,9 +495,9 @@ "useComma" = "Элементы, разделённые запятыми" [pages.xray.outbound] -"addOutbound" = "Создать аутбаунд" +"addOutbound" = "Создать исходящее подключение" "addReverse" = "Создать реверс-прокси" -"editOutbound" = "Изменить аутбаунд" +"editOutbound" = "Изменить исходящее подключение" "editReverse" = "Редактировать реверс-прокси" "tag" = "Тег" "tagDesc" = "Уникальный тег" @@ -511,7 +511,7 @@ "intercon" = "Соединение" "settings" = "Настройки" "accountInfo" = "Информация об учетной записи" -"outboundStatus" = "Статус аутбаунда" +"outboundStatus" = "Статус исходящего подключения" "sendThrough" = "Отправить через" [pages.xray.balancer] @@ -587,8 +587,8 @@ "modifyUser" = "Вы успешно изменили учетные данные администратора." "originalUserPassIncorrect" = "Неверное имя пользователя или пароль" "userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены" -"getOutboundTrafficError" = "Ошибка получения трафика аутбаунда" -"resetOutboundTrafficError" = "Ошибка сброса трафика аутбаунда" +"getOutboundTrafficError" = "Ошибка получения трафика исходящего подключения" +"resetOutboundTrafficError" = "Ошибка сброса трафика исходящего подключения" [tgbot] "keyboardClosed" = "❌ Клавиатура закрыта." @@ -596,7 +596,7 @@ "noQuery" = "❌ Запрос не найден. Пожалуйста, повторите команду." "wentWrong" = "❌ Что-то пошло не так..." "noIpRecord" = "❗ Нет записей об IP-адресе." -"noInbounds" = "❗ У вас не настроено ни одного инбаунда." +"noInbounds" = "❗ У вас не настроено ни одного входящего подключения." "unlimited" = "♾ Безлимит" "add" = "Добавить" "month" = "Месяц" @@ -606,7 +606,7 @@ "hours" = "Часов" "minutes" = "Минуты" "unknown" = "Неизвестно" -"inbounds" = "Инбаунды" +"inbounds" = "Входящие подключения" "clients" = "Клиенты" "offline" = "🔴 Офлайн" "online" = "🟢 Онлайн" @@ -620,7 +620,7 @@ "status" = "✅ Бот функционирует нормально." "usage" = "❗ Пожалуйста, укажите email для поиска." "getID" = "🆔 Ваш User ID: {{ .ID }}" -"helpAdminCommands" = "🔃 Для перезапуска Xray Core:\r\n/restart\r\n\r\n🔎 Для поиска клиента по email:\r\n/usage [Email]\r\n\r\n📊 Для поиска инбаундов (со статистикой клиентов):\r\n/inbound [имя подключения]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id" +"helpAdminCommands" = "🔃 Для перезапуска Xray Core:\r\n/restart\r\n\r\n🔎 Для поиска клиента по email:\r\n/usage [Email]\r\n\r\n📊 Для поиска входящих подключений (со статистикой клиентов):\r\n/inbound [имя подключения]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id" "helpClientCommands" = "💲 Для просмотра информации о вашей подписке используйте команду:\r\n/usage [Email]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id" "restartUsage" = "\r\n\r\n/restart" "restartSuccess" = "✅ Ядро Xray успешно перезапущено." @@ -656,7 +656,7 @@ "username" = "👤 Имя пользователя: {{ .Username }}\r\n" "password" = "👤 Пароль: {{ .Password }}\r\n" "time" = "⏰ Время: {{ .Time }}\r\n" -"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n" +"inbound" = "📍 Входящее подключение: {{ .Remark }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n" "expire" = "📅 Дата окончания: {{ .Time }}\r\n" "expireIn" = "📅 Окончание через: {{ .Time }}\r\n" @@ -685,12 +685,12 @@ "pass_prompt" = "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль." "email_prompt" = "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email." "comment_prompt" = "💬 Стандартный комментарий: {{ .ClientComment }}\n\nВведите ваш комментарий." -"inbound_client_data_id" = "🔄 Инбаунды: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Дата исчерпания: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в инбаунд!" -"inbound_client_data_pass" = "🔄 Инбаунды: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Дата исчерпания: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в инбаунд!" +"inbound_client_data_id" = "🔄 Входящие подключения: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Срок действия: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в входящее подключение!" +"inbound_client_data_pass" = "🔄 Входящие подключения: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Срок действия: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в входящее подключение!" "cancel" = "❌ Процесс отменён! \n\nВы можете снова начать с /start в любое время. 🔄" -"error_add_client" = "⚠️ Ошибка:\n\n {{ .error }}" -"using_default_value" = "Используется значение по умолчанию👌" -"incorrect_input" ="Ваш ввод недействителен.\nФразы должны быть непрерывными без пробелов.\nПравильный пример: aaaaaa\nНеправильный пример: aaa aaa 🚫" +"error_add_client" = "⚠️ Ошибка:\n\n {{ .error }}" +"using_default_value" = "Используется значение по умолчанию👌" +"incorrect_input" = "Ваш ввод недействителен.\nФразы должны быть непрерывными без пробелов.\nПравильный пример: aaaaaa\nНеправильный пример: aaa aaa 🚫" "AreYouSure" = "Вы уверены? 🤔" "SuccessResetTraffic" = "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ✅ Успешно" "FailedResetTraffic" = "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ❌ Неудача \n\n🛠️ Ошибка: [ {{ .ErrorMessage }} ]" @@ -707,7 +707,7 @@ "confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?" "dbBackup" = "📂 Бэкап БД" "serverUsage" = "💻 Состояние сервера" -"getInbounds" = "🔌 Инбаунды" +"getInbounds" = "🔌 Входящие подключения" "depleteSoon" = "⚠️ Скоро конец" "clientUsage" = "Статистика клиента" "onlines" = "🟢 Онлайн" @@ -731,7 +731,7 @@ "allClients" = "👥 Все клиенты" "addClient" = "➕ Новый клиент" "submitDisable" = "Добавить отключенным ☑️" -"submitEnable" = "Добавить включенныи ✅" +"submitEnable" = "Добавить включенным ✅" "use_default" = "🏷️ Использовать по умолчанию" "change_id" = "⚙️🔑 ID" "change_password" = "⚙️🔑 Пароль" @@ -743,7 +743,7 @@ [tgbot.answers] "successfulOperation" = "✅ Успешно!" "errorOperation" = "❗ Ошибка в операции." -"getInboundsFailed" = "❌ Не удалось получить инбаунды." +"getInboundsFailed" = "❌ Не удалось получить входящие подключения." "getClientsFailed" = "❌ Не удалось получить клиентов." "canceled" = "❌ {{ .Email }}: Операция отменена." "clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен." @@ -760,5 +760,5 @@ "enableSuccess" = "✅ {{ .Email }}: Включено успешно." "disableSuccess" = "✅ {{ .Email }}: Отключено успешно." "askToAddUserId" = "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: {{ .TgUserID }}" -"chooseClient" = "Выберите клиента для инбаунда {{ .Inbound }}" -"chooseInbound" = "Выберите инбаунд" +"chooseClient" = "Выберите клиента для входящего подключения {{ .Inbound }}" +"chooseInbound" = "Выберите входящее подключение" From 4a75bd0a48db7ea3a2717806c5a9d3dcad31b92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9E=D0=BB?= =?UTF-8?q?=D0=B5=D0=B3=D0=BE=D0=B2=D0=B8=D1=87=20=D0=A1=D0=B0=D0=B5=D0=BD?= =?UTF-8?q?=D0=BA=D0=BE?= Date: Sat, 1 Nov 2025 15:10:27 +0300 Subject: [PATCH 13/26] Feature: add setting certs for subscription while generating for panel (#3578) --- main.go | 14 ++++++++++++++ web/service/setting.go | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/main.go b/main.go index 8ab8b13f..94b23bc8 100644 --- a/main.go +++ b/main.go @@ -321,6 +321,20 @@ func updateCert(publicKey string, privateKey string) { } else { fmt.Println("set certificate private key success") } + + err = settingService.SetSubCertFile(publicKey) + if err != nil { + fmt.Println("set certificate for subscription public key failed:", err) + } else { + fmt.Println("set certificate for subscription public key success") + } + + err = settingService.SetSubKeyFile(privateKey) + if err != nil { + fmt.Println("set certificate for subscription private key failed:", err) + } else { + fmt.Println("set certificate for subscription private key success") + } } else { fmt.Println("both public and private key should be entered.") } diff --git a/web/service/setting.go b/web/service/setting.go index c8ce7896..56db346d 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -479,10 +479,18 @@ func (s *SettingService) GetSubDomain() (string, error) { return s.getString("subDomain") } +func (s *SettingService) SetSubCertFile(subCertFile string) error { + return s.setString("subCertFile", subCertFile) +} + func (s *SettingService) GetSubCertFile() (string, error) { return s.getString("subCertFile") } +func (s *SettingService) SetSubKeyFile(subKeyFile string) error { + return s.setString("subKeyFile", subKeyFile) +} + func (s *SettingService) GetSubKeyFile() (string, error) { return s.getString("subKeyFile") } From 9936af80dd76a3400444a0fc6d18e8c3c4abcd8f Mon Sep 17 00:00:00 2001 From: OleksandrParshyn <43094723+OleksandrParshyn@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:33:35 +0100 Subject: [PATCH 14/26] Fix: Invoke service.StopBot() in signal handlers (#3583) Ensures the global Telegram bot stop function (`service.StopBot()`) is called upon receiving system signals (SIGHUP for restart, SIGINT/SIGTERM for shutdown). This complements the changes in `tgbot.go` to guarantee a clean shutdown of the Telegram bot's Long Polling operation, fully resolving the 409 Conflict issue during panel restarts or shutdowns. Changes: - Added `service.StopBot()` call to the `syscall.SIGHUP` handler. - Added `service.StopBot()` call to the default shutdown handler. --- main.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.go b/main.go index 94b23bc8..e2af2beb 100644 --- a/main.go +++ b/main.go @@ -78,6 +78,10 @@ func runWebServer() { case syscall.SIGHUP: logger.Info("Received SIGHUP signal. Restarting servers...") + // --- FIX FOR TELEGRAM BOT CONFLICT (409): Stop bot before restart --- + service.StopBot() + // -- + err := server.Stop() if err != nil { logger.Debug("Error stopping web server:", err) @@ -106,6 +110,10 @@ func runWebServer() { log.Println("Sub server restarted successfully.") default: + // --- FIX FOR TELEGRAM BOT CONFLICT (409) on full shutdown --- + service.StopBot() + // ------------------------------------------------------------ + server.Stop() subServer.Stop() log.Println("Shutting down servers.") From 575ee854c8c2421ac9e4df74da53b44753995a50 Mon Sep 17 00:00:00 2001 From: lillinlin <126753730+lillinlin@users.noreply.github.com> Date: Sun, 2 Nov 2025 21:46:50 +0800 Subject: [PATCH 15/26] Better Random Reality (#3585) * Update reality_targets.js * Update inbound.js --- web/assets/js/model/inbound.js | 2 +- web/assets/js/model/reality_targets.js | 75 ++++---------------------- 2 files changed, 11 insertions(+), 66 deletions(-) diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index 0718b20c..15410750 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -744,7 +744,7 @@ class RealityStreamSettings extends XrayCommonClass { if (!target && !serverNames) { const randomTarget = typeof getRandomRealityTarget !== 'undefined' ? getRandomRealityTarget() - : { target: 'google.com:443', sni: 'google.com,www.google.com' }; + : { target: 'www.apple.com:443', sni: 'www.apple.com,apple.com' }; target = randomTarget.target; serverNames = randomTarget.sni; } diff --git a/web/assets/js/model/reality_targets.js b/web/assets/js/model/reality_targets.js index 348ff296..cfe65afc 100644 --- a/web/assets/js/model/reality_targets.js +++ b/web/assets/js/model/reality_targets.js @@ -1,74 +1,18 @@ // List of popular services for VLESS Reality Target/SNI randomization const REALITY_TARGETS = [ - // CDN & Cloud Infrastructure - { target: 'www.cloudflare.com:443', sni: 'www.cloudflare.com,cloudflare.com' }, - { target: 'www.microsoft.com:443', sni: 'www.microsoft.com,microsoft.com' }, + { target: 'www.icloud.com:443', sni: 'www.icloud.com,icloud.com' }, { target: 'www.apple.com:443', sni: 'www.apple.com,apple.com' }, - { target: 'www.amazon.com:443', sni: 'www.amazon.com,amazon.com' }, - { target: 'cloud.google.com:443', sni: 'cloud.google.com,www.google.com' }, + { target: 'www.tesla.com:443', sni: 'www.tesla.com,tesla.com' }, + { target: 'www.sony.com:443', sni: 'www.sony.com,sony.com' }, + { target: 'www.nvidia.com:443', sni: 'www.nvidia.com,nvidia.com' }, + { target: 'www.amd.com:443', sni: 'www.amd.com,amd.com' }, { target: 'azure.microsoft.com:443', sni: 'azure.microsoft.com,www.azure.com' }, { target: 'aws.amazon.com:443', sni: 'aws.amazon.com,amazon.com' }, - { target: 'www.digitalocean.com:443', sni: 'www.digitalocean.com,digitalocean.com' }, - - // Social Media - { target: 'www.facebook.com:443', sni: 'www.facebook.com,facebook.com' }, - { target: 'www.instagram.com:443', sni: 'www.instagram.com,instagram.com' }, - { target: 'www.twitter.com:443', sni: 'www.twitter.com,twitter.com' }, - { target: 'www.linkedin.com:443', sni: 'www.linkedin.com,linkedin.com' }, - { target: 'www.reddit.com:443', sni: 'www.reddit.com,reddit.com' }, - { target: 'www.pinterest.com:443', sni: 'www.pinterest.com,pinterest.com' }, - { target: 'www.tumblr.com:443', sni: 'www.tumblr.com,tumblr.com' }, - - // Video & Streaming - { target: 'www.youtube.com:443', sni: 'www.youtube.com,youtube.com' }, - { target: 'www.netflix.com:443', sni: 'www.netflix.com,netflix.com' }, - { target: 'www.twitch.tv:443', sni: 'www.twitch.tv,twitch.tv' }, - { target: 'vimeo.com:443', sni: 'vimeo.com,www.vimeo.com' }, - { target: 'www.hulu.com:443', sni: 'www.hulu.com,hulu.com' }, - { target: 'www.disneyplus.com:443', sni: 'www.disneyplus.com,disneyplus.com' }, - - // News & Media - { target: 'www.bbc.com:443', sni: 'www.bbc.com,bbc.com' }, - { target: 'www.cnn.com:443', sni: 'www.cnn.com,cnn.com' }, - { target: 'www.nytimes.com:443', sni: 'www.nytimes.com,nytimes.com' }, - { target: 'www.theguardian.com:443', sni: 'www.theguardian.com,theguardian.com' }, - { target: 'www.reuters.com:443', sni: 'www.reuters.com,reuters.com' }, - { target: 'www.bloomberg.com:443', sni: 'www.bloomberg.com,bloomberg.com' }, - - // E-commerce - { target: 'www.ebay.com:443', sni: 'www.ebay.com,ebay.com' }, - { target: 'www.alibaba.com:443', sni: 'www.alibaba.com,alibaba.com' }, - { target: 'www.shopify.com:443', sni: 'www.shopify.com,shopify.com' }, - { target: 'www.walmart.com:443', sni: 'www.walmart.com,walmart.com' }, - { target: 'www.target.com:443', sni: 'www.target.com,target.com' }, - - // Tech Companies - { target: 'www.github.com:443', sni: 'www.github.com,github.com' }, - { target: 'www.stackoverflow.com:443', sni: 'www.stackoverflow.com,stackoverflow.com' }, - { target: 'www.gitlab.com:443', sni: 'www.gitlab.com,gitlab.com' }, - { target: 'www.docker.com:443', sni: 'www.docker.com,docker.com' }, - { target: 'www.nvidia.com:443', sni: 'www.nvidia.com,nvidia.com' }, - { target: 'www.intel.com:443', sni: 'www.intel.com,intel.com' }, - { target: 'www.amd.com:443', sni: 'www.amd.com,amd.com' }, - - // Communication & Productivity - { target: 'www.zoom.us:443', sni: 'www.zoom.us,zoom.us' }, - { target: 'slack.com:443', sni: 'slack.com,www.slack.com' }, - { target: 'www.dropbox.com:443', sni: 'www.dropbox.com,dropbox.com' }, - { target: 'www.notion.so:443', sni: 'www.notion.so,notion.so' }, - { target: 'www.atlassian.com:443', sni: 'www.atlassian.com,atlassian.com' }, - { target: 'www.salesforce.com:443', sni: 'www.salesforce.com,salesforce.com' }, - - // Search & General - { target: 'www.wikipedia.org:443', sni: 'www.wikipedia.org,wikipedia.org' }, { target: 'www.bing.com:443', sni: 'www.bing.com,bing.com' }, - { target: 'www.yahoo.com:443', sni: 'www.yahoo.com,yahoo.com' }, - { target: 'www.duckduckgo.com:443', sni: 'www.duckduckgo.com,duckduckgo.com' }, - - // Gaming - { target: 'store.steampowered.com:443', sni: 'store.steampowered.com,steampowered.com' }, - { target: 'www.ea.com:443', sni: 'www.ea.com,ea.com' }, - { target: 'www.epicgames.com:443', sni: 'www.epicgames.com,epicgames.com' }, + { target: 'www.oracle.com:443', sni: 'www.oracle.com,oracle.com' }, + { target: 'www.intel.com:443', sni: 'www.intel.com,intel.com' }, + { target: 'www.microsoft.com:443', sni: 'www.microsoft.com,microsoft.com' }, + { target: 'www.amazon.com:443', sni: 'www.amazon.com,amazon.com' } ]; /** @@ -84,3 +28,4 @@ function getRandomRealityTarget() { sni: selected.sni }; } + From cf38226b5d9f2ae0328340c309f1ad8a60cdf6c1 Mon Sep 17 00:00:00 2001 From: fgsfds <4870330+fgsfds@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:26:43 +0500 Subject: [PATCH 16/26] Add update-all-geofiles key to x-ui.sh (#3586) * added update-all-geofiles key to x-ui.sh that updated all geofiles * fix * text fixes * typo fix * cleanup --- install.sh | 2 +- update.sh | 2 +- x-ui.sh | 94 ++++++++++++++++++++++++++++++------------------------ 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/install.sh b/install.sh index a2a6540b..0ec2aaa4 100644 --- a/install.sh +++ b/install.sh @@ -248,7 +248,7 @@ install_x-ui() { │ ${blue}x-ui log${plain} - Check logs │ │ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ │ ${blue}x-ui update${plain} - Update │ -│ ${blue}x-ui legacy${plain} - legacy version │ +│ ${blue}x-ui legacy${plain} - Legacy version │ │ ${blue}x-ui install${plain} - Install │ │ ${blue}x-ui uninstall${plain} - Uninstall │ └───────────────────────────────────────────────────────┘" diff --git a/update.sh b/update.sh index c7ef0235..793c5097 100755 --- a/update.sh +++ b/update.sh @@ -247,7 +247,7 @@ update_x-ui() { │ ${blue}x-ui log${plain} - Check logs │ │ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ │ ${blue}x-ui update${plain} - Update │ -│ ${blue}x-ui legacy${plain} - legacy version │ +│ ${blue}x-ui legacy${plain} - Legacy version │ │ ${blue}x-ui install${plain} - Install │ │ ${blue}x-ui uninstall${plain} - Uninstall │ └───────────────────────────────────────────────────────┘" diff --git a/x-ui.sh b/x-ui.sh index 78bb072a..4b0989eb 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -863,10 +863,32 @@ delete_ports() { fi } +update_all_geofiles() { + update_main_geofiles + update_ir_geofiles + update_ru_geofiles +} + +update_main_geofiles() { + wget -O geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat + wget -O geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat +} + +update_ir_geofiles() { + wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat + wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat +} + +update_ru_geofiles() { + wget -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat + wget -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat +} + update_geo() { echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)" echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)" echo -e "${green}\t3.${plain} runetfreedom (geoip_RU.dat, geosite_RU.dat)" + echo -e "${green}\t4.${plain} All" echo -e "${green}\t0.${plain} Back to Main Menu" read -rp "Choose an option: " choice @@ -877,41 +899,25 @@ update_geo() { show_menu ;; 1) - if [[ $release == "alpine" ]]; then - rc-service x-ui stop - else - systemctl stop x-ui - fi - rm -f geoip.dat geosite.dat - wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat - wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat + update_main_geofiles echo -e "${green}Loyalsoldier datasets have been updated successfully!${plain}" restart ;; 2) - if [[ $release == "alpine" ]]; then - rc-service x-ui stop - else - systemctl stop x-ui - fi - rm -f geoip_IR.dat geosite_IR.dat - wget -O geoip_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat - wget -O geosite_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat + update_ir_geofiles echo -e "${green}chocolate4u datasets have been updated successfully!${plain}" restart ;; 3) - if [[ $release == "alpine" ]]; then - rc-service x-ui stop - else - systemctl stop x-ui - fi - rm -f geoip_RU.dat geosite_RU.dat - wget -O geoip_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat - wget -O geosite_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat + update_ru_geofiles echo -e "${green}runetfreedom datasets have been updated successfully!${plain}" restart ;; + 4) + update_all_geofiles + echo -e "${green}All geo files have been updated successfully!${plain}" + restart + ;; *) echo -e "${red}Invalid option. Please select a valid number.${plain}\n" update_geo @@ -1869,24 +1875,25 @@ SSH_port_forwarding() { } show_usage() { - echo -e "┌───────────────────────────────────────────────────────┐ -│ ${blue}x-ui control menu usages (subcommands):${plain} │ -│ │ -│ ${blue}x-ui${plain} - Admin Management Script │ -│ ${blue}x-ui start${plain} - Start │ -│ ${blue}x-ui stop${plain} - Stop │ -│ ${blue}x-ui restart${plain} - Restart │ -│ ${blue}x-ui status${plain} - Current Status │ -│ ${blue}x-ui settings${plain} - Current Settings │ -│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │ -│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │ -│ ${blue}x-ui log${plain} - Check logs │ -│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ -│ ${blue}x-ui update${plain} - Update │ -│ ${blue}x-ui legacy${plain} - legacy version │ -│ ${blue}x-ui install${plain} - Install │ -│ ${blue}x-ui uninstall${plain} - Uninstall │ -└───────────────────────────────────────────────────────┘" + echo -e "┌────────────────────────────────────────────────────────────────┐ +│ ${blue}x-ui control menu usages (subcommands):${plain} │ +│ │ +│ ${blue}x-ui${plain} - Admin Management Script │ +│ ${blue}x-ui start${plain} - Start │ +│ ${blue}x-ui stop${plain} - Stop │ +│ ${blue}x-ui restart${plain} - Restart │ +│ ${blue}x-ui status${plain} - Current Status │ +│ ${blue}x-ui settings${plain} - Current Settings │ +│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │ +│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │ +│ ${blue}x-ui log${plain} - Check logs │ +│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ +│ ${blue}x-ui update${plain} - Update │ +│ ${blue}x-ui update-all-geofiles${plain} - Update all geo files │ +│ ${blue}x-ui legacy${plain} - Legacy version │ +│ ${blue}x-ui install${plain} - Install │ +│ ${blue}x-ui uninstall${plain} - Uninstall │ +└────────────────────────────────────────────────────────────────┘" } show_menu() { @@ -2056,6 +2063,9 @@ if [[ $# > 0 ]]; then "uninstall") check_install 0 && uninstall 0 ;; + "update-all-geofiles") + check_install 0 && update_all_geofiles 0 && restart 0 + ;; *) show_usage ;; esac else From 538f7fd5d7dd193a54d2a764d65068f7c686e541 Mon Sep 17 00:00:00 2001 From: fgsfds <4870330+fgsfds@users.noreply.github.com> Date: Sun, 9 Nov 2025 04:42:02 +0500 Subject: [PATCH 17/26] Fix: Incorrect time in xray logs (#3587) * fixed timezone in xray logs * remove leading / at the address --- web/html/index.html | 4 +++- web/service/server.go | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web/html/index.html b/web/html/index.html index 84445fbf..1c6bb0be 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -844,9 +844,11 @@ text = `${log.Email}`; } + const { locale, timeZone } = Intl.DateTimeFormat().resolvedOptions(); + formattedLogs += ` - ${new Date(log.DateTime).toLocaleString()} + ${new Date(log.DateTime).toLocaleString(locale, { timeZone })} ${log.FromAddress} ${log.ToAddress} ${log.Inbound} diff --git a/web/service/server.go b/web/service/server.go index b7cfc3a7..d48a96c3 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -794,17 +794,17 @@ func (s *ServerService) GetXrayLogs( for i, part := range parts { if i == 0 { - dateTime, err := time.Parse("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1]) + dateTime, err := time.ParseInLocation("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1], time.Local) if err != nil { continue } - entry.DateTime = dateTime + entry.DateTime = dateTime.UTC() } if part == "from" { - entry.FromAddress = parts[i+1] + entry.FromAddress = strings.TrimLeft(parts[i+1], "/") } else if part == "accepted" { - entry.ToAddress = parts[i+1] + entry.ToAddress = strings.TrimLeft(parts[i+1], "/") } else if strings.HasPrefix(part, "[") { entry.Inbound = part[1:] } else if strings.HasSuffix(part, "]") { From 784ed399305b4d979977125281ffc104737eda5f Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Sun, 9 Nov 2025 00:56:14 +0100 Subject: [PATCH 18/26] update dependencies --- go.mod | 40 ++++++++++++++--------------- go.sum | 80 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/go.mod b/go.mod index 816d05a2..1814adf3 100644 --- a/go.mod +++ b/go.mod @@ -1,45 +1,45 @@ module github.com/mhsanaei/3x-ui/v2 -go 1.25.2 +go 1.25.4 require ( - github.com/gin-contrib/gzip v1.2.4 + github.com/gin-contrib/gzip v1.2.5 github.com/gin-contrib/sessions v1.0.4 github.com/gin-gonic/gin v1.11.0 github.com/go-ldap/ldap/v3 v3.4.12 github.com/goccy/go-json v0.10.5 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 - github.com/mymmrac/telego v1.3.0 + github.com/mymmrac/telego v1.3.1 github.com/nicksnyder/go-i18n/v2 v2.6.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml/v2 v2.2.4 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v4 v4.25.9 + github.com/shirou/gopsutil/v4 v4.25.10 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/valyala/fasthttp v1.67.0 + github.com/valyala/fasthttp v1.68.0 github.com/xlzd/gotp v0.1.0 - github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6 + github.com/xtls/xray-core v1.251015.0 go.uber.org/atomic v1.11.0 golang.org/x/crypto v0.43.0 - golang.org/x/sys v0.37.0 + golang.org/x/sys v0.38.0 golang.org/x/text v0.30.0 google.golang.org/grpc v1.76.0 gorm.io/driver/sqlite v1.6.0 - gorm.io/gorm v1.31.0 + gorm.io/gorm v1.31.1 ) require ( - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/Azure/go-ntlmssp v0.0.1 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.14.1 // indirect - github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/bytedance/sonic v1.14.2 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect - github.com/ebitengine/purego v0.9.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.11 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -57,7 +57,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/ratelimit v1.0.2 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.1 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -70,17 +70,17 @@ require ( github.com/pires/go-proxyproto v0.8.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.55.0 // indirect + github.com/quic-go/quic-go v0.56.0 // indirect github.com/refraction-networking/utls v1.8.1 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/sagernet/sing v0.7.12 // indirect + github.com/sagernet/sing v0.7.13 // indirect github.com/sagernet/sing-shadowsocks v0.2.9 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.0 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fastjson v1.6.4 // indirect @@ -89,15 +89,15 @@ require ( github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/arch v0.22.0 // indirect + golang.org/x/arch v0.23.0 // indirect golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.46.0 // indirect - golang.org/x/sync v0.17.0 // indirect + golang.org/x/sync v0.18.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.38.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect google.golang.org/protobuf v1.36.10 // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/go.sum b/go.sum index d3e85890..68c6a7eb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.1 h1:NqbqUHiVYjwBDsxM1KrllG7rnoHpcp40EWrpffsgcUc= +github.com/Azure/go-ntlmssp v0.0.1/go.mod h1:P/Wrai1IsNvkfWRRN0jvRobt7ZJdz4sHQ3dOjiEGDt0= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= @@ -8,10 +8,10 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= -github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= @@ -23,14 +23,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM= github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= -github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= -github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= -github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= +github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/gin-contrib/gzip v1.2.4 h1:yNz4EhPC2kHSZJD1oc1zwp7MLEhEZ3goQeGM3a1b6jU= -github.com/gin-contrib/gzip v1.2.4/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= +github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= +github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U= github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= @@ -107,8 +107,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -130,8 +130,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mymmrac/telego v1.3.0 h1:y2bDDCioLgkcs+5luUaPgTNHKel1Qh30iUxFcMUrowg= -github.com/mymmrac/telego v1.3.0/go.mod h1:0D2l/IA/gUFn4oqsi1O4/tSnlezw5jNV/ReFRDUEKk8= +github.com/mymmrac/telego v1.3.1 h1:dI5D8LKWBw241W02LmJqoSLZXW3tuLokxVoNbIZUYQg= +github.com/mymmrac/telego v1.3.1/go.mod h1:3D0h4jJ3OzubY/gI4xDIGx4jkY26fmcPyqx0Lq24+zI= github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ= github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= @@ -148,8 +148,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= -github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= +github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= +github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo= github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -158,24 +158,26 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/sagernet/sing v0.7.12 h1:MpMbO56crPRZTbltoj1wGk4Xj9+GiwH1wTO4s3fz1EA= -github.com/sagernet/sing v0.7.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM= +github.com/sagernet/sing v0.7.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM= github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= -github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= -github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= +github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= +github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= @@ -184,14 +186,14 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= -github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= 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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.67.0 h1:tqKlJMUP6iuNG8hGjK/s9J4kadH7HLV4ijEcPGsezac= -github.com/valyala/fasthttp v1.67.0/go.mod h1:qYSIpqt/0XNmShgo/8Aq8E3UYWVVwNS2QYmzd8WIEPM= +github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok= +github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= @@ -202,8 +204,8 @@ github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU= github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= -github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6 h1:gwgJxWb9OABUJAYxiS33nQzk3MRVjidzBnHBrzKnxOw= -github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik= +github.com/xtls/xray-core v1.251015.0 h1:P7b3vt8ShhH31k4h6VJ/Pxar3tY9eK+7S8eygd6rsP0= +github.com/xtls/xray-core v1.251015.0/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -226,24 +228,24 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= -golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -256,8 +258,8 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= @@ -273,8 +275,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= -gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= -gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= From ad659e48cf78f6bbd60192c463ef19fb62dec4f8 Mon Sep 17 00:00:00 2001 From: Evgeny Popov Date: Wed, 3 Dec 2025 18:42:10 +0500 Subject: [PATCH 19/26] Update x-ui.sh (#3595) Add curl & openssl pkgs for acme inside docker container --- x-ui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-ui.sh b/x-ui.sh index 4b0989eb..b492998b 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -1086,7 +1086,7 @@ ssl_cert_issue() { zypper refresh && zypper -q install -y socat ;; alpine) - apk add socat + apk add socat curl openssl ;; *) echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" From 7ccc0877a152c35ef6933e3e3e998d91a212ad26 Mon Sep 17 00:00:00 2001 From: Anton Petrov <63284142+Psychosoc1al@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:43:37 +0300 Subject: [PATCH 20/26] Add "Last Online" printing for Telegram bot (#3593) --- web/service/tgbot.go | 5 +++++ web/translation/translate.ar_EG.toml | 9 +++++---- web/translation/translate.en_US.toml | 7 ++++--- web/translation/translate.es_ES.toml | 7 ++++--- web/translation/translate.fa_IR.toml | 15 ++++++++------- web/translation/translate.id_ID.toml | 11 ++++++----- web/translation/translate.ja_JP.toml | 15 ++++++++------- web/translation/translate.pt_BR.toml | 17 +++++++++-------- web/translation/translate.ru_RU.toml | 1 + web/translation/translate.tr_TR.toml | 15 ++++++++------- web/translation/translate.uk_UA.toml | 15 ++++++++------- web/translation/translate.vi_VN.toml | 7 ++++--- web/translation/translate.zh_CN.toml | 17 +++++++++-------- web/translation/translate.zh_TW.toml | 17 +++++++++-------- 14 files changed, 88 insertions(+), 70 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 1573b2bf..cac49dd9 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -2952,10 +2952,12 @@ func (t *Tgbot) clientInfoMsg( } status := t.I18nBot("tgbot.offline") + isOnline := false if p.IsRunning() { for _, online := range p.GetOnlineClients() { if online == traffic.Email { status = t.I18nBot("tgbot.online") + isOnline = true break } } @@ -2968,6 +2970,9 @@ func (t *Tgbot) clientInfoMsg( } if printOnline { output += t.I18nBot("tgbot.messages.online", "Status=="+status) + if !isOnline && traffic.LastOnline > 0 { + output += t.I18nBot("tgbot.messages.lastOnline", "Time=="+time.UnixMilli(traffic.LastOnline).Format("2006-01-02 15:04:05")) + } } if printActive { output += t.I18nBot("tgbot.messages.active", "Enable=="+active) diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml index 2287b52b..71d1710f 100644 --- a/web/translation/translate.ar_EG.toml +++ b/web/translation/translate.ar_EG.toml @@ -106,7 +106,7 @@ "invalidFormData" = "تنسيق البيانات المدخلة مش صحيح." "emptyUsername" = "اسم المستخدم مطلوب" "emptyPassword" = "الباسورد مطلوب" -"wrongUsernameOrPassword" = "اسم المستخدم أو كلمة المرور أو كود المصادقة الثنائية غير صحيح." +"wrongUsernameOrPassword" = "اسم المستخدم أو كلمة المرور أو كود المصادقة الثنائية غير صحيح." "successLogin" = "لقد تم تسجيل الدخول إلى حسابك بنجاح." [pages.index] @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "بيانات الأدمن" -"twoFactor" = "المصادقة الثنائية" -"twoFactorEnable" = "تفعيل المصادقة الثنائية" -"twoFactorEnableDesc" = "يضيف طبقة إضافية من المصادقة لتعزيز الأمان." +"twoFactor" = "المصادقة الثنائية" +"twoFactorEnable" = "تفعيل المصادقة الثنائية" +"twoFactorEnableDesc" = "يضيف طبقة إضافية من المصادقة لتعزيز الأمان." "twoFactorModalSetTitle" = "تفعيل المصادقة الثنائية" "twoFactorModalDeleteTitle" = "تعطيل المصادقة الثنائية" "twoFactorModalSteps" = "لإعداد المصادقة الثنائية، قم ببعض الخطوات:" @@ -663,6 +663,7 @@ "active" = "💡 مفعل: {{ .Enable }}\r\n" "enabled" = "🚨 مفعل: {{ .Enable }}\r\n" "online" = "🌐 حالة الاتصال: {{ .Status }}\r\n" +"lastOnline" = "🔙 آخر متصل: {{ .Time }}\r\n" "email" = "📧 الإيميل: {{ .Email }}\r\n" "upload" = "🔼 رفع: ↑{{ .Upload }}\r\n" "download" = "🔽 تنزيل: ↓{{ .Download }}\r\n" diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index e56f5f89..92203a85 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -663,6 +663,7 @@ "active" = "💡 Active: {{ .Enable }}\r\n" "enabled" = "🚨 Enabled: {{ .Enable }}\r\n" "online" = "🌐 Connection status: {{ .Status }}\r\n" +"lastOnline" = "🔙 Last online: {{ .Time }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" "download" = "🔽 Download: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!" "inbound_client_data_pass" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 Password: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!" "cancel" = "❌ Process Canceled! \n\nYou can /start again anytime. 🔄" -"error_add_client" = "⚠️ Error:\n\n {{ .error }}" -"using_default_value" = "Okay, I'll stick with the default value. 😊" -"incorrect_input" ="Your input is not valid.\nThe phrases should be continuous without spaces.\nCorrect example: aaaaaa\nIncorrect example: aaa aaa 🚫" +"error_add_client" = "⚠️ Error:\n\n {{ .error }}" +"using_default_value" = "Okay, I'll stick with the default value. 😊" +"incorrect_input" = "Your input is not valid.\nThe phrases should be continuous without spaces.\nCorrect example: aaaaaa\nIncorrect example: aaa aaa 🚫" "AreYouSure" = "Are you sure? 🤔" "SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Result: ✅ Success" "FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Result: ❌ Failed \n\n🛠️ Error: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index ea70494c..3da08775 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -663,6 +663,7 @@ "active" = "💡 Activo: {{ .Enable }}\r\n" "enabled" = "🚨 Habilitado: {{ .Enable }}\r\n" "online" = "🌐 Estado de conexión: {{ .Status }}\r\n" +"lastOnline" = "🔙 Última conexión: {{ .Time }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Subida: ↑{{ .Upload }}\r\n" "download" = "🔽 Bajada: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Correo: {{ .ClientEmail }}\n📊 Tráfico: {{ .ClientTraffic }}\n📅 Fecha de expiración: {{ .ClientExp }}\n🌐 Límite de IP: {{ .IpLimit }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a la entrada!" "inbound_client_data_pass" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Contraseña: {{ .ClientPass }}\n📧 Correo: {{ .ClientEmail }}\n📊 Tráfico: {{ .ClientTraffic }}\n📅 Fecha de expiración: {{ .ClientExp }}\n🌐 Límite de IP: {{ .IpLimit }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a la entrada!" "cancel" = "❌ ¡Proceso cancelado! \n\nPuedes /start de nuevo en cualquier momento. 🔄" -"error_add_client" = "⚠️ Error:\n\n {{ .error }}" -"using_default_value" = "Está bien, me quedaré con el valor predeterminado. 😊" -"incorrect_input" ="Tu entrada no es válida.\nLas frases deben ser continuas sin espacios.\nEjemplo correcto: aaaaaa\nEjemplo incorrecto: aaa aaa 🚫" +"error_add_client" = "⚠️ Error:\n\n {{ .error }}" +"using_default_value" = "Está bien, me quedaré con el valor predeterminado. 😊" +"incorrect_input" = "Tu entrada no es válida.\nLas frases deben ser continuas sin espacios.\nEjemplo correcto: aaaaaa\nEjemplo incorrecto: aaa aaa 🚫" "AreYouSure" = "¿Estás seguro? 🤔" "SuccessResetTraffic" = "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ✅ Éxito" "FailedResetTraffic" = "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ❌ Fallido \n\n🛠️ Error: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 70603d08..0da0f9ad 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -106,7 +106,7 @@ "invalidFormData" = "اطلاعات به‌درستی وارد نشده‌است" "emptyUsername" = "لطفا یک نام‌کاربری وارد کنید‌" "emptyPassword" = "لطفا یک رمزعبور وارد کنید" -"wrongUsernameOrPassword" = "نام کاربری، رمز عبور یا کد دو مرحله‌ای نامعتبر است." +"wrongUsernameOrPassword" = "نام کاربری، رمز عبور یا کد دو مرحله‌ای نامعتبر است." "successLogin" = "شما با موفقیت به حساب کاربری خود وارد شدید." [pages.index] @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "اعتبارنامه‌های ادمین" -"twoFactor" = "احراز هویت دو مرحله‌ای" -"twoFactorEnable" = "فعال‌سازی 2FA" -"twoFactorEnableDesc" = "یک لایه اضافی امنیتی برای احراز هویت فراهم می‌کند." +"twoFactor" = "احراز هویت دو مرحله‌ای" +"twoFactorEnable" = "فعال‌سازی 2FA" +"twoFactorEnableDesc" = "یک لایه اضافی امنیتی برای احراز هویت فراهم می‌کند." "twoFactorModalSetTitle" = "فعال‌سازی احراز هویت دو مرحله‌ای" "twoFactorModalDeleteTitle" = "غیرفعال‌سازی احراز هویت دو مرحله‌ای" "twoFactorModalSteps" = "برای راه‌اندازی احراز هویت دو مرحله‌ای، مراحل زیر را انجام دهید:" @@ -663,6 +663,7 @@ "active" = "💡 فعال: {{ .Enable }}\r\n" "enabled" = "🚨 وضعیت: {{ .Enable }}\r\n" "online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n" +"lastOnline" = "🔙 آخرین فعالیت: {{ .Time }}\r\n" "email" = "📧 ایمیل: {{ .Email }}\r\n" "upload" = "🔼 آپلود↑: {{ .Upload }}\r\n" "download" = "🔽 دانلود↓: {{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 شناسه: {{ .ClientId }}\n📧 ایمیل: {{ .ClientEmail }}\n📊 ترافیک: {{ .ClientTraffic }}\n📅 تاریخ انقضا: {{ .ClientExp }}\n🌐 محدودیت IP: {{ .IpLimit }}\n💬 توضیح: {{ .ClientComment }}\n\nاکنون می‌تونی مشتری را به ورودی اضافه کنی!" "inbound_client_data_pass" = "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 رمز عبور: {{ .ClientPass }}\n📧 ایمیل: {{ .ClientEmail }}\n📊 ترافیک: {{ .ClientTraffic }}\n📅 تاریخ انقضا: {{ .ClientExp }}\n🌐 محدودیت IP: {{ .IpLimit }}\n💬 توضیح: {{ .ClientComment }}\n\nاکنون می‌تونی مشتری را به ورودی اضافه کنی!" "cancel" = "❌ فرآیند لغو شد! \n\nمی‌توانید هر زمان که خواستید /start را دوباره اجرا کنید. 🔄" -"error_add_client" = "⚠️ خطا:\n\n {{ .error }}" -"using_default_value" = "باشه، از مقدار پیش‌فرض استفاده می‌کنم. 😊" -"incorrect_input" ="ورودی شما معتبر نیست.\nعبارت‌ها باید بدون فاصله باشند.\nمثال صحیح: aaaaaa\nمثال نادرست: aaa aaa 🚫" +"error_add_client" = "⚠️ خطا:\n\n {{ .error }}" +"using_default_value" = "باشه، از مقدار پیش‌فرض استفاده می‌کنم. 😊" +"incorrect_input" = "ورودی شما معتبر نیست.\nعبارت‌ها باید بدون فاصله باشند.\nمثال صحیح: aaaaaa\nمثال نادرست: aaa aaa 🚫" "AreYouSure" = "مطمئنی؟ 🤔" "SuccessResetTraffic" = "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ✅ موفقیت‌آمیز" "FailedResetTraffic" = "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ❌ ناموفق \n\n🛠️ خطا: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index 5068aecb..37d5a3c9 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -106,12 +106,12 @@ "invalidFormData" = "Format data input tidak valid." "emptyUsername" = "Nama Pengguna diperlukan" "emptyPassword" = "Kata Sandi diperlukan" -"wrongUsernameOrPassword" = "Username, kata sandi, atau kode dua faktor tidak valid." +"wrongUsernameOrPassword" = "Username, kata sandi, atau kode dua faktor tidak valid." "successLogin" = "Anda telah berhasil masuk ke akun Anda." [pages.index] "title" = "Ikhtisar" -"cpu" = "CPU" +"cpu" = "CPU" "logicalProcessors" = "Prosesor logis" "frequency" = "Frekuensi" "swap" = "Swap" @@ -663,6 +663,7 @@ "active" = "💡 Aktif: {{ .Enable }}\r\n" "enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n" "online" = "🌐 Status Koneksi: {{ .Status }}\r\n" +"lastOnline" = "🔙 Terakhir online: {{ .Time }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n" "download" = "🔽 Unduh: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Lalu lintas: {{ .ClientTraffic }}\n📅 Tanggal Kedaluwarsa: {{ .ClientExp }}\n🌐 Batas IP: {{ .IpLimit }}\n💬 Komentar: {{ .ClientComment }}\n\nSekarang kamu bisa menambahkan klien ke inbound!" "inbound_client_data_pass" = "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 Kata sandi: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Lalu lintas: {{ .ClientTraffic }}\n📅 Tanggal Kedaluwarsa: {{ .ClientExp }}\n🌐 Batas IP: {{ .IpLimit }}\n💬 Komentar: {{ .ClientComment }}\n\nSekarang kamu bisa menambahkan klien ke inbound!" "cancel" = "❌ Proses Dibatalkan! \n\nAnda dapat /start lagi kapan saja. 🔄" -"error_add_client" = "⚠️ Kesalahan:\n\n {{ .error }}" -"using_default_value" = "Oke, saya akan tetap menggunakan nilai default. 😊" -"incorrect_input" ="Masukan Anda tidak valid.\nFrasa harus berlanjut tanpa spasi.\nContoh benar: aaaaaa\nContoh salah: aaa aaa 🚫" +"error_add_client" = "⚠️ Kesalahan:\n\n {{ .error }}" +"using_default_value" = "Oke, saya akan tetap menggunakan nilai default. 😊" +"incorrect_input" = "Masukan Anda tidak valid.\nFrasa harus berlanjut tanpa spasi.\nContoh benar: aaaaaa\nContoh salah: aaa aaa 🚫" "AreYouSure" = "Apakah kamu yakin? 🤔" "SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ✅ Berhasil" "FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ❌ Gagal \n\n🛠️ Kesalahan: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml index ebec829a..ebd19ea0 100644 --- a/web/translation/translate.ja_JP.toml +++ b/web/translation/translate.ja_JP.toml @@ -106,7 +106,7 @@ "invalidFormData" = "データ形式エラー" "emptyUsername" = "ユーザー名を入力してください" "emptyPassword" = "パスワードを入力してください" -"wrongUsernameOrPassword" = "ユーザー名、パスワード、または二段階認証コードが無効です。" +"wrongUsernameOrPassword" = "ユーザー名、パスワード、または二段階認証コードが無効です。" "successLogin" = "アカウントに正常にログインしました。" [pages.index] @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "管理者の資格情報" -"twoFactor" = "二段階認証" -"twoFactorEnable" = "2FAを有効化" -"twoFactorEnableDesc" = "セキュリティを強化するために追加の認証層を追加します。" +"twoFactor" = "二段階認証" +"twoFactorEnable" = "2FAを有効化" +"twoFactorEnableDesc" = "セキュリティを強化するために追加の認証層を追加します。" "twoFactorModalSetTitle" = "二段階認証を有効にする" "twoFactorModalDeleteTitle" = "二段階認証を無効にする" "twoFactorModalSteps" = "二段階認証を設定するには、次の手順を実行してください:" @@ -663,6 +663,7 @@ "active" = "💡 有効:{{ .Enable }}\r\n" "enabled" = "🚨 有効化済み:{{ .Enable }}\r\n" "online" = "🌐 接続ステータス:{{ .Status }}\r\n" +"lastOnline" = "🔙 最終オンライン: {{ .Time }}\r\n" "email" = "📧 メール:{{ .Email }}\r\n" "upload" = "🔼 アップロード↑:{{ .Upload }}\r\n" "download" = "🔽 ダウンロード↓:{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 メール: {{ .ClientEmail }}\n📊 トラフィック: {{ .ClientTraffic }}\n📅 有効期限: {{ .ClientExp }}\n🌐 IP制限: {{ .IpLimit }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐこのクライアントをインバウンドに追加できます!" "inbound_client_data_pass" = "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 パスワード: {{ .ClientPass }}\n📧 メール: {{ .ClientEmail }}\n📊 トラフィック: {{ .ClientTraffic }}\n📅 有効期限: {{ .ClientExp }}\n🌐 IP制限: {{ .IpLimit }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐこのクライアントをインバウンドに追加できます!" "cancel" = "❌ プロセスがキャンセルされました!\n\nいつでも /start で再開できます。 🔄" -"error_add_client" = "⚠️ エラー:\n\n {{ .error }}" -"using_default_value" = "わかりました、デフォルト値を使用します。 😊" -"incorrect_input" ="入力が無効です。\nフレーズはスペースなしで続けて入力してください。\n正しい例: aaaaaa\n間違った例: aaa aaa 🚫" +"error_add_client" = "⚠️ エラー:\n\n {{ .error }}" +"using_default_value" = "わかりました、デフォルト値を使用します。 😊" +"incorrect_input" = "入力が無効です。\nフレーズはスペースなしで続けて入力してください。\n正しい例: aaaaaa\n間違った例: aaa aaa 🚫" "AreYouSure" = "本当にいいですか?🤔" "SuccessResetTraffic" = "📧 メール: {{ .ClientEmail }}\n🏁 結果: ✅ 成功" "FailedResetTraffic" = "📧 メール: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ エラー: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml index f6f3b975..0ad1be58 100644 --- a/web/translation/translate.pt_BR.toml +++ b/web/translation/translate.pt_BR.toml @@ -106,12 +106,12 @@ "invalidFormData" = "O formato dos dados de entrada é inválido." "emptyUsername" = "Nome de usuário é obrigatório" "emptyPassword" = "Senha é obrigatória" -"wrongUsernameOrPassword" = "Nome de usuário, senha ou código de dois fatores inválido." +"wrongUsernameOrPassword" = "Nome de usuário, senha ou código de dois fatores inválido." "successLogin" = "Você entrou na sua conta com sucesso." [pages.index] "title" = "Visão Geral" -"cpu" = "CPU" +"cpu" = "CPU" "logicalProcessors" = "Processadores lógicos" "frequency" = "Frequência" "swap" = "Swap" @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "Credenciais de administrador" -"twoFactor" = "Autenticação de dois fatores" -"twoFactorEnable" = "Ativar 2FA" -"twoFactorEnableDesc" = "Adiciona uma camada extra de autenticação para mais segurança." +"twoFactor" = "Autenticação de dois fatores" +"twoFactorEnable" = "Ativar 2FA" +"twoFactorEnableDesc" = "Adiciona uma camada extra de autenticação para mais segurança." "twoFactorModalSetTitle" = "Ativar autenticação de dois fatores" "twoFactorModalDeleteTitle" = "Desativar autenticação de dois fatores" "twoFactorModalSteps" = "Para configurar a autenticação de dois fatores, siga alguns passos:" @@ -663,6 +663,7 @@ "active" = "💡 Ativo: {{ .Enable }}\r\n" "enabled" = "🚨 Ativado: {{ .Enable }}\r\n" "online" = "🌐 Status da conexão: {{ .Status }}\r\n" +"lastOnline" = "🔙 Última vez online: {{ .Time }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" "download" = "🔽 Download: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Tráfego: {{ .ClientTraffic }}\n📅 Data de expiração: {{ .ClientExp }}\n🌐 Limite de IP: {{ .IpLimit }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!" "inbound_client_data_pass" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Senha: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Tráfego: {{ .ClientTraffic }}\n📅 Data de expiração: {{ .ClientExp }}\n🌐 Limite de IP: {{ .IpLimit }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!" "cancel" = "❌ Processo Cancelado! \n\nVocê pode iniciar novamente a qualquer momento com /start. 🔄" -"error_add_client" = "⚠️ Erro:\n\n {{ .error }}" -"using_default_value" = "Tudo bem, vou manter o valor padrão. 😊" -"incorrect_input" ="Sua entrada não é válida.\nAs frases devem ser contínuas, sem espaços.\nExemplo correto: aaaaaa\nExemplo incorreto: aaa aaa 🚫" +"error_add_client" = "⚠️ Erro:\n\n {{ .error }}" +"using_default_value" = "Tudo bem, vou manter o valor padrão. 😊" +"incorrect_input" = "Sua entrada não é válida.\nAs frases devem ser contínuas, sem espaços.\nExemplo correto: aaaaaa\nExemplo incorreto: aaa aaa 🚫" "AreYouSure" = "Você tem certeza? 🤔" "SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ✅ Sucesso" "FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ❌ Falhou \n\n🛠️ Erro: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 847b718e..bea5b5e9 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -663,6 +663,7 @@ "active" = "💡 Активен: {{ .Enable }}\r\n" "enabled" = "🚨 Активен: {{ .Enable }}\r\n" "online" = "🌐 Статус соединения: {{ .Status }}\r\n" +"lastOnline" = "🔙 Был(а) в сети: {{ .Time }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n" "download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml index d34f6fca..de5716aa 100644 --- a/web/translation/translate.tr_TR.toml +++ b/web/translation/translate.tr_TR.toml @@ -106,7 +106,7 @@ "invalidFormData" = "Girdi verisi formatı geçersiz." "emptyUsername" = "Kullanıcı adı gerekli" "emptyPassword" = "Şifre gerekli" -"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı, şifre veya iki adımlı doğrulama kodu." +"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı, şifre veya iki adımlı doğrulama kodu." "successLogin" = "Hesabınıza başarıyla giriş yaptınız." [pages.index] @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "Yönetici kimlik bilgileri" -"twoFactor" = "İki adımlı doğrulama" -"twoFactorEnable" = "2FA'yı Etkinleştir" -"twoFactorEnableDesc" = "Daha fazla güvenlik için ek bir doğrulama katmanı ekler." +"twoFactor" = "İki adımlı doğrulama" +"twoFactorEnable" = "2FA'yı Etkinleştir" +"twoFactorEnableDesc" = "Daha fazla güvenlik için ek bir doğrulama katmanı ekler." "twoFactorModalSetTitle" = "İki adımlı doğrulamayı etkinleştir" "twoFactorModalDeleteTitle" = "İki adımlı doğrulamayı devre dışı bırak" "twoFactorModalSteps" = "İki adımlı doğrulamayı ayarlamak için şu adımları izleyin:" @@ -663,6 +663,7 @@ "active" = "💡 Aktif: {{ .Enable }}\r\n" "enabled" = "🚨 Etkin: {{ .Enable }}\r\n" "online" = "🌐 Bağlantı durumu: {{ .Status }}\r\n" +"lastOnline" = "🔙 Son çevrimiçi: {{ .Time }}\r\n" "email" = "📧 E-posta: {{ .Email }}\r\n" "upload" = "🔼 Yükleme: ↑{{ .Upload }}\r\n" "download" = "🔽 İndirme: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Giriş: {{ .InboundRemark }}\n\n🔑 Kimlik: {{ .ClientId }}\n📧 E-posta: {{ .ClientEmail }}\n📊 Trafik: {{ .ClientTraffic }}\n📅 Bitiş Tarihi: {{ .ClientExp }}\n🌐 IP Sınırı: {{ .IpLimit }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık bu müşteriyi girişe ekleyebilirsin!" "inbound_client_data_pass" = "🔄 Giriş: {{ .InboundRemark }}\n\n🔑 Şifre: {{ .ClientPass }}\n📧 E-posta: {{ .ClientEmail }}\n📊 Trafik: {{ .ClientTraffic }}\n📅 Bitiş Tarihi: {{ .ClientExp }}\n🌐 IP Sınırı: {{ .IpLimit }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık bu müşteriyi girişe ekleyebilirsin!" "cancel" = "❌ İşlem iptal edildi! \n\nİstediğiniz zaman /start ile yeniden başlayabilirsiniz. 🔄" -"error_add_client" = "⚠️ Hata:\n\n {{ .error }}" -"using_default_value" = "Tamam, varsayılan değeri kullanacağım. 😊" -"incorrect_input" ="Girdiğiniz değer geçerli değil.\nKelime öbekleri boşluk olmadan devam etmelidir.\nDoğru örnek: aaaaaa\nYanlış örnek: aaa aaa 🚫" +"error_add_client" = "⚠️ Hata:\n\n {{ .error }}" +"using_default_value" = "Tamam, varsayılan değeri kullanacağım. 😊" +"incorrect_input" = "Girdiğiniz değer geçerli değil.\nKelime öbekleri boşluk olmadan devam etmelidir.\nDoğru örnek: aaaaaa\nYanlış örnek: aaa aaa 🚫" "AreYouSure" = "Emin misin? 🤔" "SuccessResetTraffic" = "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ✅ Başarılı" "FailedResetTraffic" = "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ❌ Başarısız \n\n🛠️ Hata: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index e8312fb4..d53b0bc0 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -106,7 +106,7 @@ "invalidFormData" = "Формат вхідних даних недійсний." "emptyUsername" = "Потрібне ім'я користувача" "emptyPassword" = "Потрібен пароль" -"wrongUsernameOrPassword" = "Невірне ім’я користувача, пароль або код двофакторної аутентифікації." +"wrongUsernameOrPassword" = "Невірне ім’я користувача, пароль або код двофакторної аутентифікації." "successLogin" = "Ви успішно увійшли до свого облікового запису." [pages.index] @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "Облікові дані адміністратора" -"twoFactor" = "Двофакторна аутентифікація" -"twoFactorEnable" = "Увімкнути 2FA" -"twoFactorEnableDesc" = "Додає додатковий рівень аутентифікації для підвищення безпеки." +"twoFactor" = "Двофакторна аутентифікація" +"twoFactorEnable" = "Увімкнути 2FA" +"twoFactorEnableDesc" = "Додає додатковий рівень аутентифікації для підвищення безпеки." "twoFactorModalSetTitle" = "Увімкнути двофакторну аутентифікацію" "twoFactorModalDeleteTitle" = "Вимкнути двофакторну аутентифікацію" "twoFactorModalSteps" = "Щоб налаштувати двофакторну аутентифікацію, виконайте кілька кроків:" @@ -663,6 +663,7 @@ "active" = "💡 Активний: {{ .Enable }}\r\n" "enabled" = "🚨 Увімкнено: {{ .Enable }}\r\n" "online" = "🌐 Стан підключення: {{ .Status }}\r\n" +"lastOnline" = "🔙 Був(ла) онлайн: {{ .Time }}\r\n" "email" = "📧 Електронна пошта: {{ .Email }}\r\n" "upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" "download" = "🔽 Download: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Вхід: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Електронна пошта: {{ .ClientEmail }}\n📊 Трафік: {{ .ClientTraffic }}\n📅 Дата завершення: {{ .ClientExp }}\n🌐 Обмеження IP: {{ .IpLimit }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідного з'єднання!" "inbound_client_data_pass" = "🔄 Вхід: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Електронна пошта: {{ .ClientEmail }}\n📊 Трафік: {{ .ClientTraffic }}\n📅 Дата завершення: {{ .ClientExp }}\n🌐 Обмеження IP: {{ .IpLimit }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідного з'єднання!" "cancel" = "❌ Процес скасовано! \n\nВи можете знову розпочати, використовуючи /start у будь-який час. 🔄" -"error_add_client" = "⚠️ Помилка:\n\n {{ .error }}" -"using_default_value" = "Гаразд, залишу значення за замовчуванням. 😊" -"incorrect_input" ="Ваш ввід невірний.\nФрази повинні бути без пробілів.\nПравильний приклад: aaaaaa\nНеправильний приклад: aaa aaa 🚫" +"error_add_client" = "⚠️ Помилка:\n\n {{ .error }}" +"using_default_value" = "Гаразд, залишу значення за замовчуванням. 😊" +"incorrect_input" = "Ваш ввід невірний.\nФрази повинні бути без пробілів.\nПравильний приклад: aaaaaa\nНеправильний приклад: aaa aaa 🚫" "AreYouSure" = "Ви впевнені? 🤔" "SuccessResetTraffic" = "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ✅ Успішно" "FailedResetTraffic" = "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ❌ Невдача \n\n🛠️ Помилка: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 787451ae..cb21cc3b 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -663,6 +663,7 @@ "active" = "💡 Đang hoạt động: {{ .Enable }}\r\n" "enabled" = "🚨 Đã bật: {{ .Enable }}\r\n" "online" = "🌐 Trạng thái kết nối: {{ .Status }}\r\n" +"lastOnline" = "🔙 Lần online gần nhất: {{ .Time }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n" "download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 Kết nối vào: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Dung lượng: {{ .ClientTraffic }}\n📅 Ngày hết hạn: {{ .ClientExp }}\n🌐 Giới hạn IP: {{ .IpLimit }}\n💬 Ghi chú: {{ .ClientComment }}\n\nBây giờ bạn có thể thêm khách hàng vào inbound!" "inbound_client_data_pass" = "🔄 Kết nối vào: {{ .InboundRemark }}\n\n🔑 Mật khẩu: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Dung lượng: {{ .ClientTraffic }}\n📅 Ngày hết hạn: {{ .ClientExp }}\n🌐 Giới hạn IP: {{ .IpLimit }}\n💬 Ghi chú: {{ .ClientComment }}\n\nBây giờ bạn có thể thêm khách hàng vào inbound!" "cancel" = "❌ Quá trình đã bị hủy! \n\nBạn có thể bắt đầu lại bất cứ lúc nào bằng cách nhập /start. 🔄" -"error_add_client" = "⚠️ Lỗi:\n\n {{ .error }}" -"using_default_value" = "Được rồi, tôi sẽ sử dụng giá trị mặc định. 😊" -"incorrect_input" ="Dữ liệu bạn nhập không hợp lệ.\nCác chuỗi phải liền mạch và không có dấu cách.\nVí dụ đúng: aaaaaa\nVí dụ sai: aaa aaa 🚫" +"error_add_client" = "⚠️ Lỗi:\n\n {{ .error }}" +"using_default_value" = "Được rồi, tôi sẽ sử dụng giá trị mặc định. 😊" +"incorrect_input" = "Dữ liệu bạn nhập không hợp lệ.\nCác chuỗi phải liền mạch và không có dấu cách.\nVí dụ đúng: aaaaaa\nVí dụ sai: aaa aaa 🚫" "AreYouSure" = "Bạn có chắc không? 🤔" "SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ✅ Thành công" "FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ❌ Thất bại \n\n🛠️ Lỗi: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index b11f9073..2ee5f9a0 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -106,7 +106,7 @@ "invalidFormData" = "数据格式错误" "emptyUsername" = "请输入用户名" "emptyPassword" = "请输入密码" -"wrongUsernameOrPassword" = "用户名、密码或双重验证码无效。" +"wrongUsernameOrPassword" = "用户名、密码或双重验证码无效。" "successLogin" = "您已成功登录您的账户。" [pages.index] @@ -242,7 +242,7 @@ "same" = "相同" "inboundData" = "入站数据" "exportInbound" = "导出入站规则" -"import"="导入" +"import" = "导入" "importInbound" = "导入入站规则" "periodicTrafficResetTitle" = "流量重置" "periodicTrafficResetDesc" = "按指定间隔自动重置流量计数器" @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "管理员凭据" -"twoFactor" = "双重验证" -"twoFactorEnable" = "启用2FA" -"twoFactorEnableDesc" = "增加额外的验证层以提高安全性。" +"twoFactor" = "双重验证" +"twoFactorEnable" = "启用2FA" +"twoFactorEnableDesc" = "增加额外的验证层以提高安全性。" "twoFactorModalSetTitle" = "启用双重认证" "twoFactorModalDeleteTitle" = "停用双重认证" "twoFactorModalSteps" = "要设定双重认证,请执行以下步骤:" @@ -663,6 +663,7 @@ "active" = "💡 激活:{{ .Enable }}\r\n" "enabled" = "🚨 已启用:{{ .Enable }}\r\n" "online" = "🌐 连接状态:{{ .Status }}\r\n" +"lastOnline" = "🔙 上次在线: {{ .Time }}\r\n" "email" = "📧 邮箱:{{ .Email }}\r\n" "upload" = "🔼 上传↑:{{ .Upload }}\r\n" "download" = "🔽 下载↓:{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 邮箱: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日期: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 备注: {{ .ClientComment }}\n\n你现在可以将客户添加到入站了!" "inbound_client_data_pass" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 密码: {{ .ClientPass }}\n📧 邮箱: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日期: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 备注: {{ .ClientComment }}\n\n你现在可以将客户添加到入站了!" "cancel" = "❌ 进程已取消!\n\n您可以随时使用 /start 重新开始。 🔄" -"error_add_client" = "⚠️ 错误:\n\n {{ .error }}" -"using_default_value" = "好的,我会使用默认值。 😊" -"incorrect_input" ="您的输入无效。\n短语应连续输入,不能有空格。\n正确示例: aaaaaa\n错误示例: aaa aaa 🚫" +"error_add_client" = "⚠️ 错误:\n\n {{ .error }}" +"using_default_value" = "好的,我会使用默认值。 😊" +"incorrect_input" = "您的输入无效。\n短语应连续输入,不能有空格。\n正确示例: aaaaaa\n错误示例: aaa aaa 🚫" "AreYouSure" = "你确定吗?🤔" "SuccessResetTraffic" = "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ✅ 成功" "FailedResetTraffic" = "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml index ad1bdab9..dc600d21 100644 --- a/web/translation/translate.zh_TW.toml +++ b/web/translation/translate.zh_TW.toml @@ -106,7 +106,7 @@ "invalidFormData" = "資料格式錯誤" "emptyUsername" = "請輸入使用者名稱" "emptyPassword" = "請輸入密碼" -"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。" +"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。" "successLogin" = "您已成功登入您的帳戶。" [pages.index] @@ -242,7 +242,7 @@ "same" = "相同" "inboundData" = "入站資料" "exportInbound" = "匯出入站規則" -"import"="匯入" +"import" = "匯入" "importInbound" = "匯入入站規則" "periodicTrafficResetTitle" = "流量重置" "periodicTrafficResetDesc" = "按指定間隔自動重置流量計數器" @@ -565,9 +565,9 @@ [pages.settings.security] "admin" = "管理員憑證" -"twoFactor" = "雙重驗證" -"twoFactorEnable" = "啟用2FA" -"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。" +"twoFactor" = "雙重驗證" +"twoFactorEnable" = "啟用2FA" +"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。" "twoFactorModalSetTitle" = "啟用雙重認證" "twoFactorModalDeleteTitle" = "停用雙重認證" "twoFactorModalSteps" = "要設定雙重認證,請執行以下步驟:" @@ -663,6 +663,7 @@ "active" = "💡 啟用:{{ .Enable }}\r\n" "enabled" = "🚨 已啟用:{{ .Enable }}\r\n" "online" = "🌐 連線狀態:{{ .Status }}\r\n" +"lastOnline" = "🔙 上次上線: {{ .Time }}\r\n" "email" = "📧 郵箱:{{ .Email }}\r\n" "upload" = "🔼 上傳↑:{{ .Upload }}\r\n" "download" = "🔽 下載↓:{{ .Download }}\r\n" @@ -688,9 +689,9 @@ "inbound_client_data_id" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n你現在可以將客戶加入入站了!" "inbound_client_data_pass" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 密碼: {{ .ClientPass }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n你現在可以將客戶加入入站了!" "cancel" = "❌ 程序已取消!\n\n您可以隨時使用 /start 重新開始。 🔄" -"error_add_client" = "⚠️ 錯誤:\n\n {{ .error }}" -"using_default_value" = "好的,我會使用預設值。 😊" -"incorrect_input" ="您的輸入無效。\n短語應連續輸入,不能有空格。\n正確示例: aaaaaa\n錯誤示例: aaa aaa 🚫" +"error_add_client" = "⚠️ 錯誤:\n\n {{ .error }}" +"using_default_value" = "好的,我會使用預設值。 😊" +"incorrect_input" = "您的輸入無效。\n短語應連續輸入,不能有空格。\n正確示例: aaaaaa\n錯誤示例: aaa aaa 🚫" "AreYouSure" = "你確定嗎?🤔" "SuccessResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ✅ 成功" "FailedResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]" From 83a1c721c761b82c719a51870e78f24d50f3a028 Mon Sep 17 00:00:00 2001 From: Roman Gogolev Date: Wed, 3 Dec 2025 16:58:54 +0300 Subject: [PATCH 21/26] Fix int64 for 32-bit arch (#3591) * fix int64 for 32-bit arch * Update web/service/tgbot.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/service/tgbot.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index cac49dd9..06c51faa 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -210,7 +210,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error { // Parse admin IDs from comma-separated string if tgBotID != "" { for _, adminID := range strings.Split(tgBotID, ",") { - id, err := strconv.Atoi(adminID) + id, err := strconv.ParseInt(adminID, 10, 64) if err != nil { logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err) return err @@ -905,8 +905,8 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) case "add_client_limit_traffic_c": - limitTraffic, _ := strconv.Atoi(dataArray[1]) - client_TotalGB = int64(limitTraffic) * 1024 * 1024 * 1024 + limitTraffic, _ := strconv.ParseInt(dataArray[1], 10, 64) + client_TotalGB = limitTraffic * 1024 * 1024 * 1024 messageId := callbackQuery.Message.GetMessageID() inbound, err := t.inboundService.GetInbound(receiver_inbound_ID) if err != nil { @@ -1010,7 +1010,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) case "reset_exp_c": if len(dataArray) == 3 { - days, err := strconv.Atoi(dataArray[2]) + days, err := strconv.ParseInt(dataArray[2], 10, 64) if err == nil { var date int64 if days > 0 { @@ -1115,7 +1115,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool t.searchClient(chatId, email, callbackQuery.Message.GetMessageID()) case "add_client_reset_exp_c": client_ExpiryTime = 0 - days, _ := strconv.Atoi(dataArray[1]) + days, _ := strconv.ParseInt(dataArray[1], 10, 64) var date int64 if client_ExpiryTime > 0 { if client_ExpiryTime-time.Now().Unix()*1000 < 0 { From e8c509c720267f6da34b9a43104f83f3dd77ef88 Mon Sep 17 00:00:00 2001 From: JieXu Date: Thu, 4 Dec 2025 04:40:49 +0800 Subject: [PATCH 22/26] Update for Red Hat base Linux (#3589) * Update install.sh * Update update.sh * Update x-ui.sh * Update install.sh * Update update.sh * Update x-ui.sh * fix --- install.sh | 90 +++++++------ update.sh | 376 +++++++++++++++++++++++++++-------------------------- x-ui.sh | 52 +++++--- 3 files changed, 271 insertions(+), 247 deletions(-) diff --git a/install.sh b/install.sh index 0ec2aaa4..1f1b37ff 100644 --- a/install.sh +++ b/install.sh @@ -15,7 +15,7 @@ cur_dir=$(pwd) if [[ -f /etc/os-release ]]; then source /etc/os-release release=$ID -elif [[ -f /usr/lib/os-release ]]; then + elif [[ -f /usr/lib/os-release ]]; then source /usr/lib/os-release release=$ID else @@ -26,14 +26,14 @@ echo "The OS release is: $release" arch() { case "$(uname -m)" in - x86_64 | x64 | amd64) echo 'amd64' ;; - i*86 | x86) echo '386' ;; - armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; - armv7* | armv7 | arm) echo 'armv7' ;; - armv6* | armv6) echo 'armv6' ;; - armv5* | armv5) echo 'armv5' ;; - s390x) echo 's390x' ;; - *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; + x86_64 | x64 | amd64) echo 'amd64' ;; + i*86 | x86) echo '386' ;; + armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; + armv7* | armv7 | arm) echo 'armv7' ;; + armv6* | armv6) echo 'armv6' ;; + armv5* | armv5) echo 'armv5' ;; + s390x) echo 's390x' ;; + *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; esac } @@ -41,26 +41,30 @@ echo "Arch: $(arch)" install_base() { case "${release}" in - ubuntu | debian | armbian) - apt-get update && apt-get install -y -q wget curl tar tzdata + ubuntu | debian | armbian) + apt-get update && apt-get install -y -q wget curl tar tzdata ;; - centos | rhel | almalinux | rocky | ol) - yum -y update && yum install -y -q wget curl tar tzdata + fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) + dnf -y update && dnf install -y -q wget curl tar tzdata ;; - fedora | amzn | virtuozzo) - dnf -y update && dnf install -y -q wget curl tar tzdata + centos) + if [[ "${VERSION_ID}" =~ ^7 ]]; then + yum -y update && yum install -y wget curl tar tzdata + else + dnf -y update && dnf install -y -q wget curl tar tzdata + fi ;; - arch | manjaro | parch) - pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata + arch | manjaro | parch) + pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata ;; - opensuse-tumbleweed | opensuse-leap) - zypper refresh && zypper -q install -y wget curl tar timezone + opensuse-tumbleweed | opensuse-leap) + zypper refresh && zypper -q install -y wget curl tar timezone ;; - alpine) - apk update && apk add wget curl tar tzdata + alpine) + apk update && apk add wget curl tar tzdata ;; - *) - apt-get update && apt-get install -y -q wget curl tar tzdata + *) + apt-get update && apt-get install -y -q wget curl tar tzdata ;; esac } @@ -77,11 +81,11 @@ config_after_install() { 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" - "https://ipv4.icanhazip.com" - "https://v4.api.ipinfo.io/ip" - "https://ipv4.myexternalip.com/raw" - "https://4.ident.me" - "https://check-host.net/ip" + "https://ipv4.icanhazip.com" + "https://v4.api.ipinfo.io/ip" + "https://ipv4.myexternalip.com/raw" + "https://4.ident.me" + "https://check-host.net/ip" ) local server_ip="" for ip_address in "${URL_lists[@]}"; do @@ -90,13 +94,13 @@ config_after_install() { break fi done - + if [[ ${#existing_webBasePath} -lt 4 ]]; then if [[ "$existing_hasDefaultCredential" == "true" ]]; then local config_webBasePath=$(gen_random_string 18) local config_username=$(gen_random_string 10) local config_password=$(gen_random_string 10) - + read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then read -rp "Please set up the panel port: " config_port @@ -105,7 +109,7 @@ config_after_install() { local config_port=$(shuf -i 1024-62000 -n 1) echo -e "${yellow}Generated random port: ${config_port}${plain}" fi - + /usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}" echo -e "This is a fresh installation, generating random login info for security concerns:" echo -e "###############################################" @@ -126,7 +130,7 @@ config_after_install() { if [[ "$existing_hasDefaultCredential" == "true" ]]; then local config_username=$(gen_random_string 10) local config_password=$(gen_random_string 10) - + echo -e "${yellow}Default credentials detected. Security update required...${plain}" /usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" echo -e "Generated new random login credentials:" @@ -138,13 +142,13 @@ config_after_install() { echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}" fi fi - + /usr/local/x-ui/x-ui migrate } install_x-ui() { cd /usr/local/ - + # Download resources if [ $# == 0 ]; then tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') @@ -166,12 +170,12 @@ install_x-ui() { tag_version=$1 tag_version_numeric=${tag_version#v} min_version="2.3.5" - + if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}" exit 1 fi - + url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" echo -e "Beginning to install x-ui $1" wget --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url} @@ -185,7 +189,7 @@ install_x-ui() { echo -e "${red}Failed to download x-ui.sh${plain}" exit 1 fi - + # Stop x-ui service and remove old resources if [[ -e /usr/local/x-ui/ ]]; then if [[ $release == "alpine" ]]; then @@ -195,7 +199,7 @@ install_x-ui() { fi rm /usr/local/x-ui/ -rf fi - + # Extract resources and set permissions tar zxvf x-ui-linux-$(arch).tar.gz rm x-ui-linux-$(arch).tar.gz -f @@ -203,19 +207,19 @@ install_x-ui() { cd x-ui chmod +x x-ui chmod +x x-ui.sh - + # Check the system's architecture and rename the file accordingly if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then mv bin/xray-linux-$(arch) bin/xray-linux-arm chmod +x bin/xray-linux-arm fi chmod +x x-ui bin/xray-linux-$(arch) - + # Update x-ui cli and se set permission mv -f /usr/bin/x-ui-temp /usr/bin/x-ui chmod +x /usr/bin/x-ui config_after_install - + if [[ $release == "alpine" ]]; then wget --inet4-only -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc if [[ $? -ne 0 ]]; then @@ -231,7 +235,7 @@ install_x-ui() { systemctl enable x-ui systemctl start x-ui fi - + echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..." echo -e "" echo -e "┌───────────────────────────────────────────────────────┐ @@ -251,7 +255,7 @@ install_x-ui() { │ ${blue}x-ui legacy${plain} - Legacy version │ │ ${blue}x-ui install${plain} - Install │ │ ${blue}x-ui uninstall${plain} - Uninstall │ -└───────────────────────────────────────────────────────┘" + └───────────────────────────────────────────────────────┘" } echo -e "${green}Running...${plain}" diff --git a/update.sh b/update.sh index 793c5097..fb4af567 100755 --- a/update.sh +++ b/update.sh @@ -9,233 +9,237 @@ plain='\033[0m' # Don't edit this config b_source="${BASH_SOURCE[0]}" while [ -h "$b_source" ]; do - b_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)" - b_source="$(readlink "$b_source")" - [[ $b_source != /* ]] && b_source="$b_dir/$b_source" + b_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)" + b_source="$(readlink "$b_source")" + [[ $b_source != /* ]] && b_source="$b_dir/$b_source" done cur_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)" script_name=$(basename "$0") # Check command exist function _command_exists() { - type "$1" &>/dev/null + type "$1" &>/dev/null } # Fail, log and exit script function _fail() { - local msg=${1} - echo -e "${red}${msg}${plain}" - exit 2 + local msg=${1} + echo -e "${red}${msg}${plain}" + exit 2 } # check root [[ $EUID -ne 0 ]] && _fail "FATAL ERROR: Please run this script with root privilege." if _command_exists wget; then - wget_bin=$(which wget) + wget_bin=$(which wget) else - _fail "ERROR: Command 'wget' not found." + _fail "ERROR: Command 'wget' not found." fi if _command_exists curl; then - curl_bin=$(which curl) + curl_bin=$(which curl) else - _fail "ERROR: Command 'curl' not found." + _fail "ERROR: Command 'curl' not found." fi # Check OS and set release variable if [[ -f /etc/os-release ]]; then - source /etc/os-release - release=$ID -elif [[ -f /usr/lib/os-release ]]; then - source /usr/lib/os-release - release=$ID + source /etc/os-release + release=$ID + elif [[ -f /usr/lib/os-release ]]; then + source /usr/lib/os-release + release=$ID else - _fail "Failed to check the system OS, please contact the author!" + _fail "Failed to check the system OS, please contact the author!" fi echo "The OS release is: $release" arch() { - case "$(uname -m)" in - x86_64 | x64 | amd64) echo 'amd64' ;; - i*86 | x86) echo '386' ;; - armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; - armv7* | armv7 | arm) echo 'armv7' ;; - armv6* | armv6) echo 'armv6' ;; - armv5* | armv5) echo 'armv5' ;; - s390x) echo 's390x' ;; - *) echo -e "${red}Unsupported CPU architecture!${plain}" && rm -f "${cur_dir}/${script_name}" >/dev/null 2>&1 && exit 2;; - esac + case "$(uname -m)" in + x86_64 | x64 | amd64) echo 'amd64' ;; + i*86 | x86) echo '386' ;; + armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; + armv7* | armv7 | arm) echo 'armv7' ;; + armv6* | armv6) echo 'armv6' ;; + armv5* | armv5) echo 'armv5' ;; + s390x) echo 's390x' ;; + *) echo -e "${red}Unsupported CPU architecture!${plain}" && rm -f "${cur_dir}/${script_name}" >/dev/null 2>&1 && exit 2;; + esac } echo "Arch: $(arch)" install_base() { - echo -e "${green}Updating and install dependency packages...${plain}" - case "${release}" in - ubuntu | debian | armbian) - apt-get update >/dev/null 2>&1 && apt-get install -y -q wget curl tar tzdata >/dev/null 2>&1 - ;; - centos | rhel | almalinux | rocky | ol) - yum -y update >/dev/null 2>&1 && yum install -y -q wget curl tar tzdata >/dev/null 2>&1 - ;; - fedora | amzn | virtuozzo) - dnf -y update >/dev/null 2>&1 && dnf install -y -q wget curl tar tzdata >/dev/null 2>&1 - ;; - arch | manjaro | parch) - pacman -Syu >/dev/null 2>&1 && pacman -Syu --noconfirm wget curl tar tzdata >/dev/null 2>&1 - ;; - opensuse-tumbleweed | opensuse-leap) - zypper refresh >/dev/null 2>&1 && zypper -q install -y wget curl tar timezone >/dev/null 2>&1 - ;; - alpine) - apk update >/dev/null 2>&1 && apk add wget curl tar tzdata >/dev/null 2>&1 - ;; - *) - apt-get update >/dev/null 2>&1 && apt install -y -q wget curl tar tzdata >/dev/null 2>&1 - ;; - esac + echo -e "${green}Updating and install dependency packages...${plain}" + case "${release}" in + ubuntu | debian | armbian) + apt-get update >/dev/null 2>&1 && apt-get install -y -q wget curl tar tzdata >/dev/null 2>&1 + ;; + fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) + dnf -y update >/dev/null 2>&1 && dnf install -y -q wget curl tar tzdata >/dev/null 2>&1 + ;; + centos) + if [[ "${VERSION_ID}" =~ ^7 ]]; then + yum -y update >/dev/null 2>&1 && yum install -y -q wget curl tar tzdata >/dev/null 2>&1 + else + dnf -y update >/dev/null 2>&1 && dnf install -y -q wget curl tar tzdata >/dev/null 2>&1 + fi + ;; + arch | manjaro | parch) + pacman -Syu >/dev/null 2>&1 && pacman -Syu --noconfirm wget curl tar tzdata >/dev/null 2>&1 + ;; + opensuse-tumbleweed | opensuse-leap) + zypper refresh >/dev/null 2>&1 && zypper -q install -y wget curl tar timezone >/dev/null 2>&1 + ;; + alpine) + apk update >/dev/null 2>&1 && apk add wget curl tar tzdata >/dev/null 2>&1 + ;; + *) + apt-get update >/dev/null 2>&1 && apt install -y -q wget curl tar tzdata >/dev/null 2>&1 + ;; + esac } config_after_update() { - echo -e "${yellow}x-ui settings:${plain}" - /usr/local/x-ui/x-ui setting -show true - /usr/local/x-ui/x-ui migrate + echo -e "${yellow}x-ui settings:${plain}" + /usr/local/x-ui/x-ui setting -show true + /usr/local/x-ui/x-ui migrate } update_x-ui() { - cd /usr/local/ - - if [ -f "/usr/local/x-ui/x-ui" ]; then - current_xui_version=$(/usr/local/x-ui/x-ui -v) - echo -e "${green}Current x-ui version: ${current_xui_version}${plain}" - else - _fail "ERROR: Current x-ui version: unknown" - fi - - echo -e "${green}Downloading new x-ui version...${plain}" - - tag_version=$(${curl_bin} -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - if [[ ! -n "$tag_version" ]]; then - echo -e "${yellow}Trying to fetch version with IPv4...${plain}" - tag_version=$(${curl_bin} -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - if [[ ! -n "$tag_version" ]]; then - _fail "ERROR: Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later" - fi - fi - echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." - ${wget_bin} -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null - if [[ $? -ne 0 ]]; then - echo -e "${yellow}Trying to fetch version with IPv4...${plain}" - ${wget_bin} --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null - if [[ $? -ne 0 ]]; then - _fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub" - fi - fi - - if [[ -e /usr/local/x-ui/ ]]; then - echo -e "${green}Stopping x-ui...${plain}" - if [[ $release == "alpine" ]]; then - if [ -f "/etc/init.d/x-ui" ]; then - rc-service x-ui stop >/dev/null 2>&1 - rc-update del x-ui >/dev/null 2>&1 - echo -e "${green}Removing old service unit version...${plain}" - rm -f /etc/init.d/x-ui >/dev/null 2>&1 - else - rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 - _fail "ERROR: x-ui service unit not installed." - fi - else - if [ -f "/etc/systemd/system/x-ui.service" ]; then - systemctl stop x-ui >/dev/null 2>&1 - systemctl disable x-ui >/dev/null 2>&1 - echo -e "${green}Removing old systemd unit version...${plain}" - rm /etc/systemd/system/x-ui.service -f >/dev/null 2>&1 - systemctl daemon-reload >/dev/null 2>&1 - else - rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 - _fail "ERROR: x-ui systemd unit not installed." - fi - fi - echo -e "${green}Removing old x-ui version...${plain}" - rm /usr/bin/x-ui -f >/dev/null 2>&1 - rm /usr/local/x-ui/x-ui.service -f >/dev/null 2>&1 - rm /usr/local/x-ui/x-ui -f >/dev/null 2>&1 - rm /usr/local/x-ui/x-ui.sh -f >/dev/null 2>&1 - echo -e "${green}Removing old xray version...${plain}" - rm /usr/local/x-ui/bin/xray-linux-amd64 -f >/dev/null 2>&1 - echo -e "${green}Removing old README and LICENSE file...${plain}" - rm /usr/local/x-ui/bin/README.md -f >/dev/null 2>&1 - rm /usr/local/x-ui/bin/LICENSE -f >/dev/null 2>&1 - else - rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 - _fail "ERROR: x-ui not installed." - fi - - echo -e "${green}Installing new x-ui version...${plain}" - tar zxvf x-ui-linux-$(arch).tar.gz >/dev/null 2>&1 - rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 - cd x-ui >/dev/null 2>&1 - chmod +x x-ui >/dev/null 2>&1 - - # Check the system's architecture and rename the file accordingly - if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then - mv bin/xray-linux-$(arch) bin/xray-linux-arm >/dev/null 2>&1 - chmod +x bin/xray-linux-arm >/dev/null 2>&1 - fi - - chmod +x x-ui bin/xray-linux-$(arch) >/dev/null 2>&1 - - echo -e "${green}Downloading and installing x-ui.sh script...${plain}" - ${wget_bin} -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - echo -e "${yellow}Trying to fetch x-ui with IPv4...${plain}" - ${wget_bin} --inet4-only -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - _fail "ERROR: Failed to download x-ui.sh script, please be sure that your server can access GitHub" - fi - fi - - chmod +x /usr/local/x-ui/x-ui.sh >/dev/null 2>&1 - chmod +x /usr/bin/x-ui >/dev/null 2>&1 - - echo -e "${green}Changing owner...${plain}" - chown -R root:root /usr/local/x-ui >/dev/null 2>&1 - - if [ -f "/usr/local/x-ui/bin/config.json" ]; then - echo -e "${green}Changing on config file permissions...${plain}" - chmod 640 /usr/local/x-ui/bin/config.json >/dev/null 2>&1 - fi - - if [[ $release == "alpine" ]]; then - echo -e "${green}Downloading and installing startup unit x-ui.rc...${plain}" - ${wget_bin} -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - ${wget_bin} --inet4-only -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - _fail "ERROR: Failed to download startup unit x-ui.rc, please be sure that your server can access GitHub" - fi - fi - chmod +x /etc/init.d/x-ui >/dev/null 2>&1 - chown root:root /etc/init.d/x-ui >/dev/null 2>&1 - rc-update add x-ui >/dev/null 2>&1 - rc-service x-ui start >/dev/null 2>&1 - else - echo -e "${green}Installing systemd unit...${plain}" - cp -f x-ui.service /etc/systemd/system/ >/dev/null 2>&1 - chown root:root /etc/systemd/system/x-ui.service >/dev/null 2>&1 - systemctl daemon-reload >/dev/null 2>&1 - systemctl enable x-ui >/dev/null 2>&1 - systemctl start x-ui >/dev/null 2>&1 - fi - - config_after_update - - echo -e "${green}x-ui ${tag_version}${plain} updating finished, it is running now..." - echo -e "" - echo -e "┌───────────────────────────────────────────────────────┐ + cd /usr/local/ + + if [ -f "/usr/local/x-ui/x-ui" ]; then + current_xui_version=$(/usr/local/x-ui/x-ui -v) + echo -e "${green}Current x-ui version: ${current_xui_version}${plain}" + else + _fail "ERROR: Current x-ui version: unknown" + fi + + echo -e "${green}Downloading new x-ui version...${plain}" + + tag_version=$(${curl_bin} -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$tag_version" ]]; then + echo -e "${yellow}Trying to fetch version with IPv4...${plain}" + tag_version=$(${curl_bin} -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$tag_version" ]]; then + _fail "ERROR: Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later" + fi + fi + echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." + ${wget_bin} -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null + if [[ $? -ne 0 ]]; then + echo -e "${yellow}Trying to fetch version with IPv4...${plain}" + ${wget_bin} --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null + if [[ $? -ne 0 ]]; then + _fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub" + fi + fi + + if [[ -e /usr/local/x-ui/ ]]; then + echo -e "${green}Stopping x-ui...${plain}" + if [[ $release == "alpine" ]]; then + if [ -f "/etc/init.d/x-ui" ]; then + rc-service x-ui stop >/dev/null 2>&1 + rc-update del x-ui >/dev/null 2>&1 + echo -e "${green}Removing old service unit version...${plain}" + rm -f /etc/init.d/x-ui >/dev/null 2>&1 + else + rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 + _fail "ERROR: x-ui service unit not installed." + fi + else + if [ -f "/etc/systemd/system/x-ui.service" ]; then + systemctl stop x-ui >/dev/null 2>&1 + systemctl disable x-ui >/dev/null 2>&1 + echo -e "${green}Removing old systemd unit version...${plain}" + rm /etc/systemd/system/x-ui.service -f >/dev/null 2>&1 + systemctl daemon-reload >/dev/null 2>&1 + else + rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 + _fail "ERROR: x-ui systemd unit not installed." + fi + fi + echo -e "${green}Removing old x-ui version...${plain}" + rm /usr/bin/x-ui -f >/dev/null 2>&1 + rm /usr/local/x-ui/x-ui.service -f >/dev/null 2>&1 + rm /usr/local/x-ui/x-ui -f >/dev/null 2>&1 + rm /usr/local/x-ui/x-ui.sh -f >/dev/null 2>&1 + echo -e "${green}Removing old xray version...${plain}" + rm /usr/local/x-ui/bin/xray-linux-amd64 -f >/dev/null 2>&1 + echo -e "${green}Removing old README and LICENSE file...${plain}" + rm /usr/local/x-ui/bin/README.md -f >/dev/null 2>&1 + rm /usr/local/x-ui/bin/LICENSE -f >/dev/null 2>&1 + else + rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 + _fail "ERROR: x-ui not installed." + fi + + echo -e "${green}Installing new x-ui version...${plain}" + tar zxvf x-ui-linux-$(arch).tar.gz >/dev/null 2>&1 + rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1 + cd x-ui >/dev/null 2>&1 + chmod +x x-ui >/dev/null 2>&1 + + # Check the system's architecture and rename the file accordingly + if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then + mv bin/xray-linux-$(arch) bin/xray-linux-arm >/dev/null 2>&1 + chmod +x bin/xray-linux-arm >/dev/null 2>&1 + fi + + chmod +x x-ui bin/xray-linux-$(arch) >/dev/null 2>&1 + + echo -e "${green}Downloading and installing x-ui.sh script...${plain}" + ${wget_bin} -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + echo -e "${yellow}Trying to fetch x-ui with IPv4...${plain}" + ${wget_bin} --inet4-only -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + _fail "ERROR: Failed to download x-ui.sh script, please be sure that your server can access GitHub" + fi + fi + + chmod +x /usr/local/x-ui/x-ui.sh >/dev/null 2>&1 + chmod +x /usr/bin/x-ui >/dev/null 2>&1 + + echo -e "${green}Changing owner...${plain}" + chown -R root:root /usr/local/x-ui >/dev/null 2>&1 + + if [ -f "/usr/local/x-ui/bin/config.json" ]; then + echo -e "${green}Changing on config file permissions...${plain}" + chmod 640 /usr/local/x-ui/bin/config.json >/dev/null 2>&1 + fi + + if [[ $release == "alpine" ]]; then + echo -e "${green}Downloading and installing startup unit x-ui.rc...${plain}" + ${wget_bin} -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + ${wget_bin} --inet4-only -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + _fail "ERROR: Failed to download startup unit x-ui.rc, please be sure that your server can access GitHub" + fi + fi + chmod +x /etc/init.d/x-ui >/dev/null 2>&1 + chown root:root /etc/init.d/x-ui >/dev/null 2>&1 + rc-update add x-ui >/dev/null 2>&1 + rc-service x-ui start >/dev/null 2>&1 + else + echo -e "${green}Installing systemd unit...${plain}" + cp -f x-ui.service /etc/systemd/system/ >/dev/null 2>&1 + chown root:root /etc/systemd/system/x-ui.service >/dev/null 2>&1 + systemctl daemon-reload >/dev/null 2>&1 + systemctl enable x-ui >/dev/null 2>&1 + systemctl start x-ui >/dev/null 2>&1 + fi + + config_after_update + + echo -e "${green}x-ui ${tag_version}${plain} updating finished, it is running now..." + echo -e "" + echo -e "┌───────────────────────────────────────────────────────┐ │ ${blue}x-ui control menu usages (subcommands):${plain} │ -│ │ +│ │ │ ${blue}x-ui${plain} - Admin Management Script │ │ ${blue}x-ui start${plain} - Start │ │ ${blue}x-ui stop${plain} - Stop │ @@ -250,7 +254,7 @@ update_x-ui() { │ ${blue}x-ui legacy${plain} - Legacy version │ │ ${blue}x-ui install${plain} - Install │ │ ${blue}x-ui uninstall${plain} - Uninstall │ -└───────────────────────────────────────────────────────┘" + └───────────────────────────────────────────────────────┘" } echo -e "${green}Running...${plain}" diff --git a/x-ui.sh b/x-ui.sh index b492998b..6169d01b 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -509,12 +509,16 @@ enable_bbr() { ubuntu | debian | armbian) apt-get update && apt-get install -yqq --no-install-recommends ca-certificates ;; - centos | rhel | almalinux | rocky | ol) - yum -y update && yum -y install ca-certificates - ;; - fedora | amzn | virtuozzo) + fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) dnf -y update && dnf -y install ca-certificates ;; + centos) + if [[ "${VERSION_ID}" =~ ^7 ]]; then + yum -y update && yum -y install ca-certificates + else + dnf -y update && dnf -y install ca-certificates + fi + ;; arch | manjaro | parch) pacman -Sy --noconfirm ca-certificates ;; @@ -1073,12 +1077,15 @@ ssl_cert_issue() { ubuntu | debian | armbian) apt-get update && apt-get install socat -y ;; - centos | rhel | almalinux | rocky | ol) - yum -y update && yum -y install socat - ;; - fedora | amzn | virtuozzo) + fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) dnf -y update && dnf -y install socat ;; + centos) + if [[ "${VERSION_ID}" =~ ^7 ]]; then + yum -y update && yum -y install socat + else + dnf -y update && dnf -y install socat + fi arch | manjaro | parch) pacman -Sy --noconfirm socat ;; @@ -1537,13 +1544,17 @@ install_iplimit() { armbian) apt-get update && apt-get install fail2ban -y ;; - centos | rhel | almalinux | rocky | ol) - yum update -y && yum install epel-release -y - yum -y install fail2ban - ;; - fedora | amzn | virtuozzo) + fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) dnf -y update && dnf -y install fail2ban ;; + centos) + if [[ "${VERSION_ID}" =~ ^7 ]]; then + yum update -y && yum install epel-release -y + yum -y install fail2ban + else + dnf -y update && dnf -y install fail2ban + fi + ;; arch | manjaro | parch) pacman -Syu --noconfirm fail2ban ;; @@ -1637,14 +1648,19 @@ remove_iplimit() { apt-get purge -y fail2ban -y apt-get autoremove -y ;; - centos | rhel | almalinux | rocky | ol) - yum remove fail2ban -y - yum autoremove -y - ;; - fedora | amzn | virtuozzo) + fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) dnf remove fail2ban -y dnf autoremove -y ;; + centos) + if [[ "${VERSION_ID}" =~ ^7 ]]; then + yum remove fail2ban -y + yum autoremove -y + else + dnf remove fail2ban -y + dnf autoremove -y + fi + ;; arch | manjaro | parch) pacman -Rns --noconfirm fail2ban ;; From 70f6d6b21a6761444c54898a6ed48793e72e7177 Mon Sep 17 00:00:00 2001 From: "Danil S." <135337715+sh1shd@users.noreply.github.com> Date: Thu, 4 Dec 2025 05:37:27 +0700 Subject: [PATCH 23/26] chore: use `Intl` for date formatting (#3588) * chore: use `Intl` for date formatting * fix: show last traffic reset * chore: use raw timestamps * fix: remove unnecessary import --- web/assets/js/util/date-util.js | 151 ------------------ web/assets/js/util/index.js | 31 ++++ web/html/common/page.html | 1 - web/html/component/aClientTable.html | 70 ++------ web/html/form/inbound.html | 4 +- web/html/inbounds.html | 28 +--- web/html/index.html | 4 +- web/html/modals/inbound_info_modal.html | 21 +-- .../settings/panel/subscription/subpage.html | 25 +-- 9 files changed, 57 insertions(+), 278 deletions(-) delete mode 100644 web/assets/js/util/date-util.js diff --git a/web/assets/js/util/date-util.js b/web/assets/js/util/date-util.js deleted file mode 100644 index bbca1272..00000000 --- a/web/assets/js/util/date-util.js +++ /dev/null @@ -1,151 +0,0 @@ -const oneMinute = 1000 * 60; // MilliseConds in a Minute -const oneHour = oneMinute * 60; // The milliseconds of one hour -const oneDay = oneHour * 24; // The Number of MilliseConds A Day -const oneWeek = oneDay * 7; // The milliseconds per week -const oneMonth = oneDay * 30; // The milliseconds of a month - -/** - * Decrease according to the number of days - * - * @param days to reduce the number of days to be reduced - */ -Date.prototype.minusDays = function (days) { - return this.minusMillis(oneDay * days); -}; - -/** - * Increase according to the number of days - * - * @param days The number of days to be increased - */ -Date.prototype.plusDays = function (days) { - return this.plusMillis(oneDay * days); -}; - -/** - * A few - * - * @param hours to be reduced - */ -Date.prototype.minusHours = function (hours) { - return this.minusMillis(oneHour * hours); -}; - -/** - * Increase hourly - * - * @param hours to increase the number of hours - */ -Date.prototype.plusHours = function (hours) { - return this.plusMillis(oneHour * hours); -}; - -/** - * Make reduction in minutes - * - * @param minutes to reduce the number of minutes - */ -Date.prototype.minusMinutes = function (minutes) { - return this.minusMillis(oneMinute * minutes); -}; - -/** - * Add in minutes - * - * @param minutes to increase the number of minutes - */ -Date.prototype.plusMinutes = function (minutes) { - return this.plusMillis(oneMinute * minutes); -}; - -/** - * Decrease in milliseconds - * - * @param millis to reduce the milliseconds - */ -Date.prototype.minusMillis = function(millis) { - let time = this.getTime() - millis; - let newDate = new Date(); - newDate.setTime(time); - return newDate; -}; - -/** - * Add in milliseconds to increase - * - * @param millis to increase the milliseconds to increase - */ -Date.prototype.plusMillis = function(millis) { - let time = this.getTime() + millis; - let newDate = new Date(); - newDate.setTime(time); - return newDate; -}; - -/** - * Setting time is 00: 00: 00.000 on the day - */ -Date.prototype.setMinTime = function () { - this.setHours(0); - this.setMinutes(0); - this.setSeconds(0); - this.setMilliseconds(0); - return this; -}; - -/** - * Setting time is 23: 59: 59.999 on the same day - */ -Date.prototype.setMaxTime = function () { - this.setHours(23); - this.setMinutes(59); - this.setSeconds(59); - this.setMilliseconds(999); - return this; -}; - -/** - * Formatting date - */ -Date.prototype.formatDate = function () { - return this.getFullYear() + "-" + NumberFormatter.addZero(this.getMonth() + 1) + "-" + NumberFormatter.addZero(this.getDate()); -}; - -/** - * Format time - */ -Date.prototype.formatTime = function () { - return NumberFormatter.addZero(this.getHours()) + ":" + NumberFormatter.addZero(this.getMinutes()) + ":" + NumberFormatter.addZero(this.getSeconds()); -}; - -/** - * Formatting date plus time - * - * @param split Date and time separation symbols, default is a space - */ -Date.prototype.formatDateTime = function (split = ' ') { - return this.formatDate() + split + this.formatTime(); -}; - -class DateUtil { - // String to date object - static parseDate(str) { - return new Date(str.replace(/-/g, '/')); - } - - static formatMillis(millis) { - return moment(millis).format('YYYY-M-D HH:mm:ss'); - } - - static firstDayOfMonth() { - const date = new Date(); - date.setDate(1); - date.setMinTime(); - return date; - } - - static convertToJalalian(date) { - return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null; - } - -} diff --git a/web/assets/js/util/index.js b/web/assets/js/util/index.js index 902974f0..13bf68b1 100644 --- a/web/assets/js/util/index.js +++ b/web/assets/js/util/index.js @@ -882,4 +882,35 @@ class FileManager { link.remove(); } +} + +class IntlUtil { + static formatDate(date) { + const language = LanguageManager.getLanguage() + + let intlOptions = { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric" + } + + const intl = new Intl.DateTimeFormat( + language, + intlOptions + ) + + return intl.format(new Date(date)) + } + static formatRelativeTime(date) { + const language = LanguageManager.getLanguage() + const now = new Date() + + const diff = Math.round((date - now) / (1000 * 60 * 60 * 24)) + const formatter = new Intl.RelativeTimeFormat(language, { numeric: 'auto' }) + + return formatter.format(diff, 'day'); + } } \ No newline at end of file diff --git a/web/html/common/page.html b/web/html/common/page.html index f1c58fe1..c0a7ca63 100644 --- a/web/html/common/page.html +++ b/web/html/common/page.html @@ -44,7 +44,6 @@ - - {{ template "page/head_end" .}} @@ -141,17 +140,7 @@ From 0695f677ba006a36b9e54af990775a316c99fe58 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 3 Dec 2025 23:45:11 +0100 Subject: [PATCH 24/26] update dependencies --- go.mod | 37 ++++++++++++----------- go.sum | 93 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 64 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 1814adf3..458eefcb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mhsanaei/3x-ui/v2 -go 1.25.4 +go 1.25.5 require ( github.com/gin-contrib/gzip v1.2.5 @@ -15,22 +15,22 @@ require ( github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml/v2 v2.2.4 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v4 v4.25.10 + github.com/shirou/gopsutil/v4 v4.25.11 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/valyala/fasthttp v1.68.0 github.com/xlzd/gotp v0.1.0 - github.com/xtls/xray-core v1.251015.0 + github.com/xtls/xray-core v1.251202.0 go.uber.org/atomic v1.11.0 - golang.org/x/crypto v0.43.0 + golang.org/x/crypto v0.45.0 golang.org/x/sys v0.38.0 - golang.org/x/text v0.30.0 - google.golang.org/grpc v1.76.0 + golang.org/x/text v0.31.0 + google.golang.org/grpc v1.77.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 ) require ( - github.com/Azure/go-ntlmssp v0.0.1 // indirect + github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.14.2 // indirect @@ -46,7 +46,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.28.0 // indirect - github.com/goccy/go-yaml v1.18.0 // indirect + github.com/goccy/go-yaml v1.19.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -57,9 +57,8 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/ratelimit v1.0.2 // indirect - github.com/klauspost/compress v1.18.1 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -69,16 +68,16 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pires/go-proxyproto v0.8.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.56.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.57.1 // indirect github.com/refraction-networking/utls v1.8.1 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sagernet/sing v0.7.13 // indirect github.com/sagernet/sing-shadowsocks v0.2.9 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect @@ -86,18 +85,18 @@ require ( github.com/valyala/fastjson v1.6.4 // indirect github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect - github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 // indirect + github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/tools v0.39.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/go.sum b/go.sum index 68c6a7eb..287a33bb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/go-ntlmssp v0.0.1 h1:NqbqUHiVYjwBDsxM1KrllG7rnoHpcp40EWrpffsgcUc= -github.com/Azure/go-ntlmssp v0.0.1/go.mod h1:P/Wrai1IsNvkfWRRN0jvRobt7ZJdz4sHQ3dOjiEGDt0= +github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= +github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= @@ -16,7 +16,6 @@ github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -58,8 +57,8 @@ github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0 github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= +github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -107,8 +106,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -146,10 +145,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= -github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= +github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo= github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -164,8 +163,8 @@ github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4 github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= -github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= -github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= +github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY= +github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -180,10 +179,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= @@ -202,26 +201,26 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= -github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU= -github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= -github.com/xtls/xray-core v1.251015.0 h1:P7b3vt8ShhH31k4h6VJ/Pxar3tY9eK+7S8eygd6rsP0= -github.com/xtls/xray-core v1.251015.0/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik= +github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM= +github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= +github.com/xtls/xray-core v1.251202.0 h1:VwoBnq9IRTbYWEBhR0CqEw2cNjTlXYH6WxzKbSjx+XE= +github.com/xtls/xray-core v1.251202.0/go.mod h1:kclzboEF0g6VBrp9/NXm8C0Aj64SDBt52OfthH1LSr4= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -230,12 +229,12 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -246,22 +245,22 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 68240061aa4b13f797cc61fc64b5be3ffbbdfed5 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 3 Dec 2025 23:45:28 +0100 Subject: [PATCH 25/26] Xray Core 25.12.2 --- .github/workflows/release.yml | 4 ++-- DockerInit.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08112c7b..6c2a6989 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: cd x-ui/bin # Download dependencies - Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.10.15/" + Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.12.2/" if [ "${{ matrix.platform }}" == "amd64" ]; then wget -q ${Xray_URL}Xray-linux-64.zip unzip Xray-linux-64.zip @@ -183,7 +183,7 @@ jobs: cd x-ui\bin # Download Xray for Windows - $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/" + $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.12.2/" Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath . Remove-Item "Xray-windows-64.zip" diff --git a/DockerInit.sh b/DockerInit.sh index fb603fb8..080af293 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -27,7 +27,7 @@ case $1 in esac mkdir -p build/bin cd build/bin -wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/Xray-linux-${ARCH}.zip" +wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.12.2/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" From 0ea8b5352a0482e6201faf55fe3963362e7a6d33 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 4 Dec 2025 00:05:21 +0100 Subject: [PATCH 26/26] fix --- install.sh | 2 +- update.sh | 2 +- x-ui.sh | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 1f1b37ff..fd730bca 100644 --- a/install.sh +++ b/install.sh @@ -255,7 +255,7 @@ install_x-ui() { │ ${blue}x-ui legacy${plain} - Legacy version │ │ ${blue}x-ui install${plain} - Install │ │ ${blue}x-ui uninstall${plain} - Uninstall │ - └───────────────────────────────────────────────────────┘" +└───────────────────────────────────────────────────────┘" } echo -e "${green}Running...${plain}" diff --git a/update.sh b/update.sh index fb4af567..66c0566c 100755 --- a/update.sh +++ b/update.sh @@ -254,7 +254,7 @@ update_x-ui() { │ ${blue}x-ui legacy${plain} - Legacy version │ │ ${blue}x-ui install${plain} - Install │ │ ${blue}x-ui uninstall${plain} - Uninstall │ - └───────────────────────────────────────────────────────┘" +└───────────────────────────────────────────────────────┘" } echo -e "${green}Running...${plain}" diff --git a/x-ui.sh b/x-ui.sh index 6169d01b..f8ead9d6 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -1086,6 +1086,7 @@ ssl_cert_issue() { else dnf -y update && dnf -y install socat fi + ;; arch | manjaro | parch) pacman -Sy --noconfirm socat ;;