diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96ef2d7b..1c36c5cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,3 +146,79 @@ jobs: asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz overwrite: true prerelease: true + + # ================================= + # Windows Build + # ================================= + build-windows: + name: Build for Windows + permissions: + contents: write + strategy: + matrix: + platform: + - amd64 + runs-on: windows-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + check-latest: true + + - name: Build 3X-UI for Windows + shell: pwsh + run: | + $env:CGO_ENABLED="1" + $env:GOOS="windows" + $env:GOARCH="amd64" + go build -ldflags "-w -s" -o xui-release.exe -v main.go + + mkdir x-ui + Copy-Item xui-release.exe x-ui\ + mkdir x-ui\bin + cd x-ui\bin + + # Download Xray for Windows + $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.6.8/" + Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" + Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath . + Remove-Item "Xray-windows-64.zip" + Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue + Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat" + Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat" + Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat" + Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat" + Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat" + Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat" + Rename-Item xray.exe xray-windows-amd64.exe + cd .. + Copy-Item -Path ..\windows_files\* -Destination . -Recurse + cd .. + + - name: Package to Zip + shell: pwsh + run: | + Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip" + + - name: Upload files to Artifacts + uses: actions/upload-artifact@v4 + with: + name: x-ui-windows-amd64 + path: ./x-ui-windows-amd64.zip + + - name: Upload files to GH release + uses: svenstaro/upload-release-action@v2 + if: | + (github.event_name == 'release' && github.event.action == 'published') || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ github.ref }} + file: x-ui-windows-amd64.zip + asset_name: x-ui-windows-amd64.zip + overwrite: true + prerelease: true \ No newline at end of file diff --git a/database/model/model.go b/database/model/model.go index 874c6d69..23340b24 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -12,11 +12,11 @@ type Protocol string const ( VMESS Protocol = "vmess" VLESS Protocol = "vless" - DOKODEMO Protocol = "dokodemo-door" + Tunnel Protocol = "tunnel" HTTP Protocol = "http" Trojan Protocol = "trojan" Shadowsocks Protocol = "shadowsocks" - Socks Protocol = "socks" + Mixed Protocol = "mixed" WireGuard Protocol = "wireguard" ) diff --git a/sub/default.json b/sub/default.json index fff1b3ad..59c8a5ce 100644 --- a/sub/default.json +++ b/sub/default.json @@ -13,7 +13,7 @@ "inbounds": [ { "port": 10808, - "protocol": "socks", + "protocol": "mixed", "settings": { "auth": "noauth", "udp": true, @@ -28,7 +28,7 @@ ], "enabled": true }, - "tag": "socks" + "tag": "mixed" }, { "port": 10809, diff --git a/web/assets/js/model/dbinbound.js b/web/assets/js/model/dbinbound.js index 8687e781..d7c0946b 100644 --- a/web/assets/js/model/dbinbound.js +++ b/web/assets/js/model/dbinbound.js @@ -50,8 +50,8 @@ class DBInbound { return this.protocol === Protocols.SHADOWSOCKS; } - get isSocks() { - return this.protocol === Protocols.SOCKS; + get isMixed() { + return this.protocol === Protocols.MIXED; } get isHTTP() { diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index c2467c36..aecedf75 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -3,8 +3,8 @@ const Protocols = { VLESS: 'vless', TROJAN: 'trojan', SHADOWSOCKS: 'shadowsocks', - DOKODEMO: 'dokodemo-door', - SOCKS: 'socks', + TUNNEL: 'tunnel', + MIXED: 'mixed', HTTP: 'http', WIREGUARD: 'wireguard', }; @@ -729,7 +729,7 @@ class RealityStreamSettings extends XrayCommonClass { constructor( show = false, xver = 0, - dest = 'google.com:443', + target = 'google.com:443', serverNames = 'google.com,www.google.com', privateKey = '', minClientVer = '', @@ -742,7 +742,7 @@ class RealityStreamSettings extends XrayCommonClass { super(); this.show = show; this.xver = xver; - this.dest = dest; + this.target = target; this.serverNames = Array.isArray(serverNames) ? serverNames.join(",") : serverNames; this.privateKey = privateKey; this.minClientVer = minClientVer; @@ -767,7 +767,7 @@ class RealityStreamSettings extends XrayCommonClass { return new RealityStreamSettings( json.show, json.xver, - json.dest, + json.target, json.serverNames, json.privateKey, json.minClientVer, @@ -783,7 +783,7 @@ class RealityStreamSettings extends XrayCommonClass { return { show: this.show, xver: this.xver, - dest: this.dest, + target: this.target, serverNames: this.serverNames.split(","), privateKey: this.privateKey, minClientVer: this.minClientVer, @@ -1712,8 +1712,8 @@ Inbound.Settings = class extends XrayCommonClass { case Protocols.VLESS: return new Inbound.VLESSSettings(protocol); case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol); case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol); - case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol); - case Protocols.SOCKS: return new Inbound.SocksSettings(protocol); + case Protocols.TUNNEL: return new Inbound.TunnelSettings(protocol); + case Protocols.MIXED: return new Inbound.MixedSettings(protocol); case Protocols.HTTP: return new Inbound.HttpSettings(protocol); case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol); default: return null; @@ -1726,8 +1726,8 @@ Inbound.Settings = class extends XrayCommonClass { case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json); case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json); case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json); - case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json); - case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json); + case Protocols.TUNNEL: return Inbound.TunnelSettings.fromJson(json); + case Protocols.MIXED: return Inbound.MixedSettings.fromJson(json); case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json); default: return null; @@ -2327,7 +2327,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { }; -Inbound.DokodemoSettings = class extends Inbound.Settings { +Inbound.TunnelSettings = class extends Inbound.Settings { constructor( protocol, address, @@ -2345,8 +2345,8 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { } static fromJson(json = {}) { - return new Inbound.DokodemoSettings( - Protocols.DOKODEMO, + return new Inbound.TunnelSettings( + Protocols.TUNNEL, json.address, json.port, XrayCommonClass.toHeaders(json.portMap), @@ -2366,8 +2366,8 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { } }; -Inbound.SocksSettings = class extends Inbound.Settings { - constructor(protocol, auth = 'password', accounts = [new Inbound.SocksSettings.SocksAccount()], udp = false, ip = '127.0.0.1') { +Inbound.MixedSettings = class extends Inbound.Settings { + constructor(protocol, auth = 'password', accounts = [new Inbound.MixedSettings.SocksAccount()], udp = false, ip = '127.0.0.1') { super(protocol); this.auth = auth; this.accounts = accounts; @@ -2387,11 +2387,11 @@ Inbound.SocksSettings = class extends Inbound.Settings { let accounts; if (json.auth === 'password') { accounts = json.accounts.map( - account => Inbound.SocksSettings.SocksAccount.fromJson(account) + account => Inbound.MixedSettings.SocksAccount.fromJson(account) ) } - return new Inbound.SocksSettings( - Protocols.SOCKS, + return new Inbound.MixedSettings( + Protocols.MIXED, json.auth, accounts, json.udp, @@ -2408,7 +2408,7 @@ Inbound.SocksSettings = class extends Inbound.Settings { }; } }; -Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass { +Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass { constructor(user = RandomUtil.randomSeq(10), pass = RandomUtil.randomSeq(10)) { super(); this.user = user; @@ -2416,7 +2416,7 @@ Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass { } static fromJson(json = {}) { - return new Inbound.SocksSettings.SocksAccount(json.user, json.pass); + return new Inbound.MixedSettings.SocksAccount(json.user, json.pass); } }; diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 2d5660fb..e798ddc1 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -6,7 +6,7 @@ const Protocols = { VLESS: "vless", Trojan: "trojan", Shadowsocks: "shadowsocks", - Socks: "socks", + Mixed: "mixed", HTTP: "http", Wireguard: "wireguard" }; @@ -643,7 +643,7 @@ class Outbound extends CommonClass { Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, - Protocols.Socks + Protocols.Mixed ].includes(this.protocol); } @@ -652,7 +652,7 @@ class Outbound extends CommonClass { } hasServers() { - return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol); + return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Mixed, Protocols.HTTP].includes(this.protocol); } hasAddressPort() { @@ -662,13 +662,13 @@ class Outbound extends CommonClass { Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, - Protocols.Socks, + Protocols.Mixed, Protocols.HTTP ].includes(this.protocol); } hasUsername() { - return [Protocols.Socks, Protocols.HTTP].includes(this.protocol); + return [Protocols.Mixed, Protocols.HTTP].includes(this.protocol); } static fromJson(json = {}) { @@ -847,7 +847,7 @@ Outbound.Settings = class extends CommonClass { case Protocols.VLESS: return new Outbound.VLESSSettings(); case Protocols.Trojan: return new Outbound.TrojanSettings(); case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings(); - case Protocols.Socks: return new Outbound.SocksSettings(); + case Protocols.Mixed: return new Outbound.MixedSettings(); case Protocols.HTTP: return new Outbound.HttpSettings(); case Protocols.Wireguard: return new Outbound.WireguardSettings(); default: return null; @@ -863,7 +863,7 @@ Outbound.Settings = class extends CommonClass { case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json); case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json); case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json); - case Protocols.Socks: return Outbound.SocksSettings.fromJson(json); + case Protocols.Mixed: return Outbound.MixedSettings.fromJson(json); case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json); case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json); default: return null; @@ -1141,7 +1141,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass { } }; -Outbound.SocksSettings = class extends CommonClass { +Outbound.MixedSettings = class extends CommonClass { constructor(address, port, user, pass) { super(); this.address = address; @@ -1153,7 +1153,7 @@ Outbound.SocksSettings = class extends CommonClass { static fromJson(json = {}) { let servers = json.servers; if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }]; - return new Outbound.SocksSettings( + return new Outbound.MixedSettings( servers[0].address, servers[0].port, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, diff --git a/web/controller/api.go b/web/controller/api.go index 32af934e..6edd7939 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -9,6 +9,7 @@ import ( type APIController struct { BaseController inboundController *InboundController + serverController *ServerController Tgbot service.Tgbot } @@ -19,43 +20,22 @@ func NewAPIController(g *gin.RouterGroup) *APIController { } func (a *APIController) initRouter(g *gin.RouterGroup) { - g = g.Group("/panel/api/inbounds") - g.Use(a.checkLogin) + // Main API group + api := g.Group("/panel/api") + api.Use(a.checkLogin) - a.inboundController = NewInboundController(g) + // Inbounds API + inbounds := api.Group("/inbounds") + a.inboundController = NewInboundController(inbounds) - inboundRoutes := []struct { - Method string - Path string - Handler gin.HandlerFunc - }{ - {"GET", "/createbackup", a.createBackup}, - {"GET", "/list", a.inboundController.getInbounds}, - {"GET", "/get/:id", a.inboundController.getInbound}, - {"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics}, - {"GET", "/getClientTrafficsById/:id", a.inboundController.getClientTrafficsById}, - {"POST", "/add", a.inboundController.addInbound}, - {"POST", "/del/:id", a.inboundController.delInbound}, - {"POST", "/update/:id", a.inboundController.updateInbound}, - {"POST", "/clientIps/:email", a.inboundController.getClientIps}, - {"POST", "/clearClientIps/:email", a.inboundController.clearClientIps}, - {"POST", "/addClient", a.inboundController.addInboundClient}, - {"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient}, - {"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient}, - {"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic}, - {"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics}, - {"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, - {"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, - {"POST", "/onlines", a.inboundController.onlines}, - {"POST", "/lastOnline", a.inboundController.lastOnline}, - {"POST", "/updateClientTraffic/:email", a.inboundController.updateClientTraffic}, - } + // Server API + server := api.Group("/server") + a.serverController = NewServerController(server) - for _, route := range inboundRoutes { - g.Handle(route.Method, route.Path, route.Handler) - } + // Extra routes + api.GET("/backuptotgbot", a.BackuptoTgbot) } -func (a *APIController) createBackup(c *gin.Context) { +func (a *APIController) BackuptoTgbot(c *gin.Context) { a.Tgbot.SendBackupToAdmins() } diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 9ff2f302..10a58daa 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -24,9 +24,12 @@ func NewInboundController(g *gin.RouterGroup) *InboundController { } func (a *InboundController) initRouter(g *gin.RouterGroup) { - g = g.Group("/inbound") - g.POST("/list", a.getInbounds) + g.GET("/list", a.getInbounds) + g.GET("/get/:id", a.getInbound) + g.GET("/getClientTraffics/:email", a.getClientTraffics) + g.GET("/getClientTrafficsById/:id", a.getClientTrafficsById) + g.POST("/add", a.addInbound) g.POST("/del/:id", a.delInbound) g.POST("/update/:id", a.updateInbound) @@ -41,6 +44,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/delDepletedClients/:id", a.delDepletedClients) g.POST("/import", a.importInbound) g.POST("/onlines", a.onlines) + g.POST("/lastOnline", a.lastOnline) + g.POST("/updateClientTraffic/:email", a.updateClientTraffic) } func (a *InboundController) getInbounds(c *gin.Context) { diff --git a/web/controller/server.go b/web/controller/server.go index b1174b8f..d5ae688b 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -37,11 +37,17 @@ func NewServerController(g *gin.RouterGroup) *ServerController { } func (a *ServerController) initRouter(g *gin.RouterGroup) { - g = g.Group("/server") - g.Use(a.checkLogin) - g.POST("/status", a.status) - g.POST("/getXrayVersion", a.getXrayVersion) + g.GET("/status", a.status) + 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) @@ -49,13 +55,8 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/updateGeofile/:fileName", a.updateGeofile) g.POST("/logs/:count", a.getLogs) g.POST("/xraylogs/:count", a.getXrayLogs) - g.POST("/getConfigJson", a.getConfigJson) - g.GET("/getDb", a.getDb) g.POST("/importDB", a.importDB) - g.POST("/getNewX25519Cert", a.getNewX25519Cert) - g.POST("/getNewmldsa65", a.getNewmldsa65) g.POST("/getNewEchCert", a.getNewEchCert) - g.POST("/getNewVlessEnc", a.getNewVlessEnc) } func (a *ServerController) refreshStatus() { @@ -276,3 +277,22 @@ func (a *ServerController) getNewVlessEnc(c *gin.Context) { } jsonObj(c, out, nil) } + +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) +} + +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) +} diff --git a/web/controller/xui.go b/web/controller/xui.go index 5b4c0a18..1afbc427 100644 --- a/web/controller/xui.go +++ b/web/controller/xui.go @@ -8,6 +8,7 @@ type XUIController struct { BaseController inboundController *InboundController + serverController *ServerController settingController *SettingController xraySettingController *XraySettingController } @@ -28,6 +29,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) { g.GET("/xray", a.xraySettings) a.inboundController = NewInboundController(g) + a.serverController = NewServerController(g) a.settingController = NewSettingController(g) a.xraySettingController = NewXraySettingController(g) } diff --git a/web/html/form/inbound.html b/web/html/form/inbound.html index 38d21660..b173c980 100644 --- a/web/html/form/inbound.html +++ b/web/html/form/inbound.html @@ -101,14 +101,14 @@ {{template "form/shadowsocks"}} - - - +