浏览代码

update dependencies

mhsanaei 1 周之前
父节点
当前提交
b578a33518
共有 3 个文件被更改,包括 393 次插入394 次删除
  1. 9 10
      go.mod
  2. 20 20
      go.sum
  3. 364 364
      web/job/ldap_sync_job.go

+ 9 - 10
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
 )

+ 20 - 20
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=

+ 364 - 364
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")
-
-    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),
-    }
-
-    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
-    }
-
-    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)
-
-    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)
-                }
-            }
-        }
-    }
-
-    // --- 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)
-    }
-
-    // --- 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)
-        }
-    }
+	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
+	}
+
+	// --- 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))
+
+	// --- 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]
+		}
+	}
+
+	// --- 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
+
+	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 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)
+		}
+	}
 }
 
-
-
 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
-    }
-
-    // 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),
-    }
-
-    // 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()
+	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,
+		})
+	}
+
+	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
+	}
+
+	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
-    }
-
-    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
-        }
-
-        // 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
-        }
-
-        // 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
-                }
-
-                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")
-    }
+	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
+
+	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)
+			}
+		}
+
+		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]
+
+			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
+				}
+			}
+		}
+	}
+
+	// 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
-            }
-        }
-    }
-
-    // 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()
-    }
-
-    // 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)
-    }
+	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()
+	}
+
+	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) + `]}`
+
+	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()
 }
-
-