| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 | 
							- package controller
 
- import (
 
- 	"fmt"
 
- 	"net/http"
 
- 	"regexp"
 
- 	"strconv"
 
- 	"time"
 
- 	"github.com/mhsanaei/3x-ui/v2/web/global"
 
- 	"github.com/mhsanaei/3x-ui/v2/web/service"
 
- 	"github.com/gin-gonic/gin"
 
- )
 
- var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
 
- // ServerController handles server management and status-related operations.
 
- type ServerController struct {
 
- 	BaseController
 
- 	serverService  service.ServerService
 
- 	settingService service.SettingService
 
- 	lastStatus *service.Status
 
- 	lastVersions        []string
 
- 	lastGetVersionsTime int64 // unix seconds
 
- }
 
- // NewServerController creates a new ServerController, initializes routes, and starts background tasks.
 
- func NewServerController(g *gin.RouterGroup) *ServerController {
 
- 	a := &ServerController{}
 
- 	a.initRouter(g)
 
- 	a.startTask()
 
- 	return a
 
- }
 
- // initRouter sets up the routes for server status, Xray management, and utility endpoints.
 
- func (a *ServerController) initRouter(g *gin.RouterGroup) {
 
- 	g.GET("/status", a.status)
 
- 	g.GET("/cpuHistory/:bucket", a.getCpuHistoryBucket)
 
- 	g.GET("/getXrayVersion", a.getXrayVersion)
 
- 	g.GET("/getConfigJson", a.getConfigJson)
 
- 	g.GET("/getDb", a.getDb)
 
- 	g.GET("/getNewUUID", a.getNewUUID)
 
- 	g.GET("/getNewX25519Cert", a.getNewX25519Cert)
 
- 	g.GET("/getNewmldsa65", a.getNewmldsa65)
 
- 	g.GET("/getNewmlkem768", a.getNewmlkem768)
 
- 	g.GET("/getNewVlessEnc", a.getNewVlessEnc)
 
- 	g.POST("/stopXrayService", a.stopXrayService)
 
- 	g.POST("/restartXrayService", a.restartXrayService)
 
- 	g.POST("/installXray/:version", a.installXray)
 
- 	g.POST("/updateGeofile", a.updateGeofile)
 
- 	g.POST("/updateGeofile/:fileName", a.updateGeofile)
 
- 	g.POST("/logs/:count", a.getLogs)
 
- 	g.POST("/xraylogs/:count", a.getXrayLogs)
 
- 	g.POST("/importDB", a.importDB)
 
- 	g.POST("/getNewEchCert", a.getNewEchCert)
 
- }
 
- // refreshStatus updates the cached server status and collects CPU history.
 
- func (a *ServerController) refreshStatus() {
 
- 	a.lastStatus = a.serverService.GetStatus(a.lastStatus)
 
- 	// collect cpu history when status is fresh
 
- 	if a.lastStatus != nil {
 
- 		a.serverService.AppendCpuSample(time.Now(), a.lastStatus.Cpu)
 
- 	}
 
- }
 
- // startTask initiates background tasks for continuous status monitoring.
 
- func (a *ServerController) startTask() {
 
- 	webServer := global.GetWebServer()
 
- 	c := webServer.GetCron()
 
- 	c.AddFunc("@every 2s", func() {
 
- 		// Always refresh to keep CPU history collected continuously.
 
- 		// Sampling is lightweight and capped to ~6 hours in memory.
 
- 		a.refreshStatus()
 
- 	})
 
- }
 
- // status returns the current server status information.
 
- func (a *ServerController) status(c *gin.Context) { jsonObj(c, a.lastStatus, nil) }
 
- // getCpuHistoryBucket retrieves aggregated CPU usage history based on the specified time bucket.
 
- func (a *ServerController) getCpuHistoryBucket(c *gin.Context) {
 
- 	bucketStr := c.Param("bucket")
 
- 	bucket, err := strconv.Atoi(bucketStr)
 
- 	if err != nil || bucket <= 0 {
 
- 		jsonMsg(c, "invalid bucket", fmt.Errorf("bad bucket"))
 
- 		return
 
- 	}
 
- 	allowed := map[int]bool{
 
- 		2:   true, // Real-time view
 
- 		30:  true, // 30s intervals
 
- 		60:  true, // 1m intervals
 
- 		120: true, // 2m intervals
 
- 		180: true, // 3m intervals
 
- 		300: true, // 5m intervals
 
- 	}
 
- 	if !allowed[bucket] {
 
- 		jsonMsg(c, "invalid bucket", fmt.Errorf("unsupported bucket"))
 
- 		return
 
- 	}
 
- 	points := a.serverService.AggregateCpuHistory(bucket, 60)
 
- 	jsonObj(c, points, nil)
 
- }
 
- // getXrayVersion retrieves available Xray versions, with caching for 1 minute.
 
- func (a *ServerController) getXrayVersion(c *gin.Context) {
 
- 	now := time.Now().Unix()
 
- 	if now-a.lastGetVersionsTime <= 60 { // 1 minute cache
 
- 		jsonObj(c, a.lastVersions, nil)
 
- 		return
 
- 	}
 
- 	versions, err := a.serverService.GetXrayVersions()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "getVersion"), err)
 
- 		return
 
- 	}
 
- 	a.lastVersions = versions
 
- 	a.lastGetVersionsTime = now
 
- 	jsonObj(c, versions, nil)
 
- }
 
- // installXray installs or updates Xray to the specified version.
 
- func (a *ServerController) installXray(c *gin.Context) {
 
- 	version := c.Param("version")
 
- 	err := a.serverService.UpdateXray(version)
 
- 	jsonMsg(c, I18nWeb(c, "pages.index.xraySwitchVersionPopover"), err)
 
- }
 
- // updateGeofile updates the specified geo file for Xray.
 
- func (a *ServerController) updateGeofile(c *gin.Context) {
 
- 	fileName := c.Param("fileName")
 
- 	// Validate the filename for security (prevent path traversal attacks)
 
- 	if fileName != "" && !a.serverService.IsValidGeofileName(fileName) {
 
- 		jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"),
 
- 			fmt.Errorf("invalid filename: contains unsafe characters or path traversal patterns"))
 
- 		return
 
- 	}
 
- 	err := a.serverService.UpdateGeofile(fileName)
 
- 	jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"), err)
 
- }
 
- // stopXrayService stops the Xray service.
 
- func (a *ServerController) stopXrayService(c *gin.Context) {
 
- 	err := a.serverService.StopXrayService()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.xray.stopError"), err)
 
- 		return
 
- 	}
 
- 	jsonMsg(c, I18nWeb(c, "pages.xray.stopSuccess"), err)
 
- }
 
- // restartXrayService restarts the Xray service.
 
- func (a *ServerController) restartXrayService(c *gin.Context) {
 
- 	err := a.serverService.RestartXrayService()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.xray.restartError"), err)
 
- 		return
 
- 	}
 
- 	jsonMsg(c, I18nWeb(c, "pages.xray.restartSuccess"), err)
 
- }
 
- // getLogs retrieves the application logs based on count, level, and syslog filters.
 
- func (a *ServerController) getLogs(c *gin.Context) {
 
- 	count := c.Param("count")
 
- 	level := c.PostForm("level")
 
- 	syslog := c.PostForm("syslog")
 
- 	logs := a.serverService.GetLogs(count, level, syslog)
 
- 	jsonObj(c, logs, nil)
 
- }
 
- // getXrayLogs retrieves Xray logs with filtering options for direct, blocked, and proxy traffic.
 
- func (a *ServerController) getXrayLogs(c *gin.Context) {
 
- 	count := c.Param("count")
 
- 	filter := c.PostForm("filter")
 
- 	showDirect := c.PostForm("showDirect")
 
- 	showBlocked := c.PostForm("showBlocked")
 
- 	showProxy := c.PostForm("showProxy")
 
- 	var freedoms []string
 
- 	var blackholes []string
 
- 	//getting tags for freedom and blackhole outbounds
 
- 	config, err := a.settingService.GetDefaultXrayConfig()
 
- 	if err == nil && config != nil {
 
- 		if cfgMap, ok := config.(map[string]interface{}); ok {
 
- 			if outbounds, ok := cfgMap["outbounds"].([]interface{}); ok {
 
- 				for _, outbound := range outbounds {
 
- 					if obMap, ok := outbound.(map[string]interface{}); ok {
 
- 						switch obMap["protocol"] {
 
- 						case "freedom":
 
- 							if tag, ok := obMap["tag"].(string); ok {
 
- 								freedoms = append(freedoms, tag)
 
- 							}
 
- 						case "blackhole":
 
- 							if tag, ok := obMap["tag"].(string); ok {
 
- 								blackholes = append(blackholes, tag)
 
- 							}
 
- 						}
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	if len(freedoms) == 0 {
 
- 		freedoms = []string{"direct"}
 
- 	}
 
- 	if len(blackholes) == 0 {
 
- 		blackholes = []string{"blocked"}
 
- 	}
 
- 	logs := a.serverService.GetXrayLogs(count, filter, showDirect, showBlocked, showProxy, freedoms, blackholes)
 
- 	jsonObj(c, logs, nil)
 
- }
 
- // getConfigJson retrieves the Xray configuration as JSON.
 
- func (a *ServerController) getConfigJson(c *gin.Context) {
 
- 	configJson, err := a.serverService.GetConfigJson()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.index.getConfigError"), err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, configJson, nil)
 
- }
 
- // getDb downloads the database file.
 
- func (a *ServerController) getDb(c *gin.Context) {
 
- 	db, err := a.serverService.GetDb()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.index.getDatabaseError"), err)
 
- 		return
 
- 	}
 
- 	filename := "x-ui.db"
 
- 	if !isValidFilename(filename) {
 
- 		c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid filename"))
 
- 		return
 
- 	}
 
- 	// Set the headers for the response
 
- 	c.Header("Content-Type", "application/octet-stream")
 
- 	c.Header("Content-Disposition", "attachment; filename="+filename)
 
- 	// Write the file contents to the response
 
- 	c.Writer.Write(db)
 
- }
 
- func isValidFilename(filename string) bool {
 
- 	// Validate that the filename only contains allowed characters
 
- 	return filenameRegex.MatchString(filename)
 
- }
 
- // importDB imports a database file and restarts the Xray service.
 
- func (a *ServerController) importDB(c *gin.Context) {
 
- 	// Get the file from the request body
 
- 	file, _, err := c.Request.FormFile("db")
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.index.readDatabaseError"), err)
 
- 		return
 
- 	}
 
- 	defer file.Close()
 
- 	// Always restart Xray before return
 
- 	defer a.serverService.RestartXrayService()
 
- 	// lastGetStatusTime removed; no longer needed
 
- 	// Import it
 
- 	err = a.serverService.ImportDB(file)
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.index.importDatabaseError"), err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, I18nWeb(c, "pages.index.importDatabaseSuccess"), nil)
 
- }
 
- // getNewX25519Cert generates a new X25519 certificate.
 
- func (a *ServerController) getNewX25519Cert(c *gin.Context) {
 
- 	cert, err := a.serverService.GetNewX25519Cert()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewX25519CertError"), err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, cert, nil)
 
- }
 
- // getNewmldsa65 generates a new ML-DSA-65 key.
 
- func (a *ServerController) getNewmldsa65(c *gin.Context) {
 
- 	cert, err := a.serverService.GetNewmldsa65()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewmldsa65Error"), err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, cert, nil)
 
- }
 
- // getNewEchCert generates a new ECH certificate for the given SNI.
 
- func (a *ServerController) getNewEchCert(c *gin.Context) {
 
- 	sni := c.PostForm("sni")
 
- 	cert, err := a.serverService.GetNewEchCert(sni)
 
- 	if err != nil {
 
- 		jsonMsg(c, "get ech certificate", err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, cert, nil)
 
- }
 
- // getNewVlessEnc generates a new VLESS encryption key.
 
- func (a *ServerController) getNewVlessEnc(c *gin.Context) {
 
- 	out, err := a.serverService.GetNewVlessEnc()
 
- 	if err != nil {
 
- 		jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewVlessEncError"), err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, out, nil)
 
- }
 
- // getNewUUID generates a new UUID.
 
- func (a *ServerController) getNewUUID(c *gin.Context) {
 
- 	uuidResp, err := a.serverService.GetNewUUID()
 
- 	if err != nil {
 
- 		jsonMsg(c, "Failed to generate UUID", err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, uuidResp, nil)
 
- }
 
- // getNewmlkem768 generates a new ML-KEM-768 key.
 
- func (a *ServerController) getNewmlkem768(c *gin.Context) {
 
- 	out, err := a.serverService.GetNewmlkem768()
 
- 	if err != nil {
 
- 		jsonMsg(c, "Failed to generate mlkem768 keys", err)
 
- 		return
 
- 	}
 
- 	jsonObj(c, out, nil)
 
- }
 
 
  |