diff --git a/DockerEntrypoint.sh b/DockerEntrypoint.sh
new file mode 100644
index 00000000..26083c5d
--- /dev/null
+++ b/DockerEntrypoint.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Start fail2ban
+fail2ban-client -x -f start
+
+# Run x-ui
+exec /app/x-ui
diff --git a/DockerInit.sh b/DockerInit.sh
index 0b83457c..d1a5e40a 100755
--- a/DockerInit.sh
+++ b/DockerInit.sh
@@ -1,22 +1,28 @@
#!/bin/sh
-if [ $1 == "amd64" ]; then
- ARCH="64";
- FNAME="amd64";
-elif [ $1 == "arm64" ]; then
- ARCH="arm64-v8a"
- FNAME="arm64";
-else
- ARCH="64";
- FNAME="amd64";
-fi
+
+case $1 in
+ amd64)
+ ARCH="64"
+ FNAME="amd64"
+ ;;
+ arm64)
+ ARCH="arm64-v8a"
+ FNAME="arm64"
+ ;;
+ *)
+ ARCH="64"
+ FNAME="amd64"
+ ;;
+esac
+
mkdir -p build/bin
cd build/bin
+
wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
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/geosite.dat"
wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
-
-cd ../../
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 6114d548..9e30d9f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,20 +1,47 @@
-#Build latest x-ui from source
+# ========================================================
+# Stage: Builder
+# ========================================================
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
WORKDIR /app
-ARG TARGETARCH
-RUN apk --no-cache --update add build-base gcc wget unzip
+ARG TARGETARCH
+ENV CGO_ENABLED=1
+
+RUN apk --no-cache --update add \
+ build-base \
+ gcc \
+ wget \
+ unzip
+
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"
-
-#Build app image using latest x-ui
+# ========================================================
+# Stage: Final Image of 3x-ui
+# ========================================================
FROM alpine
ENV TZ=Asia/Tehran
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/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" ]
-ENTRYPOINT [ "/app/x-ui" ]
+ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
diff --git a/README.md b/README.md
index 9ba7e289..9f6325ed 100644
--- a/README.md
+++ b/README.md
@@ -178,7 +178,7 @@ If you want to use routing to WARP follow steps as below:
2. Install WARP on **socks proxy mode**:
```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)
@@ -280,6 +280,7 @@ Reference syntax:
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
+| XUI_LOG_FOLDER | `string` | `"/var/log"` |
Example:
diff --git a/config/config.go b/config/config.go
index 30f0ca81..70be5ae6 100644
--- a/config/config.go
+++ b/config/config.go
@@ -65,3 +65,11 @@ func GetDBFolderPath() string {
func GetDBPath() string {
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
}
+
+func GetLogFolder() string {
+ logFolderPath := os.Getenv("XUI_LOG_FOLDER")
+ if logFolderPath == "" {
+ logFolderPath = "/var/log"
+ }
+ return logFolderPath
+}
diff --git a/database/db.go b/database/db.go
index 97ef2160..aaeb8bd9 100644
--- a/database/db.go
+++ b/database/db.go
@@ -6,6 +6,7 @@ import (
"io/fs"
"os"
"path"
+
"x-ui/config"
"x-ui/database/model"
"x-ui/xray"
@@ -26,7 +27,6 @@ var initializers = []func() error{
}
func initUser() error {
-
err := db.AutoMigrate(&model.User{})
if err != nil {
return err
@@ -54,9 +54,11 @@ func initInbound() error {
func initSetting() error {
return db.AutoMigrate(&model.Setting{})
}
+
func initInboundClientIps() error {
return db.AutoMigrate(&model.InboundClientIps{})
}
+
func initClientTraffic() error {
return db.AutoMigrate(&xray.ClientTraffic{})
}
diff --git a/install.sh b/install.sh
index 9e995095..653fe979 100644
--- a/install.sh
+++ b/install.sh
@@ -8,7 +8,7 @@ plain='\033[0m'
cur_dir=$(pwd)
# 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
if [[ -f /etc/os-release ]]; then
@@ -41,12 +41,12 @@ if [[ "${release}" == "centos" ]]; then
fi
elif [[ "${release}" == "ubuntu" ]]; 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
elif [[ "${release}" == "fedora" ]]; 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
elif [[ "${release}" == "debian" ]]; then
@@ -68,7 +68,7 @@ install_base() {
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() {
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
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index a9699bbb..d0e9cc03 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -3,6 +3,7 @@ package controller
import (
"fmt"
"strconv"
+
"x-ui/database/model"
"x-ui/logger"
"x-ui/web/global"
@@ -40,7 +41,6 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
-
}
func (a *InboundController) startTask() {
@@ -79,6 +79,7 @@ func (a *InboundController) getInbound(c *gin.Context) {
}
jsonObj(c, inbound, nil)
}
+
func (a *InboundController) getClientTraffics(c *gin.Context) {
email := c.Param("email")
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html
index 140d7626..0c393675 100644
--- a/web/html/xui/inbound_client_table.html
+++ b/web/html/xui/inbound_client_table.html
@@ -45,7 +45,9 @@
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
[[client._totalGB]]GB
- ♾
+
+
+
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 3640c9bb..015ba192 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -224,7 +224,9 @@
[[ sizeFormat(dbInbound.total) ]]
- ♾
+
+
+
diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go
index 758929e9..1c292a50 100644
--- a/web/job/check_client_ip_job.go
+++ b/web/job/check_client_ip_job.go
@@ -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
}
diff --git a/web/job/clear_logs_job.go b/web/job/clear_logs_job.go
new file mode 100644
index 00000000..34f13eaa
--- /dev/null
+++ b/web/job/clear_logs_job.go
@@ -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)
+ }
+ }
+}
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 802e753d..b278eb57 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"time"
+
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
@@ -74,7 +75,6 @@ func (s *InboundService) getAllEmails() ([]string, error) {
FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
`).Scan(&emails).Error
-
if err != nil {
return nil, err
}
@@ -816,7 +816,8 @@ func (s *InboundService) UpdateClientStat(email string, client *model.Client) er
"enable": true,
"email": client.Email,
"total": client.TotalGB,
- "expiry_time": client.ExpiryTime})
+ "expiry_time": client.ExpiryTime,
+ })
err := result.Error
if err != nil {
return err
@@ -1068,8 +1069,8 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
return err
}
return nil
-
}
+
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
if err != nil {
@@ -1126,7 +1127,6 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
return err
}
return nil
-
}
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})
err := result.Error
-
if err != nil {
return err
}
@@ -1209,7 +1208,6 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
err := result.Error
-
if err != nil {
return err
}
@@ -1224,7 +1222,6 @@ func (s *InboundService) ResetAllTraffics() error {
Updates(map[string]interface{}{"up": 0, "down": 0})
err := result.Error
-
if err != nil {
return err
}
@@ -1411,7 +1408,6 @@ func (s *InboundService) ClearClientIps(clientEmail string) error {
Where("client_email = ?", clientEmail).
Update("ips", "")
err := result.Error
-
if err != nil {
return err
}
diff --git a/web/service/server.go b/web/service/server.go
index 936e6469..5216be5a 100644
--- a/web/service/server.go
+++ b/web/service/server.go
@@ -14,6 +14,7 @@ import (
"runtime"
"strings"
"time"
+
"x-ui/config"
"x-ui/database"
"x-ui/logger"
@@ -250,7 +251,6 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
}
func (s *ServerService) StopXrayService() (string error) {
-
err := s.xrayService.StopXray()
if err != nil {
logger.Error("stop xray failed:", err)
@@ -261,7 +261,6 @@ func (s *ServerService) StopXrayService() (string error) {
}
func (s *ServerService) RestartXrayService() (string error) {
-
s.xrayService.StopXray()
defer func() {
err := s.xrayService.RestartXray(true)
@@ -377,7 +376,6 @@ func (s *ServerService) UpdateXray(version string) error {
}
return nil
-
}
func (s *ServerService) GetLogs(count string, logLevel string) ([]string, error) {
diff --git a/web/web.go b/web/web.go
index b78ac2f0..14e06f67 100644
--- a/web/web.go
+++ b/web/web.go
@@ -253,6 +253,9 @@ func (s *Server) startTask() {
// check client ips from log file every 20 sec
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
var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotenabled()
diff --git a/x-ui.sh b/x-ui.sh
index 99bde08a..fc328f8e 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -56,6 +56,13 @@ elif [[ "${release}" == "debian" ]]; then
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() {
if [[ $# > 1 ]]; then
echo && read -p "$1 [Default $2]: " temp
@@ -296,25 +303,28 @@ enable_bbr() {
fi
# Check the OS and install necessary packages
- if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
- 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}')" == "debian" ]]; then
- 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
- sudo dnf -y update && sudo dnf -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
- else
- echo "Unsupported operating system. Please check the script and install the necessary packages manually."
- exit 1
- fi
+ case "${release}" in
+ ubuntu|debian)
+ apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
+ ;;
+ centos)
+ yum -y update && yum -y install ca-certificates
+ ;;
+ fedora)
+ dnf -y update && dnf -y install ca-certificates
+ ;;
+ *)
+ echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
+ exit 1
+ ;;
+ esac
# Enable BBR
- echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
- echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
+ echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf
+ echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
# Apply changes
- sudo sysctl -p
+ sysctl -p
# Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
@@ -434,24 +444,24 @@ show_xray_status() {
open_ports() {
if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..."
- sudo apt-get update
- sudo apt-get install -y ufw
+ apt-get update
+ apt-get install -y ufw
else
echo "ufw firewall is already installed"
fi
# 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"
else
# Open the necessary ports
- sudo ufw allow ssh
- sudo ufw allow http
- sudo ufw allow https
- sudo ufw allow 2053/tcp
+ ufw allow ssh
+ ufw allow http
+ ufw allow https
+ ufw allow 2053/tcp
# Enable the firewall
- sudo ufw --force enable
+ ufw --force enable
fi
# Prompt the user to enter a list of ports
@@ -472,15 +482,15 @@ open_ports() {
end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and open each port
for ((i = start_port; i <= end_port; i++)); do
- sudo ufw allow $i
+ ufw allow $i
done
else
- sudo ufw allow "$port"
+ ufw allow "$port"
fi
done
# Confirm that the ports are open
- sudo ufw status | grep $ports
+ ufw status | grep $ports
}
update_geo() {
@@ -539,7 +549,7 @@ ssl_cert_issue_main() {
}
ssl_cert_issue() {
- #check for acme.sh first
+ # check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it"
install_acme
@@ -548,24 +558,30 @@ ssl_cert_issue() {
exit 1
fi
fi
- #install socat second
- if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]]; then
- yum install socat -y
- else
- apt install socat -y
- fi
+ # install socat second
+ case "${release}" in
+ ubuntu|debian)
+ apt update && apt install socat -y ;;
+ centos)
+ 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
- LOGE "install socat failed,please check logs"
+ LOGE "install socat failed, please check logs"
exit 1
else
LOGI "install socat succeed..."
fi
- #get the domain here,and we need verify it
+ # get the domain here,and we need verify it
local domain=""
read -p "Please enter your domain name:" domain
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}')
if [ ${currentCert} == ${domain} ]; then
@@ -577,7 +593,7 @@ ssl_cert_issue() {
LOGI "your domain is ready for issuing cert now..."
fi
- #create a directory for install cert
+ # create a directory for install cert
certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then
mkdir -p "$certPath"
@@ -586,15 +602,15 @@ ssl_cert_issue() {
mkdir -p "$certPath"
fi
- #get needed port here
+ # get needed port here
local WebPort=80
read -p "please choose which port do you use,default will be 80 port:" WebPort
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
LOGE "your input ${WebPort} is invalid,will use default port"
fi
LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
- #NOTE:This should be handled by user
- #open the port and kill the occupied progress
+ # NOTE:This should be handled by user
+ # open the port and kill the occupied progress
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
if [ $? -ne 0 ]; then
@@ -604,7 +620,7 @@ ssl_cert_issue() {
else
LOGE "issue certs succeed,installing certs..."
fi
- #install cert
+ # install cert
~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \
--fullchain-file /root/cert/${domain}/fullchain.pem
@@ -628,18 +644,17 @@ ssl_cert_issue() {
ls -lah cert/*
chmod 755 $certPath/*
fi
-
}
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}\t3.${plain} Turn on/off WireProxy"
echo -e "${green}\t4.${plain} Uninstall WARP"
read -p "Choose an option: " choice
case "$choice" in
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)
warp a
@@ -679,8 +694,8 @@ run_speedtest() {
echo "Error: Package manager not found. You may need to install Speedtest manually."
return 1
else
- curl -s $speedtest_install_script | sudo bash
- sudo $pkg_manager install -y speedtest
+ curl -s $speedtest_install_script | bash
+ $pkg_manager install -y speedtest
fi
fi
@@ -688,110 +703,29 @@ run_speedtest() {
speedtest
}
-iplimit_main() {
- echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
- echo -e "${green}\t2.${plain} Change Ban Duration"
- echo -e "${green}\t3.${plain} Unban Everyone"
- echo -e "${green}\t4.${plain} Check Logs"
- echo -e "${green}\t5.${plain} Uninstall IP Limit"
- echo -e "${green}\t0.${plain} Back to Main Menu"
- read -p "Choose an option: " choice
- case "$choice" in
- 0)
- show_menu ;;
- 1)
- confirm "Proceed with installation of Fail2ban & IP Limit?" "y"
- if [[ $? == 0 ]]; then
- install_iplimit
- else
- iplimit_main
- fi ;;
- 2)
- read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM
- 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
- sudo systemctl restart fail2ban
- echo -e "${green}Bantime set to ${NUM} minutes successfully.${plain}"
- else
- echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
- fi
- iplimit_main ;;
- 3)
- confirm "Proceed with Unbanning everyone from IP Limit jail?" "y"
- if [[ $? == 0 ]]; then
- fail2ban-client reload --restart --unban 3x-ipl
- echo -e "${green}All users Unbanned successfully.${plain}"
- iplimit_main
- else
- echo -e "${yellow}Cancelled.${plain}"
- fi
- iplimit_main ;;
- 4)
- if test -f "/var/log/3xipl-banned.log"; then
- if [[ -s "/var/log/3xipl-banned.log" ]]; then
- cat /var/log/3xipl-banned.log
- else
- echo -e "${red}Log file is empty.${plain}\n"
- fi
- else
- echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
- iplimit_main
- fi ;;
- 5)
- remove_iplimit ;;
- *) echo "Invalid choice" ;;
- esac
-}
+create_iplimit_jails() {
+ # Use default bantime if not passed => 5 minutes
+ local bantime="${1:-5}"
-install_iplimit() {
- if ! command -v fail2ban-client &>/dev/null; then
- echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n"
- # Check the OS and install necessary packages
- case "${release}" in
- ubuntu|debian)
- sudo apt-get update && sudo apt-get install fail2ban -y ;;
- centos)
- sudo yum -y update && sudo yum -y install fail2ban ;;
- fedora)
- sudo dnf -y update && sudo dnf -y install fail2ban ;;
- *)
- echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
- exit 1 ;;
- esac
- echo -e "${green}Fail2ban installed successfully!${plain}\n"
- else
- echo -e "${yellow}Fail2ban is already installed.${plain}\n"
- fi
+ 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
- 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)
- if grep -qw '3x-ipl' /etc/fail2ban/jail.local || grep -qw '3x-ipl' /etc/fail2ban/jail.conf; then
- 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
- fi
-
- #Check if log file exists
- if ! test -f "/var/log/3xipl-banned.log"; then
- touch /var/log/3xipl-banned.log
- fi
-
- #Check if service log file exists so fail2ban won't return error
- if ! test -f "/var/log/3xipl.log"; then
- touch /var/log/3xipl.log
- fi
-
-
- 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
-
- sudo cat > /etc/fail2ban/filter.d/3x-ipl.conf << 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*.+\s*\|\|\s*SRC\s*=\s*
ignoreregex =
EOF
- sudo cat > /etc/fail2ban/action.d/3x-ipl.conf << 'EOF'
+ cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-common.conf
@@ -807,21 +741,132 @@ actionstop = -D -p -j f2b-
actioncheck = -n -L | grep -q 'f2b-[ \t]'
actionban = -I f2b- 1 -s -j
- echo "$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> /var/log/3xipl-banned.log
+ echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> ${iplimit_banned_log_path}
actionunban = -D f2b- -s -j
- echo "$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> /var/log/3xipl-banned.log
+ echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> ${iplimit_banned_log_path}
[Init]
EOF
- #Launching fail2ban
- if ! sudo systemctl is-active --quiet fail2ban; then
- sudo systemctl start fail2ban
+ 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() {
+ echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
+ echo -e "${green}\t2.${plain} Change Ban Duration"
+ echo -e "${green}\t3.${plain} Unban Everyone"
+ echo -e "${green}\t4.${plain} Check Logs"
+ echo -e "${green}\t5.${plain} Uninstall IP Limit"
+ echo -e "${green}\t0.${plain} Back to Main Menu"
+ read -p "Choose an option: " choice
+ case "$choice" in
+ 0)
+ show_menu ;;
+ 1)
+ confirm "Proceed with installation of Fail2ban & IP Limit?" "y"
+ if [[ $? == 0 ]]; then
+ install_iplimit
+ else
+ iplimit_main
+ fi ;;
+ 2)
+ read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM
+ if [[ $NUM =~ ^[0-9]+$ ]]; then
+ create_iplimit_jail ${NUM}
+ systemctl restart fail2ban
+ else
+ echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
+ fi
+ iplimit_main ;;
+ 3)
+ confirm "Proceed with Unbanning everyone from IP Limit jail?" "y"
+ if [[ $? == 0 ]]; then
+ fail2ban-client reload --restart --unban 3x-ipl
+ echo -e "${green}All users Unbanned successfully.${plain}"
+ iplimit_main
+ else
+ echo -e "${yellow}Cancelled.${plain}"
+ fi
+ iplimit_main ;;
+ 4)
+ if test -f "${iplimit_banned_log_path}"; then
+ if [[ -s "${iplimit_banned_log_path}" ]]; then
+ cat ${iplimit_banned_log_path}
+ else
+ echo -e "${red}Log file is empty.${plain}\n"
+ fi
+ else
+ echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
+ iplimit_main
+ fi ;;
+ 5)
+ remove_iplimit ;;
+ *) echo "Invalid choice" ;;
+ esac
+}
+
+install_iplimit() {
+ if ! command -v fail2ban-client &>/dev/null; then
+ echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n"
+ # Check the OS and install necessary packages
+ case "${release}" in
+ ubuntu|debian)
+ apt update && apt install fail2ban -y ;;
+ centos)
+ yum -y update && yum -y install fail2ban ;;
+ fedora)
+ 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"
+ exit 1 ;;
+ esac
+ echo -e "${green}Fail2ban installed successfully!${plain}\n"
+ else
+ echo -e "${yellow}Fail2ban is already installed.${plain}\n"
+ fi
+
+ echo -e "${green}Configuring IP Limit...${plain}\n"
+
+ # make sure there's no conflict for jail files
+ iplimit_remove_conflicts
+
+ # Check if log file exists
+ if ! test -f "${iplimit_banned_log_path}"; then
+ touch ${iplimit_banned_log_path}
+ fi
+
+ # Check if service log file exists so fail2ban won't return error
+ if ! test -f "${iplimit_log_path}"; then
+ touch ${iplimit_log_path}
+ fi
+
+ # Create the iplimit jail files
+ # we didn't pass the bantime here to use the default value
+ create_iplimit_jails
+
+ # Launching fail2ban
+ if ! systemctl is-active --quiet fail2ban; then
+ systemctl start fail2ban
else
systemctl restart fail2ban
fi
- sudo systemctl enable fail2ban
+ systemctl enable fail2ban
echo -e "${green}IP Limit installed and configured successfully!${plain}\n"
before_show_menu
@@ -837,27 +882,27 @@ remove_iplimit(){
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.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"
before_show_menu ;;
2)
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.d/3x-ipl.conf
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
- sudo systemctl stop fail2ban
- sudo systemctl disable fail2ban
+ systemctl stop fail2ban
+ systemctl disable fail2ban
case "${release}" in
ubuntu|debian)
- sudo apt-get remove fail2ban -y ;;
+ apt remove fail2ban -y ;;
centos)
- sudo yum -y remove fail2ban ;;
+ yum -y remove fail2ban ;;
fedora)
- sudo dnf -y remove fail2ban ;;
+ dnf -y remove fail2ban ;;
*)
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
exit 1 ;;
esac
- rm -rf /etc/fail2ban/*
+ rm -rf /etc/fail2ban
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n"
before_show_menu ;;
0)
@@ -917,7 +962,7 @@ show_menu() {
${green}19.${plain} Update Geo Files
${green}20.${plain} Active Firewall and open ports
${green}21.${plain} Speedtest by Ookla
- "
+"
show_status
echo && read -p "Please enter your selection [0-21]: " num
diff --git a/xray/process.go b/xray/process.go
index 42b2ee5c..0882c716 100644
--- a/xray/process.go
+++ b/xray/process.go
@@ -14,6 +14,7 @@ import (
"sync"
"syscall"
"x-ui/config"
+ "x-ui/logger"
"x-ui/util/common"
"github.com/Workiva/go-datastructures/queue"
@@ -47,10 +48,47 @@ func GetBlockedIPsPath() string {
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) {
p.Stop()
}
+
type Process struct {
*process
}