Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/config.go12
-rw-r--r--database/db.go11
-rw-r--r--database/model/model.go76
-rw-r--r--logger/logger.go20
-rw-r--r--main.go14
-rw-r--r--sub/sub.go9
-rw-r--r--sub/subController.go7
-rw-r--r--sub/subJsonService.go3
-rw-r--r--sub/subService.go7
-rw-r--r--util/common/err.go4
-rw-r--r--util/common/format.go1
-rw-r--r--util/common/multi_error.go3
-rw-r--r--util/crypto/crypto.go3
-rw-r--r--util/json_util/json.go7
-rw-r--r--util/random/random.go5
-rw-r--r--util/reflect_util/reflect.go3
-rw-r--r--util/sys/psutil.go2
-rw-r--r--util/sys/sys_linux.go4
-rw-r--r--util/sys/sys_windows.go5
-rw-r--r--web/controller/api.go4
-rw-r--r--web/controller/base.go5
-rw-r--r--web/controller/inbound.go24
-rw-r--r--web/controller/index.go8
-rw-r--r--web/controller/server.go23
-rw-r--r--web/controller/setting.go10
-rw-r--r--web/controller/util.go8
-rw-r--r--web/controller/xray_setting.go10
-rw-r--r--web/controller/xui.go7
-rw-r--r--web/entity/entity.go111
-rw-r--r--web/global/global.go13
-rw-r--r--web/global/hashStorage.go18
-rw-r--r--web/job/check_client_ip_job.go2
-rw-r--r--web/job/check_cpu_usage.go4
-rw-r--r--web/job/check_hash_storage.go4
-rw-r--r--web/job/check_xray_running_job.go8
-rw-r--r--web/job/clear_logs_job.go2
-rw-r--r--web/job/periodic_traffic_reset_job.go4
-rw-r--r--web/job/stats_notify_job.go9
-rw-r--r--web/job/xray_traffic_job.go3
-rw-r--r--web/locale/locale.go19
-rw-r--r--web/middleware/domainValidator.go6
-rw-r--r--web/middleware/redirect.go3
-rw-r--r--web/network/auto_https_conn.go10
-rw-r--r--web/network/auto_https_listener.go6
-rw-r--r--web/service/inbound.go19
-rw-r--r--web/service/outbound.go2
-rw-r--r--web/service/panel.go2
-rw-r--r--web/service/server.go15
-rw-r--r--web/service/setting.go2
-rw-r--r--web/service/tgbot.go68
-rw-r--r--web/service/user.go4
-rw-r--r--web/service/warp.go2
-rw-r--r--web/service/xray.go16
-rw-r--r--web/service/xray_setting.go2
-rw-r--r--web/session/session.go12
-rw-r--r--web/web.go20
-rw-r--r--xray/api.go14
-rw-r--r--xray/client_traffic.go2
-rw-r--r--xray/config.go3
-rw-r--r--xray/inbound.go3
-rw-r--r--xray/log_writer.go3
-rw-r--r--xray/process.go29
-rw-r--r--xray/traffic.go2
63 files changed, 624 insertions, 113 deletions
diff --git a/config/config.go b/config/config.go
index d5fe65ff..c9a3e83c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,3 +1,5 @@
+// Package config provides configuration management utilities for the 3x-ui panel,
+// including version information, logging levels, database paths, and environment variable handling.
package config
import (
@@ -16,8 +18,10 @@ var version string
//go:embed name
var name string
+// LogLevel represents the logging level for the application.
type LogLevel string
+// Logging level constants
const (
Debug LogLevel = "debug"
Info LogLevel = "info"
@@ -26,14 +30,17 @@ const (
Error LogLevel = "error"
)
+// GetVersion returns the version string of the 3x-ui application.
func GetVersion() string {
return strings.TrimSpace(version)
}
+// GetName returns the name of the 3x-ui application.
func GetName() string {
return strings.TrimSpace(name)
}
+// GetLogLevel returns the current logging level based on environment variables or defaults to Info.
func GetLogLevel() LogLevel {
if IsDebug() {
return Debug
@@ -45,10 +52,12 @@ func GetLogLevel() LogLevel {
return LogLevel(logLevel)
}
+// IsDebug returns true if debug mode is enabled via the XUI_DEBUG environment variable.
func IsDebug() bool {
return os.Getenv("XUI_DEBUG") == "true"
}
+// GetBinFolderPath returns the path to the binary folder, defaulting to "bin" if not set via XUI_BIN_FOLDER.
func GetBinFolderPath() string {
binFolderPath := os.Getenv("XUI_BIN_FOLDER")
if binFolderPath == "" {
@@ -74,6 +83,7 @@ func getBaseDir() string {
return exeDir
}
+// GetDBFolderPath returns the path to the database folder based on environment variables or platform defaults.
func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath != "" {
@@ -85,10 +95,12 @@ func GetDBFolderPath() string {
return "/etc/x-ui"
}
+// GetDBPath returns the full path to the database file.
func GetDBPath() string {
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
}
+// GetLogFolder returns the path to the log folder based on environment variables or platform defaults.
func GetLogFolder() string {
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
if logFolderPath != "" {
diff --git a/database/db.go b/database/db.go
index 8414b118..6de81d79 100644
--- a/database/db.go
+++ b/database/db.go
@@ -1,3 +1,5 @@
+// Package database provides database initialization, migration, and management utilities
+// for the 3x-ui panel using GORM with SQLite.
package database
import (
@@ -45,6 +47,7 @@ func initModels() error {
return nil
}
+// initUser creates a default admin user if the users table is empty.
func initUser() error {
empty, err := isTableEmpty("users")
if err != nil {
@@ -68,6 +71,7 @@ func initUser() error {
return nil
}
+// runSeeders migrates user passwords to bcrypt and records seeder execution to prevent re-running.
func runSeeders(isUsersEmpty bool) error {
empty, err := isTableEmpty("history_of_seeders")
if err != nil {
@@ -107,12 +111,14 @@ func runSeeders(isUsersEmpty bool) error {
return nil
}
+// isTableEmpty returns true if the named table contains zero rows.
func isTableEmpty(tableName string) (bool, error) {
var count int64
err := db.Table(tableName).Count(&count).Error
return count == 0, err
}
+// InitDB sets up the database connection, migrates models, and runs seeders.
func InitDB(dbPath string) error {
dir := path.Dir(dbPath)
err := os.MkdirAll(dir, fs.ModePerm)
@@ -151,6 +157,7 @@ func InitDB(dbPath string) error {
return runSeeders(isUsersEmpty)
}
+// CloseDB closes the database connection if it exists.
func CloseDB() error {
if db != nil {
sqlDB, err := db.DB()
@@ -162,14 +169,17 @@ func CloseDB() error {
return nil
}
+// GetDB returns the global GORM database instance.
func GetDB() *gorm.DB {
return db
}
+// IsNotFound checks if the given error is a GORM record not found error.
func IsNotFound(err error) bool {
return err == gorm.ErrRecordNotFound
}
+// IsSQLiteDB checks if the given file is a valid SQLite database by reading its signature.
func IsSQLiteDB(file io.ReaderAt) (bool, error) {
signature := []byte("SQLite format 3\x00")
buf := make([]byte, len(signature))
@@ -180,6 +190,7 @@ func IsSQLiteDB(file io.ReaderAt) (bool, error) {
return bytes.Equal(buf, signature), nil
}
+// Checkpoint performs a WAL checkpoint on the SQLite database to ensure data consistency.
func Checkpoint() error {
// Update WAL
err := db.Exec("PRAGMA wal_checkpoint;").Error
diff --git a/database/model/model.go b/database/model/model.go
index abf8075c..720f0223 100644
--- a/database/model/model.go
+++ b/database/model/model.go
@@ -1,3 +1,4 @@
+// Package model defines the database models and data structures used by the 3x-ui panel.
package model
import (
@@ -7,8 +8,10 @@ import (
"github.com/mhsanaei/3x-ui/v2/xray"
)
+// Protocol represents the protocol type for Xray inbounds.
type Protocol string
+// Protocol constants for different Xray inbound protocols
const (
VMESS Protocol = "vmess"
VLESS Protocol = "vless"
@@ -20,27 +23,29 @@ const (
WireGuard Protocol = "wireguard"
)
+// User represents a user account in the 3x-ui panel.
type User struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username"`
Password string `json:"password"`
}
+// Inbound represents an Xray inbound configuration with traffic statistics and settings.
type Inbound struct {
- Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
- UserId int `json:"-"`
- Up int64 `json:"up" form:"up"`
- Down int64 `json:"down" form:"down"`
- Total int64 `json:"total" form:"total"`
- AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"`
- Remark string `json:"remark" form:"remark"`
- Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"`
- ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
- TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"`
- LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"`
- ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
+ Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` // Unique identifier
+ UserId int `json:"-"` // Associated user ID
+ Up int64 `json:"up" form:"up"` // Upload traffic in bytes
+ Down int64 `json:"down" form:"down"` // Download traffic in bytes
+ Total int64 `json:"total" form:"total"` // Total traffic limit in bytes
+ AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"` // All-time traffic usage
+ Remark string `json:"remark" form:"remark"` // Human-readable remark
+ Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled
+ ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp
+ TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"` // Traffic reset schedule
+ LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp
+ ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics
- // config part
+ // Xray configuration fields
Listen string `json:"listen" form:"listen"`
Port int `json:"port" form:"port"`
Protocol Protocol `json:"protocol" form:"protocol"`
@@ -50,6 +55,7 @@ type Inbound struct {
Sniffing string `json:"sniffing" form:"sniffing"`
}
+// OutboundTraffics tracks traffic statistics for Xray outbound connections.
type OutboundTraffics struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Tag string `json:"tag" form:"tag" gorm:"unique"`
@@ -58,17 +64,20 @@ type OutboundTraffics struct {
Total int64 `json:"total" form:"total" gorm:"default:0"`
}
+// InboundClientIps stores IP addresses associated with inbound clients for access control.
type InboundClientIps struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
Ips string `json:"ips" form:"ips"`
}
+// HistoryOfSeeders tracks which database seeders have been executed to prevent re-running.
type HistoryOfSeeders struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
SeederName string `json:"seederName"`
}
+// GenXrayInboundConfig generates an Xray inbound configuration from the Inbound model.
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
listen := i.Listen
if listen != "" {
@@ -85,33 +94,36 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
}
}
+// Setting stores key-value configuration settings for the 3x-ui panel.
type Setting struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Key string `json:"key" form:"key"`
Value string `json:"value" form:"value"`
}
+// Client represents a client configuration for Xray inbounds with traffic limits and settings.
type Client struct {
- ID string `json:"id"`
- Security string `json:"security"`
- Password string `json:"password"`
- Flow string `json:"flow"`
- Email string `json:"email"`
- LimitIP int `json:"limitIp"`
- TotalGB int64 `json:"totalGB" form:"totalGB"`
- ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
- Enable bool `json:"enable" form:"enable"`
- TgID int64 `json:"tgId" form:"tgId"`
- SubID string `json:"subId" form:"subId"`
- Comment string `json:"comment" form:"comment"`
- Reset int `json:"reset" form:"reset"`
- CreatedAt int64 `json:"created_at,omitempty"`
- UpdatedAt int64 `json:"updated_at,omitempty"`
+ ID string `json:"id"` // Unique client identifier
+ Security string `json:"security"` // Security method (e.g., "auto", "aes-128-gcm")
+ Password string `json:"password"` // Client password
+ Flow string `json:"flow"` // Flow control (XTLS)
+ Email string `json:"email"` // Client email identifier
+ LimitIP int `json:"limitIp"` // IP limit for this client
+ TotalGB int64 `json:"totalGB" form:"totalGB"` // Total traffic limit in GB
+ ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp
+ Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
+ TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
+ SubID string `json:"subId" form:"subId"` // Subscription identifier
+ Comment string `json:"comment" form:"comment"` // Client comment
+ Reset int `json:"reset" form:"reset"` // Reset period in days
+ CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
+ UpdatedAt int64 `json:"updated_at,omitempty"` // Last update timestamp
}
+// VLESSSettings contains VLESS protocol-specific configuration settings.
type VLESSSettings struct {
- Clients []Client `json:"clients"`
- Decryption string `json:"decryption"`
- Encryption string `json:"encryption"`
- Fallbacks []any `json:"fallbacks"`
+ Clients []Client `json:"clients"` // List of VLESS clients
+ Decryption string `json:"decryption"` // Decryption method
+ Encryption string `json:"encryption"` // Encryption method (usually "none" for VLESS)
+ Fallbacks []any `json:"fallbacks"` // Fallback configurations
}
diff --git a/logger/logger.go b/logger/logger.go
index 3705c3df..ccacf697 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -1,3 +1,5 @@
+// Package logger provides logging functionality for the 3x-ui panel with
+// buffered log storage and multiple log levels.
package logger
import (
@@ -9,7 +11,11 @@ import (
)
var (
- logger *logging.Logger
+ logger *logging.Logger
+
+ // addToBuffer appends a log entry into the in-memory ring buffer used for
+ // retrieving recent logs via the web UI. It keeps the buffer bounded to avoid
+ // uncontrolled growth.
logBuffer []struct {
time string
level logging.Level
@@ -21,6 +27,7 @@ func init() {
InitLogger(logging.INFO)
}
+// InitLogger initializes the logger with the specified logging level.
func InitLogger(level logging.Level) {
newLogger := logging.MustGetLogger("x-ui")
var err error
@@ -47,51 +54,61 @@ func InitLogger(level logging.Level) {
logger = newLogger
}
+// Debug logs a debug message and adds it to the log buffer.
func Debug(args ...any) {
logger.Debug(args...)
addToBuffer("DEBUG", fmt.Sprint(args...))
}
+// Debugf logs a formatted debug message and adds it to the log buffer.
func Debugf(format string, args ...any) {
logger.Debugf(format, args...)
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
}
+// Info logs an info message and adds it to the log buffer.
func Info(args ...any) {
logger.Info(args...)
addToBuffer("INFO", fmt.Sprint(args...))
}
+// Infof logs a formatted info message and adds it to the log buffer.
func Infof(format string, args ...any) {
logger.Infof(format, args...)
addToBuffer("INFO", fmt.Sprintf(format, args...))
}
+// Notice logs a notice message and adds it to the log buffer.
func Notice(args ...any) {
logger.Notice(args...)
addToBuffer("NOTICE", fmt.Sprint(args...))
}
+// Noticef logs a formatted notice message and adds it to the log buffer.
func Noticef(format string, args ...any) {
logger.Noticef(format, args...)
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
}
+// Warning logs a warning message and adds it to the log buffer.
func Warning(args ...any) {
logger.Warning(args...)
addToBuffer("WARNING", fmt.Sprint(args...))
}
+// Warningf logs a formatted warning message and adds it to the log buffer.
func Warningf(format string, args ...any) {
logger.Warningf(format, args...)
addToBuffer("WARNING", fmt.Sprintf(format, args...))
}
+// Error logs an error message and adds it to the log buffer.
func Error(args ...any) {
logger.Error(args...)
addToBuffer("ERROR", fmt.Sprint(args...))
}
+// Errorf logs a formatted error message and adds it to the log buffer.
func Errorf(format string, args ...any) {
logger.Errorf(format, args...)
addToBuffer("ERROR", fmt.Sprintf(format, args...))
@@ -115,6 +132,7 @@ func addToBuffer(level string, newLog string) {
})
}
+// GetLogs retrieves up to c log entries from the buffer that are at or below the specified level.
func GetLogs(c int, level string) []string {
var output []string
logLevel, _ := logging.LogLevel(level)
diff --git a/main.go b/main.go
index d02a37eb..119dc4d9 100644
--- a/main.go
+++ b/main.go
@@ -1,3 +1,5 @@
+// Package main is the entry point for the 3x-ui web panel application.
+// It initializes the database, web server, and handles command-line operations for managing the panel.
package main
import (
@@ -22,6 +24,7 @@ import (
"github.com/op/go-logging"
)
+// runWebServer initializes and starts the web server for the 3x-ui panel.
func runWebServer() {
log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
@@ -111,6 +114,7 @@ func runWebServer() {
}
}
+// resetSetting resets all panel settings to their default values.
func resetSetting() {
err := database.InitDB(config.GetDBPath())
if err != nil {
@@ -127,6 +131,7 @@ func resetSetting() {
}
}
+// showSetting displays the current panel settings if show is true.
func showSetting(show bool) {
if show {
settingService := service.SettingService{}
@@ -176,6 +181,7 @@ func showSetting(show bool) {
}
}
+// updateTgbotEnableSts enables or disables the Telegram bot notifications based on the status parameter.
func updateTgbotEnableSts(status bool) {
settingService := service.SettingService{}
currentTgSts, err := settingService.GetTgbotEnabled()
@@ -195,6 +201,7 @@ func updateTgbotEnableSts(status bool) {
}
}
+// updateTgbotSetting updates Telegram bot settings including token, chat ID, and runtime schedule.
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
@@ -232,6 +239,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
}
}
+// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
err := database.InitDB(config.GetDBPath())
if err != nil {
@@ -290,6 +298,7 @@ func updateSetting(port int, username string, password string, webBasePath strin
}
}
+// updateCert updates the SSL certificate files for the panel.
func updateCert(publicKey string, privateKey string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
@@ -317,6 +326,7 @@ func updateCert(publicKey string, privateKey string) {
}
}
+// GetCertificate displays the current SSL certificate settings if getCert is true.
func GetCertificate(getCert bool) {
if getCert {
settingService := service.SettingService{}
@@ -334,6 +344,7 @@ func GetCertificate(getCert bool) {
}
}
+// GetListenIP displays the current panel listen IP address if getListen is true.
func GetListenIP(getListen bool) {
if getListen {
@@ -348,6 +359,7 @@ func GetListenIP(getListen bool) {
}
}
+// migrateDb performs database migration operations for the 3x-ui panel.
func migrateDb() {
inboundService := service.InboundService{}
@@ -360,6 +372,8 @@ func migrateDb() {
fmt.Println("Migration done!")
}
+// main is the entry point of the 3x-ui application.
+// It parses command-line arguments to run the web server, migrate database, or update settings.
func main() {
if len(os.Args) < 2 {
runWebServer()
diff --git a/sub/sub.go b/sub/sub.go
index 448842ae..c5445339 100644
--- a/sub/sub.go
+++ b/sub/sub.go
@@ -1,3 +1,5 @@
+// Package sub provides subscription server functionality for the 3x-ui panel,
+// including HTTP/HTTPS servers for serving subscription links and JSON configurations.
package sub
import (
@@ -39,6 +41,7 @@ func setEmbeddedTemplates(engine *gin.Engine) error {
return nil
}
+// Server represents the subscription server that serves subscription links and JSON configurations.
type Server struct {
httpServer *http.Server
listener net.Listener
@@ -50,6 +53,7 @@ type Server struct {
cancel context.CancelFunc
}
+// NewServer creates a new subscri