🚀 Some improvements for x-ui.sh and ip job (#665)

This commit is contained in:
Hamidreza 2023-07-01 15:56:43 +03:30 committed by GitHub
parent f726474a5d
commit 1028319386
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 428 additions and 294 deletions

7
DockerEntrypoint.sh Normal file
View file

@ -0,0 +1,7 @@
#!/bin/sh
# Start fail2ban
fail2ban-client -x -f start
# Run x-ui
exec /app/x-ui

View file

@ -1,22 +1,28 @@
#!/bin/sh #!/bin/sh
if [ $1 == "amd64" ]; then
ARCH="64"; case $1 in
FNAME="amd64"; amd64)
elif [ $1 == "arm64" ]; then ARCH="64"
ARCH="arm64-v8a" FNAME="amd64"
FNAME="arm64"; ;;
else arm64)
ARCH="64"; ARCH="arm64-v8a"
FNAME="amd64"; FNAME="arm64"
fi ;;
*)
ARCH="64"
FNAME="amd64"
;;
esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip" wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat" wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
cd ../../

View file

@ -1,20 +1,47 @@
#Build latest x-ui from source # ========================================================
# Stage: Builder
# ========================================================
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
RUN apk --no-cache --update add build-base gcc wget unzip ENV CGO_ENABLED=1
RUN apk --no-cache --update add \
build-base \
gcc \
wget \
unzip
COPY . . COPY . .
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
RUN go build -o build/x-ui main.go
RUN ./DockerInit.sh "$TARGETARCH" RUN ./DockerInit.sh "$TARGETARCH"
# ========================================================
#Build app image using latest x-ui # Stage: Final Image of 3x-ui
# ========================================================
FROM alpine FROM alpine
ENV TZ=Asia/Tehran ENV TZ=Asia/Tehran
WORKDIR /app WORKDIR /app
RUN apk add ca-certificates tzdata RUN apk add --no-cache --update \
ca-certificates \
tzdata \
fail2ban
COPY --from=builder /app/build/ /app/ COPY --from=builder /app/build/ /app/
COPY --from=builder /app/DockerEntrypoint.sh /app/
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
# Configure fail2ban
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
&& sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local
RUN chmod +x \
/app/DockerEntrypoint.sh \
/app/x-ui \
/usr/bin/x-ui
VOLUME [ "/etc/x-ui" ] VOLUME [ "/etc/x-ui" ]
ENTRYPOINT [ "/app/x-ui" ] ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]

View file

@ -178,7 +178,7 @@ If you want to use routing to WARP follow steps as below:
2. Install WARP on **socks proxy mode**: 2. Install WARP on **socks proxy mode**:
```sh ```sh
bash <(curl -sSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh) bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
``` ```
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json) 3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
@ -280,6 +280,7 @@ Reference syntax:
| XUI_DEBUG | `boolean` | `false` | | XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` | | XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | | XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
Example: Example:

View file

@ -65,3 +65,11 @@ func GetDBFolderPath() string {
func GetDBPath() string { func GetDBPath() string {
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName()) return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
} }
func GetLogFolder() string {
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
if logFolderPath == "" {
logFolderPath = "/var/log"
}
return logFolderPath
}

View file

@ -6,6 +6,7 @@ import (
"io/fs" "io/fs"
"os" "os"
"path" "path"
"x-ui/config" "x-ui/config"
"x-ui/database/model" "x-ui/database/model"
"x-ui/xray" "x-ui/xray"
@ -26,7 +27,6 @@ var initializers = []func() error{
} }
func initUser() error { func initUser() error {
err := db.AutoMigrate(&model.User{}) err := db.AutoMigrate(&model.User{})
if err != nil { if err != nil {
return err return err
@ -54,9 +54,11 @@ func initInbound() error {
func initSetting() error { func initSetting() error {
return db.AutoMigrate(&model.Setting{}) return db.AutoMigrate(&model.Setting{})
} }
func initInboundClientIps() error { func initInboundClientIps() error {
return db.AutoMigrate(&model.InboundClientIps{}) return db.AutoMigrate(&model.InboundClientIps{})
} }
func initClientTraffic() error { func initClientTraffic() error {
return db.AutoMigrate(&xray.ClientTraffic{}) return db.AutoMigrate(&xray.ClientTraffic{})
} }

View file

@ -8,7 +8,7 @@ plain='\033[0m'
cur_dir=$(pwd) cur_dir=$(pwd)
# check root # check root
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error${plain} Please run this script with root privilege \n " && exit 1 [[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
# Check OS and set release variable # Check OS and set release variable
if [[ -f /etc/os-release ]]; then if [[ -f /etc/os-release ]]; then
@ -41,12 +41,12 @@ if [[ "${release}" == "centos" ]]; then
fi fi
elif [[ "${release}" == "ubuntu" ]]; then elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version${plain}\n" && exit 1 echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "fedora" ]]; then elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then if [[ ${os_version} -lt 36 ]]; then
echo -e "${red}please use Fedora 36 or higher version${plain}\n" && exit 1 echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "debian" ]]; then elif [[ "${release}" == "debian" ]]; then
@ -68,7 +68,7 @@ install_base() {
esac esac
} }
#This function will be called when user installed x-ui out of sercurity # This function will be called when user installed x-ui out of sercurity
config_after_install() { config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}" echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
read -p "Do you want to continue with the modification [y/n]? ": config_confirm read -p "Do you want to continue with the modification [y/n]? ": config_confirm

View file

@ -3,6 +3,7 @@ package controller
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
"x-ui/web/global" "x-ui/web/global"
@ -40,7 +41,6 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients) g.POST("/delDepletedClients/:id", a.delDepletedClients)
} }
func (a *InboundController) startTask() { func (a *InboundController) startTask() {
@ -79,6 +79,7 @@ func (a *InboundController) getInbound(c *gin.Context) {
} }
jsonObj(c, inbound, nil) jsonObj(c, inbound, nil)
} }
func (a *InboundController) getClientTraffics(c *gin.Context) { func (a *InboundController) getClientTraffics(c *gin.Context) {
email := c.Param("email") email := c.Param("email")
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email) clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)

View file

@ -45,7 +45,9 @@
<a-tag :color="statsColor(record, client.email)"> <a-tag :color="statsColor(record, client.email)">
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] / [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template> <template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
<template v-else></template> <template v-else>
<svg style="fill: currentColor; height: 16px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
</template>
</a-tag> </a-tag>
</a-popover> </a-popover>
</template> </template>

View file

@ -224,7 +224,9 @@
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else></template> <template v-else>
<svg style="fill: currentColor; height: 16px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
</template>
</a-tag> </a-tag>
</a-popover> </a-popover>
</template> </template>

View file

@ -5,23 +5,26 @@ import (
"log" "log"
"os" "os"
"regexp" "regexp"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/web/service"
"x-ui/xray"
"sort" "sort"
"strings" "strings"
"time" "time"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/xray"
) )
type CheckClientIpJob struct { type CheckClientIpJob struct {}
xrayService service.XrayService
}
var job *CheckClientIpJob var job *CheckClientIpJob
var disAllowedIps []string var disAllowedIps []string
var ipFiles = []string{
xray.GetBlockedIPsPath(),
xray.GetIPLimitLogPath(),
xray.GetIPLimitBannedLogPath(),
xray.GetAccessPersistentLogPath(),
}
func NewCheckClientIpJob() *CheckClientIpJob { func NewCheckClientIpJob() *CheckClientIpJob {
job = new(CheckClientIpJob) job = new(CheckClientIpJob)
@ -31,37 +34,28 @@ func NewCheckClientIpJob() *CheckClientIpJob {
func (j *CheckClientIpJob) Run() { func (j *CheckClientIpJob) Run() {
logger.Debug("Check Client IP Job...") logger.Debug("Check Client IP Job...")
if hasLimitIp() { // create files required for iplimit if not exists
//create log file for Fail2ban IP Limit for i := 0; i < len(ipFiles); i++ {
logIpFile, err := os.OpenFile("/var/log/3xipl.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
checkError(err) j.checkError(err)
defer logIpFile.Close() defer file.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()
} }
// check for limit ip
if j.hasLimitIp() {
j.processLogFile()
}
// write to blocked ips
blockedIps := []byte(strings.Join(disAllowedIps, ",")) blockedIps := []byte(strings.Join(disAllowedIps, ","))
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0644)
// check if file exists, if not create one j.checkError(err)
_, 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)
} }
func hasLimitIp() bool { func (j *CheckClientIpJob) hasLimitIp() bool {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Find(&inbounds).Error err := db.Model(model.Inbound{}).Find(&inbounds).Error
if err != nil { if err != nil {
return false return false
@ -83,11 +77,12 @@ func hasLimitIp() bool {
} }
} }
} }
return false return false
} }
func processLogFile() { func (j *CheckClientIpJob) processLogFile() {
accessLogPath := GetAccessLogPath() accessLogPath := xray.GetAccessLogPath()
if accessLogPath == "" { if accessLogPath == "" {
logger.Warning("access.log doesn't exist in your config.json") logger.Warning("access.log doesn't exist in your config.json")
return return
@ -95,7 +90,7 @@ func processLogFile() {
data, err := os.ReadFile(accessLogPath) data, err := os.ReadFile(accessLogPath)
InboundClientIps := make(map[string][]string) InboundClientIps := make(map[string][]string)
checkError(err) j.checkError(err)
lines := strings.Split(string(data), "\n") lines := strings.Split(string(data), "\n")
for _, line := range lines { for _, line := range lines {
@ -116,7 +111,7 @@ func processLogFile() {
matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1]) matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
if InboundClientIps[matchesEmail] != nil { if InboundClientIps[matchesEmail] != nil {
if contains(InboundClientIps[matchesEmail], ip) { if j.contains(InboundClientIps[matchesEmail], ip) {
continue continue
} }
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip) InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
@ -125,68 +120,50 @@ func processLogFile() {
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip) InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
} }
} }
} }
disAllowedIps = []string{} disAllowedIps = []string{}
shouldCleanLog := false shouldCleanLog := false
for clientEmail, ips := range InboundClientIps { for clientEmail, ips := range InboundClientIps {
inboundClientIps, err := GetInboundClientIps(clientEmail) inboundClientIps, err := j.getInboundClientIps(clientEmail)
sort.Strings(ips) sort.Strings(ips)
if err != nil { if err != nil {
addInboundClientIps(clientEmail, ips) j.addInboundClientIps(clientEmail, ips)
} else { } 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) 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 { if shouldCleanLog {
//copy log // copy access log to persistent file
logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
checkError(err) j.checkError(err)
input, err := os.ReadFile(accessLogPath) input, err := os.ReadFile(accessLogPath)
checkError(err) j.checkError(err)
if _, err := logAccessP.Write(input); err != nil { if _, err := logAccessP.Write(input); err != nil {
checkError(err) j.checkError(err)
} }
defer logAccessP.Close() defer logAccessP.Close()
// clean log
if err := os.Truncate(GetAccessLogPath(), 0); err != nil { // clean access log
checkError(err) if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
j.checkError(err)
} }
} }
} }
func GetAccessLogPath() string {
config, err := os.ReadFile(xray.GetConfigPath()) func (j *CheckClientIpJob) checkError(e error) {
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) {
if e != nil { if e != nil {
logger.Warning("client ip job err:", e) 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 { for _, v := range s {
if v == str { if v == str {
return true return true
@ -195,7 +172,8 @@ func contains(s []string, str string) bool {
return false return false
} }
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
db := database.GetDB() db := database.GetDB()
InboundClientIps := &model.InboundClientIps{} InboundClientIps := &model.InboundClientIps{}
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error 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 return InboundClientIps, nil
} }
func addInboundClientIps(clientEmail string, ips []string) error {
func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string) error {
inboundClientIps := &model.InboundClientIps{} inboundClientIps := &model.InboundClientIps{}
jsonIps, err := json.Marshal(ips) jsonIps, err := json.Marshal(ips)
checkError(err) j.checkError(err)
inboundClientIps.ClientEmail = clientEmail inboundClientIps.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps) inboundClientIps.Ips = string(jsonIps)
@ -229,17 +208,17 @@ func addInboundClientIps(clientEmail string, ips []string) error {
} }
return nil 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) jsonIps, err := json.Marshal(ips)
checkError(err) j.checkError(err)
inboundClientIps.ClientEmail = clientEmail inboundClientIps.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps) inboundClientIps.Ips = string(jsonIps)
// check inbound limitation // check inbound limitation
inbound, err := GetInboundByEmail(clientEmail) inbound, err := j.getInboundByEmail(clientEmail)
checkError(err) j.checkError(err)
if inbound.Settings == "" { if inbound.Settings == "" {
logger.Debug("wrong data ", inbound) logger.Debug("wrong data ", inbound)
@ -251,13 +230,20 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
clients := settings["clients"] clients := settings["clients"]
shouldCleanLog := false 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 { for _, client := range clients {
if client.Email == clientEmail { if client.Email == clientEmail {
limitIp := client.LimitIP limitIp := client.LimitIP
if limitIp != 0 { if limitIp != 0 {
shouldCleanLog = true shouldCleanLog = true
if limitIp < len(ips) && inbound.Enable { if limitIp < len(ips) && inbound.Enable {
@ -280,27 +266,14 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
return shouldCleanLog return shouldCleanLog
} }
func DisableInbound(id int) error { func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, 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) {
db := database.GetDB() db := database.GetDB()
var inbounds *model.Inbound var inbounds *model.Inbound
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
return inbounds, nil return inbounds, nil
} }

25
web/job/clear_logs_job.go Normal file
View file

@ -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)
}
}
}

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@ -74,7 +75,6 @@ func (s *InboundService) getAllEmails() ([]string, error) {
FROM inbounds, FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
`).Scan(&emails).Error `).Scan(&emails).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -816,7 +816,8 @@ func (s *InboundService) UpdateClientStat(email string, client *model.Client) er
"enable": true, "enable": true,
"email": client.Email, "email": client.Email,
"total": client.TotalGB, "total": client.TotalGB,
"expiry_time": client.ExpiryTime}) "expiry_time": client.ExpiryTime,
})
err := result.Error err := result.Error
if err != nil { if err != nil {
return err return err
@ -1068,8 +1069,8 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
return err return err
} }
return nil return nil
} }
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error { func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
_, inbound, err := s.GetClientInboundByEmail(clientEmail) _, inbound, err := s.GetClientInboundByEmail(clientEmail)
if err != nil { if err != nil {
@ -1126,7 +1127,6 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
return err return err
} }
return nil return nil
} }
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error { func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
@ -1137,7 +1137,6 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
err := result.Error err := result.Error
if err != nil { if err != nil {
return err return err
} }
@ -1209,7 +1208,6 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0}) Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
err := result.Error err := result.Error
if err != nil { if err != nil {
return err return err
} }
@ -1224,7 +1222,6 @@ func (s *InboundService) ResetAllTraffics() error {
Updates(map[string]interface{}{"up": 0, "down": 0}) Updates(map[string]interface{}{"up": 0, "down": 0})
err := result.Error err := result.Error
if err != nil { if err != nil {
return err return err
} }
@ -1411,7 +1408,6 @@ func (s *InboundService) ClearClientIps(clientEmail string) error {
Where("client_email = ?", clientEmail). Where("client_email = ?", clientEmail).
Update("ips", "") Update("ips", "")
err := result.Error err := result.Error
if err != nil { if err != nil {
return err return err
} }

View file

@ -14,6 +14,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"x-ui/config" "x-ui/config"
"x-ui/database" "x-ui/database"
"x-ui/logger" "x-ui/logger"
@ -250,7 +251,6 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
} }
func (s *ServerService) StopXrayService() (string error) { func (s *ServerService) StopXrayService() (string error) {
err := s.xrayService.StopXray() err := s.xrayService.StopXray()
if err != nil { if err != nil {
logger.Error("stop xray failed:", err) logger.Error("stop xray failed:", err)
@ -261,7 +261,6 @@ func (s *ServerService) StopXrayService() (string error) {
} }
func (s *ServerService) RestartXrayService() (string error) { func (s *ServerService) RestartXrayService() (string error) {
s.xrayService.StopXray() s.xrayService.StopXray()
defer func() { defer func() {
err := s.xrayService.RestartXray(true) err := s.xrayService.RestartXray(true)
@ -377,7 +376,6 @@ func (s *ServerService) UpdateXray(version string) error {
} }
return nil return nil
} }
func (s *ServerService) GetLogs(count string, logLevel string) ([]string, error) { func (s *ServerService) GetLogs(count string, logLevel string) ([]string, error) {

View file

@ -253,6 +253,9 @@ func (s *Server) startTask() {
// check client ips from log file every 20 sec // check client ips from log file every 20 sec
s.cron.AddJob("@every 20s", job.NewCheckClientIpJob()) s.cron.AddJob("@every 20s", job.NewCheckClientIpJob())
// check client ips from log file every 3 day
s.cron.AddJob("@every 3d", job.NewClearLogsJob())
// Make a traffic condition every day, 8:30 // Make a traffic condition every day, 8:30
var entry cron.EntryID var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotenabled() isTgbotenabled, err := s.settingService.GetTgbotenabled()

269
x-ui.sh
View file

@ -56,6 +56,13 @@ elif [[ "${release}" == "debian" ]]; then
fi fi
fi fi
# Declare Variables
log_folder="${XUI_LOG_FOLDER:=/var/log}"
iplimit_log_path="${log_folder}/3xipl.log"
iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
confirm() { confirm() {
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
echo && read -p "$1 [Default $2]: " temp echo && read -p "$1 [Default $2]: " temp
@ -296,25 +303,28 @@ enable_bbr() {
fi fi
# Check the OS and install necessary packages # Check the OS and install necessary packages
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then case "${release}" in
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates ubuntu|debian)
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates ;;
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then centos)
sudo dnf -y update && sudo dnf -y install ca-certificates yum -y update && yum -y install ca-certificates
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then ;;
sudo yum -y update && sudo yum -y install ca-certificates fedora)
else dnf -y update && dnf -y install ca-certificates
echo "Unsupported operating system. Please check the script and install the necessary packages manually." ;;
exit 1 *)
fi echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1
;;
esac
# Enable BBR # Enable BBR
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
# Apply changes # Apply changes
sudo sysctl -p sysctl -p
# Verify that BBR is enabled # Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
@ -434,24 +444,24 @@ show_xray_status() {
open_ports() { open_ports() {
if ! command -v ufw &>/dev/null; then if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..." echo "ufw firewall is not installed. Installing now..."
sudo apt-get update apt-get update
sudo apt-get install -y ufw apt-get install -y ufw
else else
echo "ufw firewall is already installed" echo "ufw firewall is already installed"
fi fi
# Check if the firewall is inactive # Check if the firewall is inactive
if sudo ufw status | grep -q "Status: active"; then if ufw status | grep -q "Status: active"; then
echo "firewall is already active" echo "firewall is already active"
else else
# Open the necessary ports # Open the necessary ports
sudo ufw allow ssh ufw allow ssh
sudo ufw allow http ufw allow http
sudo ufw allow https ufw allow https
sudo ufw allow 2053/tcp ufw allow 2053/tcp
# Enable the firewall # Enable the firewall
sudo ufw --force enable ufw --force enable
fi fi
# Prompt the user to enter a list of ports # Prompt the user to enter a list of ports
@ -472,15 +482,15 @@ open_ports() {
end_port=$(echo $port | cut -d'-' -f2) end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and open each port # Loop through the range and open each port
for ((i = start_port; i <= end_port; i++)); do for ((i = start_port; i <= end_port; i++)); do
sudo ufw allow $i ufw allow $i
done done
else else
sudo ufw allow "$port" ufw allow "$port"
fi fi
done done
# Confirm that the ports are open # Confirm that the ports are open
sudo ufw status | grep $ports ufw status | grep $ports
} }
update_geo() { update_geo() {
@ -539,7 +549,7 @@ ssl_cert_issue_main() {
} }
ssl_cert_issue() { ssl_cert_issue() {
#check for acme.sh first # check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it" echo "acme.sh could not be found. we will install it"
install_acme install_acme
@ -548,24 +558,30 @@ ssl_cert_issue() {
exit 1 exit 1
fi fi
fi fi
#install socat second # install socat second
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]]; then case "${release}" in
yum install socat -y ubuntu|debian)
else apt update && apt install socat -y ;;
apt install socat -y centos)
fi yum -y update && yum -y install socat ;;
fedora)
dnf -y update && dnf -y install socat ;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1 ;;
esac
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "install socat failed,please check logs" LOGE "install socat failed, please check logs"
exit 1 exit 1
else else
LOGI "install socat succeed..." LOGI "install socat succeed..."
fi fi
#get the domain here,and we need verify it # get the domain here,and we need verify it
local domain="" local domain=""
read -p "Please enter your domain name:" domain read -p "Please enter your domain name:" domain
LOGD "your domain is:${domain},check it..." LOGD "your domain is:${domain},check it..."
#here we need to judge whether there exists cert already # here we need to judge whether there exists cert already
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}') local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
if [ ${currentCert} == ${domain} ]; then if [ ${currentCert} == ${domain} ]; then
@ -577,7 +593,7 @@ ssl_cert_issue() {
LOGI "your domain is ready for issuing cert now..." LOGI "your domain is ready for issuing cert now..."
fi fi
#create a directory for install cert # create a directory for install cert
certPath="/root/cert/${domain}" certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then if [ ! -d "$certPath" ]; then
mkdir -p "$certPath" mkdir -p "$certPath"
@ -586,15 +602,15 @@ ssl_cert_issue() {
mkdir -p "$certPath" mkdir -p "$certPath"
fi fi
#get needed port here # get needed port here
local WebPort=80 local WebPort=80
read -p "please choose which port do you use,default will be 80 port:" WebPort read -p "please choose which port do you use,default will be 80 port:" WebPort
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
LOGE "your input ${WebPort} is invalid,will use default port" LOGE "your input ${WebPort} is invalid,will use default port"
fi fi
LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..." LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
#NOTE:This should be handled by user # NOTE:This should be handled by user
#open the port and kill the occupied progress # open the port and kill the occupied progress
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort} ~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -604,7 +620,7 @@ ssl_cert_issue() {
else else
LOGE "issue certs succeed,installing certs..." LOGE "issue certs succeed,installing certs..."
fi fi
#install cert # install cert
~/.acme.sh/acme.sh --installcert -d ${domain} \ ~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \ --key-file /root/cert/${domain}/privkey.pem \
--fullchain-file /root/cert/${domain}/fullchain.pem --fullchain-file /root/cert/${domain}/fullchain.pem
@ -628,18 +644,17 @@ ssl_cert_issue() {
ls -lah cert/* ls -lah cert/*
chmod 755 $certPath/* chmod 755 $certPath/*
fi fi
} }
warp_cloudflare() { warp_cloudflare() {
echo -e "${green}\t1.${plain} install WARP" echo -e "${green}\t1.${plain} Install WARP socks5 proxy"
echo -e "${green}\t2.${plain} Account Type (free, plus, team)" echo -e "${green}\t2.${plain} Account Type (free, plus, team)"
echo -e "${green}\t3.${plain} Turn on/off WireProxy" echo -e "${green}\t3.${plain} Turn on/off WireProxy"
echo -e "${green}\t4.${plain} Uninstall WARP" echo -e "${green}\t4.${plain} Uninstall WARP"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
1) 1)
bash <(curl -sSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh) bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
;; ;;
2) 2)
warp a warp a
@ -679,8 +694,8 @@ run_speedtest() {
echo "Error: Package manager not found. You may need to install Speedtest manually." echo "Error: Package manager not found. You may need to install Speedtest manually."
return 1 return 1
else else
curl -s $speedtest_install_script | sudo bash curl -s $speedtest_install_script | bash
sudo $pkg_manager install -y speedtest $pkg_manager install -y speedtest
fi fi
fi fi
@ -688,6 +703,70 @@ run_speedtest() {
speedtest speedtest
} }
create_iplimit_jails() {
# Use default bantime if not passed => 5 minutes
local bantime="${1:-5}"
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl]
enabled=true
filter=3x-ipl
action=3x-ipl
logpath=${iplimit_log_path}
maxretry=3
findtime=100
bantime=${bantime}m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
ignoreregex =
EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-common.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
[Init]
EOF
echo -e "${green}Created Ip Limit jail files with a bantime of ${bantime} minutes.${plain}"
}
iplimit_remove_conflicts() {
local jail_files=(
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
)
for file in "${jail_files[@]}"; do
# Check for [3x-ipl] config in jail file then remove it
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
fi
done
}
iplimit_main() { iplimit_main() {
echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit" echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
echo -e "${green}\t2.${plain} Change Ban Duration" echo -e "${green}\t2.${plain} Change Ban Duration"
@ -709,9 +788,8 @@ iplimit_main() {
2) 2)
read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM
if [[ $NUM =~ ^[0-9]+$ ]]; then if [[ $NUM =~ ^[0-9]+$ ]]; then
echo -e "\n[3x-ipl]\nenabled=true\nfilter=3x-ipl\naction=3x-ipl\nlogpath=/var/log/3xipl.log\nmaxretry=3\nfindtime=100\nbantime=${NUM}m" > /etc/fail2ban/jail.d/3x-ipl.conf create_iplimit_jail ${NUM}
sudo systemctl restart fail2ban systemctl restart fail2ban
echo -e "${green}Bantime set to ${NUM} minutes successfully.${plain}"
else else
echo -e "${red}${NUM} is not a number! Please, try again.${plain}" echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
fi fi
@ -727,9 +805,9 @@ iplimit_main() {
fi fi
iplimit_main ;; iplimit_main ;;
4) 4)
if test -f "/var/log/3xipl-banned.log"; then if test -f "${iplimit_banned_log_path}"; then
if [[ -s "/var/log/3xipl-banned.log" ]]; then if [[ -s "${iplimit_banned_log_path}" ]]; then
cat /var/log/3xipl-banned.log cat ${iplimit_banned_log_path}
else else
echo -e "${red}Log file is empty.${plain}\n" echo -e "${red}Log file is empty.${plain}\n"
fi fi
@ -749,11 +827,11 @@ install_iplimit() {
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu|debian) ubuntu|debian)
sudo apt-get update && sudo apt-get install fail2ban -y ;; apt update && apt install fail2ban -y ;;
centos) centos)
sudo yum -y update && sudo yum -y install fail2ban ;; yum -y update && yum -y install fail2ban ;;
fedora) fedora)
sudo dnf -y update && sudo dnf -y install fail2ban ;; dnf -y update && dnf -y install fail2ban ;;
*) *)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1 ;; exit 1 ;;
@ -765,63 +843,30 @@ install_iplimit() {
echo -e "${green}Configuring IP Limit...${plain}\n" echo -e "${green}Configuring IP Limit...${plain}\n"
#Check if [3x-ipl] exists in jail.local (just making sure there's no double config for jail) # make sure there's no conflict for jail files
if grep -qw '3x-ipl' /etc/fail2ban/jail.local || grep -qw '3x-ipl' /etc/fail2ban/jail.conf; then iplimit_remove_conflicts
echo -e "${red}Found conflicts in /etc/fail2ban/jail.conf or jail.local file!\nPlease manually remove anything related 3x-ipl in that files and try again.\nInstallation of IP Limit failed.${plain}\n"
exit 1 # Check if log file exists
if ! test -f "${iplimit_banned_log_path}"; then
touch ${iplimit_banned_log_path}
fi fi
#Check if log file exists # Check if service log file exists so fail2ban won't return error
if ! test -f "/var/log/3xipl-banned.log"; then if ! test -f "${iplimit_log_path}"; then
touch /var/log/3xipl-banned.log touch ${iplimit_log_path}
fi fi
#Check if service log file exists so fail2ban won't return error # Create the iplimit jail files
if ! test -f "/var/log/3xipl.log"; then # we didn't pass the bantime here to use the default value
touch /var/log/3xipl.log create_iplimit_jails
fi
# Launching fail2ban
echo -e "\n[3x-ipl]\nenabled=true\nfilter=3x-ipl\naction=3x-ipl\nlogpath=/var/log/3xipl.log\nmaxretry=3\nfindtime=100\nbantime=5m" > /etc/fail2ban/jail.d/3x-ipl.conf if ! systemctl is-active --quiet fail2ban; then
systemctl start fail2ban
sudo cat > /etc/fail2ban/filter.d/3x-ipl.conf << EOF
[Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
ignoreregex =
EOF
sudo cat > /etc/fail2ban/action.d/3x-ipl.conf << 'EOF'
[INCLUDES]
before = iptables-common.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> /var/log/3xipl-banned.log
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> /var/log/3xipl-banned.log
[Init]
EOF
#Launching fail2ban
if ! sudo systemctl is-active --quiet fail2ban; then
sudo systemctl start fail2ban
else else
systemctl restart fail2ban systemctl restart fail2ban
fi fi
sudo systemctl enable fail2ban systemctl enable fail2ban
echo -e "${green}IP Limit installed and configured successfully!${plain}\n" echo -e "${green}IP Limit installed and configured successfully!${plain}\n"
before_show_menu before_show_menu
@ -837,27 +882,27 @@ remove_iplimit(){
rm -f /etc/fail2ban/filter.d/3x-ipl.conf rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.d/3x-ipl.conf rm -f /etc/fail2ban/action.d/3x-ipl.conf
rm -f /etc/fail2ban/jail.d/3x-ipl.conf rm -f /etc/fail2ban/jail.d/3x-ipl.conf
sudo systemctl restart fail2ban systemctl restart fail2ban
echo -e "${green}IP Limit removed successfully!${plain}\n" echo -e "${green}IP Limit removed successfully!${plain}\n"
before_show_menu ;; before_show_menu ;;
2) 2)
rm -f /etc/fail2ban/filter.d/3x-ipl.conf rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.d/3x-ipl.conf rm -f /etc/fail2ban/action.d/3x-ipl.conf
rm -f /etc/fail2ban/jail.d/3x-ipl.conf rm -f /etc/fail2ban/jail.d/3x-ipl.conf
sudo systemctl stop fail2ban systemctl stop fail2ban
sudo systemctl disable fail2ban systemctl disable fail2ban
case "${release}" in case "${release}" in
ubuntu|debian) ubuntu|debian)
sudo apt-get remove fail2ban -y ;; apt remove fail2ban -y ;;
centos) centos)
sudo yum -y remove fail2ban ;; yum -y remove fail2ban ;;
fedora) fedora)
sudo dnf -y remove fail2ban ;; dnf -y remove fail2ban ;;
*) *)
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
exit 1 ;; exit 1 ;;
esac esac
rm -rf /etc/fail2ban/* rm -rf /etc/fail2ban
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n" echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n"
before_show_menu ;; before_show_menu ;;
0) 0)
@ -917,7 +962,7 @@ show_menu() {
${green}19.${plain} Update Geo Files ${green}19.${plain} Update Geo Files
${green}20.${plain} Active Firewall and open ports ${green}20.${plain} Active Firewall and open ports
${green}21.${plain} Speedtest by Ookla ${green}21.${plain} Speedtest by Ookla
" "
show_status show_status
echo && read -p "Please enter your selection [0-21]: " num echo && read -p "Please enter your selection [0-21]: " num

View file

@ -14,6 +14,7 @@ import (
"sync" "sync"
"syscall" "syscall"
"x-ui/config" "x-ui/config"
"x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
"github.com/Workiva/go-datastructures/queue" "github.com/Workiva/go-datastructures/queue"
@ -47,10 +48,47 @@ func GetBlockedIPsPath() string {
return config.GetBinFolderPath() + "/BlockedIps" return config.GetBinFolderPath() + "/BlockedIps"
} }
func GetIPLimitLogPath() string {
return config.GetLogFolder() + "/3xipl.log"
}
func GetIPLimitBannedLogPath() string {
return config.GetLogFolder() + "/3xipl-banned.log"
}
func GetAccessPersistentLogPath() string {
return config.GetLogFolder() + "/3xipl-access-persistent.log"
}
func GetAccessLogPath() string {
config, err := os.ReadFile(GetConfigPath())
if err != nil {
logger.Warningf("Something went wrong: %s", err)
}
jsonConfig := map[string]interface{}{}
err = json.Unmarshal([]byte(config), &jsonConfig)
if err != nil {
logger.Warningf("Something went wrong: %s", err)
}
if jsonConfig["log"] != nil {
jsonLog := jsonConfig["log"].(map[string]interface{})
if jsonLog["access"] != nil {
accessLogPath := jsonLog["access"].(string)
return accessLogPath
}
}
return ""
}
func stopProcess(p *Process) { func stopProcess(p *Process) {
p.Stop() p.Stop()
} }
type Process struct { type Process struct {
*process *process
} }