🚀 Some improvements for x-ui.sh and ip job (#665)
This commit is contained in:
@@ -5,23 +5,26 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
)
|
||||
|
||||
type CheckClientIpJob struct {
|
||||
xrayService service.XrayService
|
||||
}
|
||||
type CheckClientIpJob struct {}
|
||||
|
||||
var job *CheckClientIpJob
|
||||
var disAllowedIps []string
|
||||
var ipFiles = []string{
|
||||
xray.GetBlockedIPsPath(),
|
||||
xray.GetIPLimitLogPath(),
|
||||
xray.GetIPLimitBannedLogPath(),
|
||||
xray.GetAccessPersistentLogPath(),
|
||||
}
|
||||
|
||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||
job = new(CheckClientIpJob)
|
||||
@@ -31,37 +34,28 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
||||
func (j *CheckClientIpJob) Run() {
|
||||
logger.Debug("Check Client IP Job...")
|
||||
|
||||
if hasLimitIp() {
|
||||
//create log file for Fail2ban IP Limit
|
||||
logIpFile, err := os.OpenFile("/var/log/3xipl.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
checkError(err)
|
||||
defer logIpFile.Close()
|
||||
log.SetOutput(logIpFile)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
|
||||
//create file to collect access.log to another file accessp.log (p=persistent)
|
||||
logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
checkError(err)
|
||||
defer logAccessP.Close()
|
||||
|
||||
processLogFile()
|
||||
// create files required for iplimit if not exists
|
||||
for i := 0; i < len(ipFiles); i++ {
|
||||
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
j.checkError(err)
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
// check for limit ip
|
||||
if j.hasLimitIp() {
|
||||
j.processLogFile()
|
||||
}
|
||||
|
||||
// write to blocked ips
|
||||
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
||||
|
||||
// check if file exists, if not create one
|
||||
_, err := os.Stat(xray.GetBlockedIPsPath())
|
||||
if os.IsNotExist(err) {
|
||||
_, err = os.OpenFile(xray.GetBlockedIPsPath(), os.O_RDWR|os.O_CREATE, 0755)
|
||||
checkError(err)
|
||||
}
|
||||
err = os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
||||
checkError(err)
|
||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0644)
|
||||
j.checkError(err)
|
||||
}
|
||||
|
||||
func hasLimitIp() bool {
|
||||
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
|
||||
err := db.Model(model.Inbound{}).Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -83,11 +77,12 @@ func hasLimitIp() bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func processLogFile() {
|
||||
accessLogPath := GetAccessLogPath()
|
||||
func (j *CheckClientIpJob) processLogFile() {
|
||||
accessLogPath := xray.GetAccessLogPath()
|
||||
if accessLogPath == "" {
|
||||
logger.Warning("access.log doesn't exist in your config.json")
|
||||
return
|
||||
@@ -95,7 +90,7 @@ func processLogFile() {
|
||||
|
||||
data, err := os.ReadFile(accessLogPath)
|
||||
InboundClientIps := make(map[string][]string)
|
||||
checkError(err)
|
||||
j.checkError(err)
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
@@ -116,7 +111,7 @@ func processLogFile() {
|
||||
matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
|
||||
|
||||
if InboundClientIps[matchesEmail] != nil {
|
||||
if contains(InboundClientIps[matchesEmail], ip) {
|
||||
if j.contains(InboundClientIps[matchesEmail], ip) {
|
||||
continue
|
||||
}
|
||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||
@@ -125,68 +120,50 @@ func processLogFile() {
|
||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
disAllowedIps = []string{}
|
||||
shouldCleanLog := false
|
||||
|
||||
for clientEmail, ips := range InboundClientIps {
|
||||
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
||||
inboundClientIps, err := j.getInboundClientIps(clientEmail)
|
||||
sort.Strings(ips)
|
||||
if err != nil {
|
||||
addInboundClientIps(clientEmail, ips)
|
||||
|
||||
j.addInboundClientIps(clientEmail, ips)
|
||||
} else {
|
||||
shouldCleanLog = updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// added 3 seconds delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||
time.Sleep(time.Second * 3)
|
||||
//added 3 seconds delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||
|
||||
if shouldCleanLog {
|
||||
//copy log
|
||||
logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
checkError(err)
|
||||
// copy access log to persistent file
|
||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
j.checkError(err)
|
||||
input, err := os.ReadFile(accessLogPath)
|
||||
checkError(err)
|
||||
j.checkError(err)
|
||||
if _, err := logAccessP.Write(input); err != nil {
|
||||
checkError(err)
|
||||
j.checkError(err)
|
||||
}
|
||||
defer logAccessP.Close()
|
||||
// clean log
|
||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
||||
checkError(err)
|
||||
|
||||
// clean access log
|
||||
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
||||
j.checkError(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func GetAccessLogPath() string {
|
||||
|
||||
config, err := os.ReadFile(xray.GetConfigPath())
|
||||
checkError(err)
|
||||
|
||||
jsonConfig := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||
checkError(err)
|
||||
if jsonConfig["log"] != nil {
|
||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
||||
if jsonLog["access"] != nil {
|
||||
|
||||
accessLogPath := jsonLog["access"].(string)
|
||||
|
||||
return accessLogPath
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
||||
func checkError(e error) {
|
||||
func (j *CheckClientIpJob) checkError(e error) {
|
||||
if e != nil {
|
||||
logger.Warning("client ip job err:", e)
|
||||
}
|
||||
}
|
||||
func contains(s []string, str string) bool {
|
||||
|
||||
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
@@ -195,7 +172,8 @@ func contains(s []string, str string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||
|
||||
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||
db := database.GetDB()
|
||||
InboundClientIps := &model.InboundClientIps{}
|
||||
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
||||
@@ -204,10 +182,11 @@ func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||
}
|
||||
return InboundClientIps, nil
|
||||
}
|
||||
func addInboundClientIps(clientEmail string, ips []string) error {
|
||||
|
||||
func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string) error {
|
||||
inboundClientIps := &model.InboundClientIps{}
|
||||
jsonIps, err := json.Marshal(ips)
|
||||
checkError(err)
|
||||
j.checkError(err)
|
||||
|
||||
inboundClientIps.ClientEmail = clientEmail
|
||||
inboundClientIps.Ips = string(jsonIps)
|
||||
@@ -229,17 +208,17 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
|
||||
|
||||
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
|
||||
jsonIps, err := json.Marshal(ips)
|
||||
checkError(err)
|
||||
j.checkError(err)
|
||||
|
||||
inboundClientIps.ClientEmail = clientEmail
|
||||
inboundClientIps.Ips = string(jsonIps)
|
||||
|
||||
// check inbound limitation
|
||||
inbound, err := GetInboundByEmail(clientEmail)
|
||||
checkError(err)
|
||||
inbound, err := j.getInboundByEmail(clientEmail)
|
||||
j.checkError(err)
|
||||
|
||||
if inbound.Settings == "" {
|
||||
logger.Debug("wrong data ", inbound)
|
||||
@@ -251,13 +230,20 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
||||
clients := settings["clients"]
|
||||
shouldCleanLog := false
|
||||
|
||||
// create iplimit log file channel
|
||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
||||
}
|
||||
defer logIpFile.Close()
|
||||
log.SetOutput(logIpFile)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
|
||||
limitIp := client.LimitIP
|
||||
|
||||
if limitIp != 0 {
|
||||
|
||||
shouldCleanLog = true
|
||||
|
||||
if limitIp < len(ips) && inbound.Enable {
|
||||
@@ -280,27 +266,14 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
||||
return shouldCleanLog
|
||||
}
|
||||
|
||||
func DisableInbound(id int) error {
|
||||
db := database.GetDB()
|
||||
result := db.Model(model.Inbound{}).
|
||||
Where("id = ? and enable = ?", id, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
logger.Warning("disable inbound with id:", id)
|
||||
|
||||
if err == nil {
|
||||
job.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds *model.Inbound
|
||||
|
||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return inbounds, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"os"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
)
|
||||
|
||||
type ClearLogsJob struct{}
|
||||
|
||||
func NewClearLogsJob() *ClearLogsJob {
|
||||
return new(ClearLogsJob)
|
||||
}
|
||||
|
||||
// Here Run is an interface method of the Job interface
|
||||
func (j *ClearLogsJob) Run() {
|
||||
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
||||
|
||||
// clear log files
|
||||
for i := 0; i < len(logFiles); i++ {
|
||||
if err := os.Truncate(logFiles[i], 0); err != nil {
|
||||
logger.Warning("clear logs job err:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user